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 { | |
Bob Nystrom
2014/08/27 00:42:04
Instead of actually parsing to an AST and then com
nweiz
2014/08/27 01:36:33
Checking for absolute vs relative globs uses the A
Bob Nystrom
2014/08/27 22:16:44
Sure, but you could bubble that up at parse time a
nweiz
2014/09/02 19:48:15
No; only the top-level AST node caches its RegExp.
Bob Nystrom
2014/09/02 20:23:50
SGTM.
| |
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 [couldBeRelative] or both will be true. | |
21 final bool couldBeAbsolute = false; | |
22 | |
23 /// Whether this glob could match a relative path. | |
24 /// | |
25 /// Either this or [couldBeRelative] or both will be true. | |
26 final bool couldBeRelative = 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 couldBeAbsolute => nodes.first.couldBeAbsolute; | |
44 bool get couldBeRelative => nodes.first.couldBeRelative; | |
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; | |
Bob Nystrom
2014/08/27 00:42:04
Why not AstNode?
nweiz
2014/08/27 01:36:33
Because in practice they're always SequenceNodes a
| |
168 | |
169 bool get couldBeAbsolute => options.any((node) => node.couldBeAbsolute); | |
170 bool get couldBeRelative => options.any((node) => node.couldBeRelative); | |
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 couldBeAbsolute { | |
192 var nativeText = _context.style == p.Style.windows ? | |
193 text.replaceAll('/', '\\') : text; | |
194 return _context.isAbsolute(nativeText); | |
195 } | |
196 | |
197 bool get couldBeRelative => !couldBeAbsolute; | |
198 | |
199 LiteralNode(this.text, this._context); | |
200 | |
201 String _toRegExp() => regExpQuote(text); | |
202 | |
203 String toString() => text; | |
204 } | |
OLD | NEW |