| 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 |