forked from catapult-project/catapult
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsource_filter_parser.js
224 lines (202 loc) · 7.1 KB
/
source_filter_parser.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var SourceFilterParser = (function() {
'use strict';
/**
* Parses |filterText|, extracting a sort method, a list of filters, and a
* copy of |filterText| with all sort parameters removed.
*/
function SourceFilterParser(filterText) {
// Final output will be stored here.
this.filter = null;
this.sort = {};
this.filterTextWithoutSort = '';
var filterList = parseFilter_(filterText);
// Text filters are stored here as strings and then added as a function at
// the end, for performance reasons.
var textFilters = [];
// Filter functions are first created individually, and then merged.
var filterFunctions = [];
for (var i = 0; i < filterList.length; ++i) {
var filterElement = filterList[i].parsed;
var negated = filterList[i].negated;
var sort = parseSortDirective_(filterElement, negated);
if (sort) {
this.sort = sort;
continue;
}
this.filterTextWithoutSort += filterList[i].original;
var filter = parseRestrictDirective_(filterElement, negated);
if (!filter)
filter = parseStringDirective_(filterElement, negated);
if (filter) {
if (negated) {
filter = (function(func, sourceEntry) {
return !func(sourceEntry);
}).bind(null, filter);
}
filterFunctions.push(filter);
continue;
}
textFilters.push({text: filterElement, negated: negated});
}
// Create a single filter for all text filters, so they can share a
// TabePrinter.
filterFunctions.push(textFilter_.bind(null, textFilters));
// Create function to go through all the filters.
this.filter = function(sourceEntry) {
for (var i = 0; i < filterFunctions.length; ++i) {
if (!filterFunctions[i](sourceEntry))
return false;
}
return true;
};
}
/**
* Parses a single "sort:" directive, and returns a dictionary containing
* the sort function and direction. Returns null on failure, including
* the case when no such sort function exists.
*/
function parseSortDirective_(filterElement, backwards) {
var match = /^sort:(.*)$/.exec(filterElement);
if (!match)
return null;
return {method: match[1], backwards: backwards};
}
/**
* Tries to parses |filterElement| as a single "is:" directive, and returns a
* new filter function. Returns null on failure.
*/
function parseRestrictDirective_(filterElement) {
var match = /^is:(.*)$/.exec(filterElement);
if (!match)
return null;
if (match[1] == 'active') {
return function(sourceEntry) {
return !sourceEntry.isInactive();
};
}
if (match[1] == 'error') {
return function(sourceEntry) {
return sourceEntry.isError();
};
}
return null;
}
/**
* Tries to parse |filterElement| as a single filter of a type that takes
* arbitrary strings as input, and returns a new filter function on success.
* Returns null on failure.
*/
function parseStringDirective_(filterElement) {
var match = RegExp('^([^:]*):(.*)$').exec(filterElement);
if (!match)
return null;
// Split parameters around commas and remove empty elements.
var parameters = match[2].split(',');
parameters = parameters.filter(function(string) {
return string.length > 0;
});
if (match[1] == 'type') {
return function(sourceEntry) {
var i;
var sourceType = sourceEntry.getSourceTypeString().toLowerCase();
for (i = 0; i < parameters.length; ++i) {
if (sourceType.search(parameters[i]) != -1)
return true;
}
return false;
};
}
if (match[1] == 'id') {
return function(sourceEntry) {
return parameters.indexOf(sourceEntry.getSourceId() + '') != -1;
};
}
return null;
}
/**
* Takes in the text of a filter and returns a list of
* {parsed, original, negated} values that correspond to substrings of the
* filter before and after filtering, and whether or not it started with a
* '-'. Extra whitespace other than a single character after each element is
* ignored. Parsed strings are all lowercase.
*/
function parseFilter_(filterText) {
// Assemble a list of quoted and unquoted strings in the filter.
var filterList = [];
var position = 0;
while (position < filterText.length) {
var inQuote = false;
var filterElement = '';
var negated = false;
var startPosition = position;
while (position < filterText.length) {
var nextCharacter = filterText[position];
++position;
if (nextCharacter == '\\' && position < filterText.length) {
// If there's a backslash, skip the backslash and add the next
// character to the element.
filterElement += filterText[position];
++position;
continue;
} else if (nextCharacter == '"') {
// If there's an unescaped quote character, toggle |inQuote| without
// modifying the element.
inQuote = !inQuote;
} else if (!inQuote && /\s/.test(nextCharacter)) {
// If not in a quote and have a whitespace character, that's the
// end of the element.
break;
} else if (nextCharacter == '-' && startPosition == position - 1) {
// If this is the first character, and it's a '-', this entry is
// negated.
negated = true;
} else {
// Otherwise, add the next character to the element.
filterElement += nextCharacter;
}
}
if (filterElement.length > 0) {
var filter = {
parsed: filterElement.toLowerCase(),
original: filterText.substring(startPosition, position),
negated: negated,
};
filterList.push(filter);
}
}
return filterList;
}
/**
* Takes in a list of text filters and a SourceEntry. Each filter has
* "text" and "negated" fields. Returns true if the SourceEntry matches all
* filters in the (possibly empty) list.
*/
function textFilter_(textFilters, sourceEntry) {
var tablePrinter = null;
for (var i = 0; i < textFilters.length; ++i) {
var text = textFilters[i].text;
var negated = textFilters[i].negated;
var match = false;
// The description is often not contained in one of the log entries.
// The source type almost never is, so check for them directly.
var description = sourceEntry.getDescription().toLowerCase();
var type = sourceEntry.getSourceTypeString().toLowerCase();
if (description.indexOf(text) != -1 || type.indexOf(text) != -1) {
match = true;
} else {
if (!tablePrinter)
tablePrinter = sourceEntry.createTablePrinter();
match = tablePrinter.search(text);
}
if (negated)
match = !match;
if (!match)
return false;
}
return true;
}
return SourceFilterParser;
})();