OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'package:path/path.dart' as p; | 5 import 'package:path/path.dart' as p; |
6 import 'package:collection/collection.dart'; | 6 import 'package:collection/collection.dart'; |
7 | 7 |
8 import 'utils.dart'; | 8 import 'utils.dart'; |
9 | 9 |
10 const _SEPARATOR = 0x2F; // "/" | 10 const _SEPARATOR = 0x2F; // "/" |
(...skipping 20 matching lines...) Expand all Loading... |
31 | 31 |
32 /// Returns a new glob with all the options bubbled to the top level. | 32 /// Returns a new glob with all the options bubbled to the top level. |
33 /// | 33 /// |
34 /// In particular, this returns a glob AST with two guarantees: | 34 /// In particular, this returns a glob AST with two guarantees: |
35 /// | 35 /// |
36 /// 1. There are no [OptionsNode]s other than the one at the top level. | 36 /// 1. There are no [OptionsNode]s other than the one at the top level. |
37 /// 2. It matches the same set of paths as [this]. | 37 /// 2. It matches the same set of paths as [this]. |
38 /// | 38 /// |
39 /// For example, given the glob `{foo,bar}/{click/clack}`, this would return | 39 /// For example, given the glob `{foo,bar}/{click/clack}`, this would return |
40 /// `{foo/click,foo/clack,bar/click,bar/clack}`. | 40 /// `{foo/click,foo/clack,bar/click,bar/clack}`. |
41 OptionsNode flattenOptions() => new OptionsNode( | 41 OptionsNode flattenOptions() => new OptionsNode([ |
42 [new SequenceNode([this], caseSensitive: caseSensitive)], | 42 new SequenceNode([this], caseSensitive: caseSensitive) |
43 caseSensitive: caseSensitive); | 43 ], caseSensitive: caseSensitive); |
44 | 44 |
45 /// Returns whether this glob matches [string]. | 45 /// Returns whether this glob matches [string]. |
46 bool matches(String string) { | 46 bool matches(String string) { |
47 if (_regExp == null) { | 47 if (_regExp == null) { |
48 _regExp = new RegExp('^${_toRegExp()}\$', caseSensitive: caseSensitive); | 48 _regExp = new RegExp('^${_toRegExp()}\$', caseSensitive: caseSensitive); |
49 } | 49 } |
50 return _regExp.hasMatch(string); | 50 return _regExp.hasMatch(string); |
51 } | 51 } |
52 | 52 |
53 /// Subclasses should override this to return a regular expression component. | 53 /// Subclasses should override this to return a regular expression component. |
(...skipping 10 matching lines...) Expand all Loading... |
64 | 64 |
65 SequenceNode(Iterable<AstNode> nodes, {bool caseSensitive: true}) | 65 SequenceNode(Iterable<AstNode> nodes, {bool caseSensitive: true}) |
66 : nodes = nodes.toList(), | 66 : nodes = nodes.toList(), |
67 super._(caseSensitive); | 67 super._(caseSensitive); |
68 | 68 |
69 OptionsNode flattenOptions() { | 69 OptionsNode flattenOptions() { |
70 if (nodes.isEmpty) { | 70 if (nodes.isEmpty) { |
71 return new OptionsNode([this], caseSensitive: caseSensitive); | 71 return new OptionsNode([this], caseSensitive: caseSensitive); |
72 } | 72 } |
73 | 73 |
74 var sequences = nodes.first.flattenOptions().options | 74 var sequences = |
75 .map((sequence) => sequence.nodes); | 75 nodes.first.flattenOptions().options.map((sequence) => sequence.nodes); |
76 for (var node in nodes.skip(1)) { | 76 for (var node in nodes.skip(1)) { |
77 // Concatenate all sequences in the next options node ([nextSequences]) | 77 // Concatenate all sequences in the next options node ([nextSequences]) |
78 // onto all previous sequences ([sequences]). | 78 // onto all previous sequences ([sequences]). |
79 var nextSequences = node.flattenOptions().options; | 79 var nextSequences = node.flattenOptions().options; |
80 sequences = sequences.expand((sequence) { | 80 sequences = sequences.expand((sequence) { |
81 return nextSequences.map((nextSequence) { | 81 return nextSequences.map((nextSequence) { |
82 return sequence.toList()..addAll(nextSequence.nodes); | 82 return sequence.toList()..addAll(nextSequence.nodes); |
83 }); | 83 }); |
84 }); | 84 }); |
85 } | 85 } |
86 | 86 |
87 return new OptionsNode(sequences.map((sequence) { | 87 return new OptionsNode(sequences.map((sequence) { |
88 // Combine any adjacent LiteralNodes in [sequence]. | 88 // Combine any adjacent LiteralNodes in [sequence]. |
89 return new SequenceNode(sequence.fold/*<List<AstNode>>*/([], (combined, no
de) { | 89 return new SequenceNode( |
90 if (combined.isEmpty || combined.last is! LiteralNode || | 90 sequence.fold<List<AstNode>>([], (combined, node) { |
91 node is! LiteralNode) { | 91 if (combined.isEmpty || |
92 return combined..add(node); | 92 combined.last is! LiteralNode || |
93 } | 93 node is! LiteralNode) { |
| 94 return combined..add(node); |
| 95 } |
94 | 96 |
95 combined[combined.length - 1] = new LiteralNode( | 97 combined[combined.length - 1] = new LiteralNode( |
96 // TODO(nweiz): Avoid casting when sdk#25565 is fixed. | 98 // TODO(nweiz): Avoid casting when sdk#25565 is fixed. |
97 (combined.last as LiteralNode).text + (node as LiteralNode).text, | 99 (combined.last as LiteralNode).text + |
98 caseSensitive: caseSensitive); | 100 (node as LiteralNode).text, |
99 return combined; | 101 caseSensitive: caseSensitive); |
100 }), caseSensitive: caseSensitive); | 102 return combined; |
| 103 }), |
| 104 caseSensitive: caseSensitive); |
101 }), caseSensitive: caseSensitive); | 105 }), caseSensitive: caseSensitive); |
102 } | 106 } |
103 | 107 |
104 /// Splits this glob into components along its path separators. | 108 /// Splits this glob into components along its path separators. |
105 /// | 109 /// |
106 /// For example, given the glob `foo/*/*.dart`, this would return three globs: | 110 /// For example, given the glob `foo/*/*.dart`, this would return three globs: |
107 /// `foo`, `*`, and `*.dart`. | 111 /// `foo`, `*`, and `*.dart`. |
108 /// | 112 /// |
109 /// Path separators within options nodes are not split. For example, | 113 /// Path separators within options nodes are not split. For example, |
110 /// `foo/{bar,baz/bang}/qux` will return three globs: `foo`, `{bar,baz/bang}`, | 114 /// `foo/{bar,baz/bang}/qux` will return three globs: `foo`, `{bar,baz/bang}`, |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
178 addNode(new LiteralNode(components.last, caseSensitive: caseSensitive)); | 182 addNode(new LiteralNode(components.last, caseSensitive: caseSensitive)); |
179 if (literal.text.endsWith('/')) finishComponent(); | 183 if (literal.text.endsWith('/')) finishComponent(); |
180 } | 184 } |
181 | 185 |
182 finishComponent(); | 186 finishComponent(); |
183 return componentsToReturn; | 187 return componentsToReturn; |
184 } | 188 } |
185 | 189 |
186 String _toRegExp() => nodes.map((node) => node._toRegExp()).join(); | 190 String _toRegExp() => nodes.map((node) => node._toRegExp()).join(); |
187 | 191 |
188 bool operator==(Object other) => other is SequenceNode && | 192 bool operator ==(Object other) => |
| 193 other is SequenceNode && |
189 const IterableEquality().equals(nodes, other.nodes); | 194 const IterableEquality().equals(nodes, other.nodes); |
190 | 195 |
191 int get hashCode => const IterableEquality().hash(nodes); | 196 int get hashCode => const IterableEquality().hash(nodes); |
192 | 197 |
193 String toString() => nodes.join(); | 198 String toString() => nodes.join(); |
194 } | 199 } |
195 | 200 |
196 /// A node matching zero or more non-separator characters. | 201 /// A node matching zero or more non-separator characters. |
197 class StarNode extends AstNode { | 202 class StarNode extends AstNode { |
198 StarNode({bool caseSensitive: true}) : super._(caseSensitive); | 203 StarNode({bool caseSensitive: true}) : super._(caseSensitive); |
199 | 204 |
200 String _toRegExp() => '[^/]*'; | 205 String _toRegExp() => '[^/]*'; |
201 | 206 |
202 bool operator==(Object other) => other is StarNode; | 207 bool operator ==(Object other) => other is StarNode; |
203 | 208 |
204 int get hashCode => 0; | 209 int get hashCode => 0; |
205 | 210 |
206 String toString() => '*'; | 211 String toString() => '*'; |
207 } | 212 } |
208 | 213 |
209 /// A node matching zero or more characters that may be separators. | 214 /// A node matching zero or more characters that may be separators. |
210 class DoubleStarNode extends AstNode { | 215 class DoubleStarNode extends AstNode { |
211 /// The path context for the glob. | 216 /// The path context for the glob. |
212 /// | 217 /// |
(...skipping 21 matching lines...) Expand all Loading... |
234 assert(_context.style == p.Style.url); | 239 assert(_context.style == p.Style.url); |
235 buffer.write(r'[a-zA-Z][-+.a-zA-Z\d]*://|/'); | 240 buffer.write(r'[a-zA-Z][-+.a-zA-Z\d]*://|/'); |
236 } | 241 } |
237 | 242 |
238 // Use `[^]` rather than `.` so that it matches newlines as well. | 243 // Use `[^]` rather than `.` so that it matches newlines as well. |
239 buffer.write(r'))[^]*'); | 244 buffer.write(r'))[^]*'); |
240 | 245 |
241 return buffer.toString(); | 246 return buffer.toString(); |
242 } | 247 } |
243 | 248 |
244 bool operator==(Object other) => other is DoubleStarNode; | 249 bool operator ==(Object other) => other is DoubleStarNode; |
245 | 250 |
246 int get hashCode => 1; | 251 int get hashCode => 1; |
247 | 252 |
248 String toString() => '**'; | 253 String toString() => '**'; |
249 } | 254 } |
250 | 255 |
251 /// A node matching a single non-separator character. | 256 /// A node matching a single non-separator character. |
252 class AnyCharNode extends AstNode { | 257 class AnyCharNode extends AstNode { |
253 AnyCharNode({bool caseSensitive: true}) : super._(caseSensitive); | 258 AnyCharNode({bool caseSensitive: true}) : super._(caseSensitive); |
254 | 259 |
255 String _toRegExp() => '[^/]'; | 260 String _toRegExp() => '[^/]'; |
256 | 261 |
257 bool operator==(Object other) => other is AnyCharNode; | 262 bool operator ==(Object other) => other is AnyCharNode; |
258 | 263 |
259 int get hashCode => 2; | 264 int get hashCode => 2; |
260 | 265 |
261 String toString() => '?'; | 266 String toString() => '?'; |
262 } | 267 } |
263 | 268 |
264 /// A node matching a single character in a range of options. | 269 /// A node matching a single character in a range of options. |
265 class RangeNode extends AstNode { | 270 class RangeNode extends AstNode { |
266 /// The ranges matched by this node. | 271 /// The ranges matched by this node. |
267 /// | 272 /// |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
312 buffer.write(regExpQuote(start)); | 317 buffer.write(regExpQuote(start)); |
313 if (range.isSingleton) continue; | 318 if (range.isSingleton) continue; |
314 buffer.write('-'); | 319 buffer.write('-'); |
315 buffer.write(regExpQuote(new String.fromCharCodes([range.max]))); | 320 buffer.write(regExpQuote(new String.fromCharCodes([range.max]))); |
316 } | 321 } |
317 | 322 |
318 buffer.write(']'); | 323 buffer.write(']'); |
319 return buffer.toString(); | 324 return buffer.toString(); |
320 } | 325 } |
321 | 326 |
322 bool operator==(Object other) { | 327 bool operator ==(Object other) { |
323 if (other is! RangeNode) return false; | 328 if (other is! RangeNode) return false; |
324 if ((other as RangeNode).negated != negated) return false; | 329 if ((other as RangeNode).negated != negated) return false; |
325 return const SetEquality().equals(ranges, (other as RangeNode).ranges); | 330 return const SetEquality().equals(ranges, (other as RangeNode).ranges); |
326 } | 331 } |
327 | 332 |
328 int get hashCode => (negated ? 1 : 3) * const SetEquality().hash(ranges); | 333 int get hashCode => (negated ? 1 : 3) * const SetEquality().hash(ranges); |
329 | 334 |
330 String toString() { | 335 String toString() { |
331 var buffer = new StringBuffer()..write('['); | 336 var buffer = new StringBuffer()..write('['); |
332 for (var range in ranges) { | 337 for (var range in ranges) { |
(...skipping 19 matching lines...) Expand all Loading... |
352 : options = options.toList(), | 357 : options = options.toList(), |
353 super._(caseSensitive); | 358 super._(caseSensitive); |
354 | 359 |
355 OptionsNode flattenOptions() => new OptionsNode( | 360 OptionsNode flattenOptions() => new OptionsNode( |
356 options.expand((option) => option.flattenOptions().options), | 361 options.expand((option) => option.flattenOptions().options), |
357 caseSensitive: caseSensitive); | 362 caseSensitive: caseSensitive); |
358 | 363 |
359 String _toRegExp() => | 364 String _toRegExp() => |
360 '(?:${options.map((option) => option._toRegExp()).join("|")})'; | 365 '(?:${options.map((option) => option._toRegExp()).join("|")})'; |
361 | 366 |
362 bool operator==(Object other) => other is OptionsNode && | 367 bool operator ==(Object other) => |
| 368 other is OptionsNode && |
363 const UnorderedIterableEquality().equals(options, other.options); | 369 const UnorderedIterableEquality().equals(options, other.options); |
364 | 370 |
365 int get hashCode => const UnorderedIterableEquality().hash(options); | 371 int get hashCode => const UnorderedIterableEquality().hash(options); |
366 | 372 |
367 String toString() => '{${options.join(',')}}'; | 373 String toString() => '{${options.join(',')}}'; |
368 } | 374 } |
369 | 375 |
370 /// A node that matches a literal string. | 376 /// A node that matches a literal string. |
371 class LiteralNode extends AstNode { | 377 class LiteralNode extends AstNode { |
372 /// The string to match. | 378 /// The string to match. |
373 final String text; | 379 final String text; |
374 | 380 |
375 /// The path context for the glob. | 381 /// The path context for the glob. |
376 /// | 382 /// |
377 /// This is used to determine whether this could match an absolute path. | 383 /// This is used to determine whether this could match an absolute path. |
378 final p.Context _context; | 384 final p.Context _context; |
379 | 385 |
380 bool get canMatchAbsolute { | 386 bool get canMatchAbsolute { |
381 var nativeText = _context.style == p.Style.windows ? | 387 var nativeText = |
382 text.replaceAll('/', '\\') : text; | 388 _context.style == p.Style.windows ? text.replaceAll('/', '\\') : text; |
383 return _context.isAbsolute(nativeText); | 389 return _context.isAbsolute(nativeText); |
384 } | 390 } |
385 | 391 |
386 bool get canMatchRelative => !canMatchAbsolute; | 392 bool get canMatchRelative => !canMatchAbsolute; |
387 | 393 |
388 LiteralNode(this.text, {p.Context context, bool caseSensitive: true}) | 394 LiteralNode(this.text, {p.Context context, bool caseSensitive: true}) |
389 : _context = context, | 395 : _context = context, |
390 super._(caseSensitive); | 396 super._(caseSensitive); |
391 | 397 |
392 String _toRegExp() => regExpQuote(text); | 398 String _toRegExp() => regExpQuote(text); |
393 | 399 |
394 bool operator==(Object other) => other is LiteralNode && other.text == text; | 400 bool operator ==(Object other) => other is LiteralNode && other.text == text; |
395 | 401 |
396 int get hashCode => text.hashCode; | 402 int get hashCode => text.hashCode; |
397 | 403 |
398 String toString() => text; | 404 String toString() => text; |
399 } | 405 } |
OLD | NEW |