OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library glob.ast; |
| 6 |
| 7 import 'package:path/path.dart' as p; |
| 8 |
| 9 import 'utils.dart'; |
| 10 |
| 11 const _SEPARATOR = 0x2F; // "/" |
| 12 |
| 13 /// A node in the abstract syntax tree for a glob. |
| 14 abstract class AstNode { |
| 15 /// The cached regular expression that this AST was compiled into. |
| 16 RegExp _regExp; |
| 17 |
| 18 /// Whether this glob could match an absolute path. |
| 19 /// |
| 20 /// Either this or [canMatchRelative] or both will be true. |
| 21 final bool canMatchAbsolute = false; |
| 22 |
| 23 /// Whether this glob could match a relative path. |
| 24 /// |
| 25 /// Either this or [canMatchRelative] or both will be true. |
| 26 final bool canMatchRelative = true; |
| 27 |
| 28 /// Returns whether this glob matches [string]. |
| 29 bool matches(String string) { |
| 30 if (_regExp == null) _regExp = new RegExp('^${_toRegExp()}\$'); |
| 31 return _regExp.hasMatch(string); |
| 32 } |
| 33 |
| 34 /// Subclasses should override this to return a regular expression component. |
| 35 String _toRegExp(); |
| 36 } |
| 37 |
| 38 /// A sequence of adjacent AST nodes. |
| 39 class SequenceNode extends AstNode { |
| 40 /// The nodes in the sequence. |
| 41 final List<AstNode> nodes; |
| 42 |
| 43 bool get canMatchAbsolute => nodes.first.canMatchAbsolute; |
| 44 bool get canMatchRelative => nodes.first.canMatchRelative; |
| 45 |
| 46 SequenceNode(Iterable<AstNode> nodes) |
| 47 : nodes = nodes.toList(); |
| 48 |
| 49 String _toRegExp() => nodes.map((node) => node._toRegExp()).join(); |
| 50 |
| 51 String toString() => nodes.join(); |
| 52 } |
| 53 |
| 54 /// A node matching zero or more non-separator characters. |
| 55 class StarNode extends AstNode { |
| 56 StarNode(); |
| 57 |
| 58 String _toRegExp() => '[^/]*'; |
| 59 |
| 60 String toString() => '*'; |
| 61 } |
| 62 |
| 63 /// A node matching zero or more characters that may be separators. |
| 64 class DoubleStarNode extends AstNode { |
| 65 /// The path context for the glob. |
| 66 /// |
| 67 /// This is used to determine what absolute paths look like. |
| 68 final p.Context _context; |
| 69 |
| 70 DoubleStarNode(this._context); |
| 71 |
| 72 String _toRegExp() { |
| 73 // Double star shouldn't match paths with a leading "../", since these paths |
| 74 // wouldn't be listed with this glob. We only check for "../" at the |
| 75 // beginning since the paths are normalized before being checked against the |
| 76 // glob. |
| 77 var buffer = new StringBuffer()..write(r'(?!^(?:\.\./|'); |
| 78 |
| 79 // A double star at the beginning of the glob also shouldn't match absolute |
| 80 // paths, since those also wouldn't be listed. Which root patterns we look |
| 81 // for depends on the style of path we're matching. |
| 82 if (_context.style == p.Style.posix) { |
| 83 buffer.write(r'/'); |
| 84 } else if (_context.style == p.Style.windows) { |
| 85 buffer.write(r'//|[A-Za-z]:/'); |
| 86 } else { |
| 87 assert(_context.style == p.Style.url); |
| 88 buffer.write(r'[a-zA-Z][-+.a-zA-Z\d]*://|/'); |
| 89 } |
| 90 |
| 91 // Use `[^]` rather than `.` so that it matches newlines as well. |
| 92 buffer.write(r'))[^]*'); |
| 93 |
| 94 return buffer.toString(); |
| 95 } |
| 96 |
| 97 String toString() => '**'; |
| 98 } |
| 99 |
| 100 /// A node matching a single non-separator character. |
| 101 class AnyCharNode extends AstNode { |
| 102 AnyCharNode(); |
| 103 |
| 104 String _toRegExp() => '[^/]'; |
| 105 |
| 106 String toString() => '?'; |
| 107 } |
| 108 |
| 109 /// A node matching a single character in a range of options. |
| 110 class RangeNode extends AstNode { |
| 111 /// The ranges matched by this node. |
| 112 /// |
| 113 /// The ends of the ranges are unicode code points. |
| 114 final Set<Range> ranges; |
| 115 |
| 116 /// Whether this range was negated. |
| 117 final bool negated; |
| 118 |
| 119 RangeNode(Iterable<Range> ranges, {this.negated}) |
| 120 : ranges = ranges.toSet(); |
| 121 |
| 122 String _toRegExp() { |
| 123 var buffer = new StringBuffer(); |
| 124 |
| 125 var containsSeparator = ranges.any((range) => range.contains(_SEPARATOR)); |
| 126 if (!negated && containsSeparator) { |
| 127 // Add `(?!/)` because ranges are never allowed to match separators. |
| 128 buffer.write('(?!/)'); |
| 129 } |
| 130 |
| 131 buffer.write('['); |
| 132 if (negated) { |
| 133 buffer.write('^'); |
| 134 // If the range doesn't itself exclude separators, exclude them ourselves, |
| 135 // since ranges are never allowed to match them. |
| 136 if (!containsSeparator) buffer.write('/'); |
| 137 } |
| 138 |
| 139 for (var range in ranges) { |
| 140 var start = new String.fromCharCodes([range.min]); |
| 141 buffer.write(regExpQuote(start)); |
| 142 if (range.isSingleton) continue; |
| 143 buffer.write('-'); |
| 144 buffer.write(regExpQuote(new String.fromCharCodes([range.max]))); |
| 145 } |
| 146 |
| 147 buffer.write(']'); |
| 148 return buffer.toString(); |
| 149 } |
| 150 |
| 151 String toString() { |
| 152 var buffer = new StringBuffer()..write('['); |
| 153 for (var range in ranges) { |
| 154 buffer.writeCharCode(range.min); |
| 155 if (range.isSingleton) continue; |
| 156 buffer.write('-'); |
| 157 buffer.writeCharCode(range.max); |
| 158 } |
| 159 buffer.write(']'); |
| 160 return buffer.toString(); |
| 161 } |
| 162 } |
| 163 |
| 164 /// A node that matches one of several options. |
| 165 class OptionsNode extends AstNode { |
| 166 /// The options to match. |
| 167 final List<SequenceNode> options; |
| 168 |
| 169 bool get canMatchAbsolute => options.any((node) => node.canMatchAbsolute); |
| 170 bool get canMatchRelative => options.any((node) => node.canMatchRelative); |
| 171 |
| 172 OptionsNode(Iterable<SequenceNode> options) |
| 173 : options = options.toList(); |
| 174 |
| 175 String _toRegExp() => |
| 176 '(?:${options.map((option) => option._toRegExp()).join("|")})'; |
| 177 |
| 178 String toString() => '{${options.join(',')}}'; |
| 179 } |
| 180 |
| 181 /// A node that matches a literal string. |
| 182 class LiteralNode extends AstNode { |
| 183 /// The string to match. |
| 184 final String text; |
| 185 |
| 186 /// The path context for the glob. |
| 187 /// |
| 188 /// This is used to determine whether this could match an absolute path. |
| 189 final p.Context _context; |
| 190 |
| 191 bool get canMatchAbsolute { |
| 192 var nativeText = _context.style == p.Style.windows ? |
| 193 text.replaceAll('/', '\\') : text; |
| 194 return _context.isAbsolute(nativeText); |
| 195 } |
| 196 |
| 197 bool get canMatchRelative => !canMatchAbsolute; |
| 198 |
| 199 LiteralNode(this.text, this._context); |
| 200 |
| 201 String _toRegExp() => regExpQuote(text); |
| 202 |
| 203 String toString() => text; |
| 204 } |
OLD | NEW |