Index: pkg/glob/lib/src/ast.dart |
diff --git a/pkg/glob/lib/src/ast.dart b/pkg/glob/lib/src/ast.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5e1da1a72c2fddff8fac2c64c6f3d132d8c0c0f9 |
--- /dev/null |
+++ b/pkg/glob/lib/src/ast.dart |
@@ -0,0 +1,204 @@ |
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library glob.ast; |
+ |
+import 'package:path/path.dart' as p; |
+ |
+import 'utils.dart'; |
+ |
+const _SEPARATOR = 0x2F; // "/" |
+ |
+/// A node in the abstract syntax tree for a glob. |
+abstract class AstNode { |
+ /// The cached regular expression that this AST was compiled into. |
+ RegExp _regExp; |
+ |
+ /// Whether this glob could match an absolute path. |
+ /// |
+ /// Either this or [canMatchRelative] or both will be true. |
+ final bool canMatchAbsolute = false; |
+ |
+ /// Whether this glob could match a relative path. |
+ /// |
+ /// Either this or [canMatchRelative] or both will be true. |
+ final bool canMatchRelative = true; |
+ |
+ /// Returns whether this glob matches [string]. |
+ bool matches(String string) { |
+ if (_regExp == null) _regExp = new RegExp('^${_toRegExp()}\$'); |
+ return _regExp.hasMatch(string); |
+ } |
+ |
+ /// Subclasses should override this to return a regular expression component. |
+ String _toRegExp(); |
+} |
+ |
+/// A sequence of adjacent AST nodes. |
+class SequenceNode extends AstNode { |
+ /// The nodes in the sequence. |
+ final List<AstNode> nodes; |
+ |
+ bool get canMatchAbsolute => nodes.first.canMatchAbsolute; |
+ bool get canMatchRelative => nodes.first.canMatchRelative; |
+ |
+ SequenceNode(Iterable<AstNode> nodes) |
+ : nodes = nodes.toList(); |
+ |
+ String _toRegExp() => nodes.map((node) => node._toRegExp()).join(); |
+ |
+ String toString() => nodes.join(); |
+} |
+ |
+/// A node matching zero or more non-separator characters. |
+class StarNode extends AstNode { |
+ StarNode(); |
+ |
+ String _toRegExp() => '[^/]*'; |
+ |
+ String toString() => '*'; |
+} |
+ |
+/// A node matching zero or more characters that may be separators. |
+class DoubleStarNode extends AstNode { |
+ /// The path context for the glob. |
+ /// |
+ /// This is used to determine what absolute paths look like. |
+ final p.Context _context; |
+ |
+ DoubleStarNode(this._context); |
+ |
+ String _toRegExp() { |
+ // Double star shouldn't match paths with a leading "../", since these paths |
+ // wouldn't be listed with this glob. We only check for "../" at the |
+ // beginning since the paths are normalized before being checked against the |
+ // glob. |
+ var buffer = new StringBuffer()..write(r'(?!^(?:\.\./|'); |
+ |
+ // A double star at the beginning of the glob also shouldn't match absolute |
+ // paths, since those also wouldn't be listed. Which root patterns we look |
+ // for depends on the style of path we're matching. |
+ if (_context.style == p.Style.posix) { |
+ buffer.write(r'/'); |
+ } else if (_context.style == p.Style.windows) { |
+ buffer.write(r'//|[A-Za-z]:/'); |
+ } else { |
+ assert(_context.style == p.Style.url); |
+ buffer.write(r'[a-zA-Z][-+.a-zA-Z\d]*://|/'); |
+ } |
+ |
+ // Use `[^]` rather than `.` so that it matches newlines as well. |
+ buffer.write(r'))[^]*'); |
+ |
+ return buffer.toString(); |
+ } |
+ |
+ String toString() => '**'; |
+} |
+ |
+/// A node matching a single non-separator character. |
+class AnyCharNode extends AstNode { |
+ AnyCharNode(); |
+ |
+ String _toRegExp() => '[^/]'; |
+ |
+ String toString() => '?'; |
+} |
+ |
+/// A node matching a single character in a range of options. |
+class RangeNode extends AstNode { |
+ /// The ranges matched by this node. |
+ /// |
+ /// The ends of the ranges are unicode code points. |
+ final Set<Range> ranges; |
+ |
+ /// Whether this range was negated. |
+ final bool negated; |
+ |
+ RangeNode(Iterable<Range> ranges, {this.negated}) |
+ : ranges = ranges.toSet(); |
+ |
+ String _toRegExp() { |
+ var buffer = new StringBuffer(); |
+ |
+ var containsSeparator = ranges.any((range) => range.contains(_SEPARATOR)); |
+ if (!negated && containsSeparator) { |
+ // Add `(?!/)` because ranges are never allowed to match separators. |
+ buffer.write('(?!/)'); |
+ } |
+ |
+ buffer.write('['); |
+ if (negated) { |
+ buffer.write('^'); |
+ // If the range doesn't itself exclude separators, exclude them ourselves, |
+ // since ranges are never allowed to match them. |
+ if (!containsSeparator) buffer.write('/'); |
+ } |
+ |
+ for (var range in ranges) { |
+ var start = new String.fromCharCodes([range.min]); |
+ buffer.write(regExpQuote(start)); |
+ if (range.isSingleton) continue; |
+ buffer.write('-'); |
+ buffer.write(regExpQuote(new String.fromCharCodes([range.max]))); |
+ } |
+ |
+ buffer.write(']'); |
+ return buffer.toString(); |
+ } |
+ |
+ String toString() { |
+ var buffer = new StringBuffer()..write('['); |
+ for (var range in ranges) { |
+ buffer.writeCharCode(range.min); |
+ if (range.isSingleton) continue; |
+ buffer.write('-'); |
+ buffer.writeCharCode(range.max); |
+ } |
+ buffer.write(']'); |
+ return buffer.toString(); |
+ } |
+} |
+ |
+/// A node that matches one of several options. |
+class OptionsNode extends AstNode { |
+ /// The options to match. |
+ final List<SequenceNode> options; |
+ |
+ bool get canMatchAbsolute => options.any((node) => node.canMatchAbsolute); |
+ bool get canMatchRelative => options.any((node) => node.canMatchRelative); |
+ |
+ OptionsNode(Iterable<SequenceNode> options) |
+ : options = options.toList(); |
+ |
+ String _toRegExp() => |
+ '(?:${options.map((option) => option._toRegExp()).join("|")})'; |
+ |
+ String toString() => '{${options.join(',')}}'; |
+} |
+ |
+/// A node that matches a literal string. |
+class LiteralNode extends AstNode { |
+ /// The string to match. |
+ final String text; |
+ |
+ /// The path context for the glob. |
+ /// |
+ /// This is used to determine whether this could match an absolute path. |
+ final p.Context _context; |
+ |
+ bool get canMatchAbsolute { |
+ var nativeText = _context.style == p.Style.windows ? |
+ text.replaceAll('/', '\\') : text; |
+ return _context.isAbsolute(nativeText); |
+ } |
+ |
+ bool get canMatchRelative => !canMatchAbsolute; |
+ |
+ LiteralNode(this.text, this._context); |
+ |
+ String _toRegExp() => regExpQuote(text); |
+ |
+ String toString() => text; |
+} |