Index: pkg/glob/lib/src/ast.dart |
diff --git a/pkg/glob/lib/src/ast.dart b/pkg/glob/lib/src/ast.dart |
index 5e1da1a72c2fddff8fac2c64c6f3d132d8c0c0f9..d49b7af6a0d4e7f474fb5cc763be89aefa21985d 100644 |
--- a/pkg/glob/lib/src/ast.dart |
+++ b/pkg/glob/lib/src/ast.dart |
@@ -25,6 +25,14 @@ abstract class AstNode { |
/// Either this or [canMatchRelative] or both will be true. |
final bool canMatchRelative = true; |
+ /// Returns a new glob with all the options bubbled to the top level. |
+ /// |
+ /// In particular, this returns a glob AST with two guarantees: |
+ /// |
+ /// 1. There are no [OptionsNode]s other than the one at the top level. |
+ /// 2. It matches the same set of paths as [this]. |
+ OptionsNode expand() => new OptionsNode([new SequenceNode([this])]); |
Bob Nystrom
2014/09/18 22:17:39
How about "flattenOptions"? Using the same name as
nweiz
2014/09/22 23:48:39
Done.
|
+ |
/// Returns whether this glob matches [string]. |
bool matches(String string) { |
if (_regExp == null) _regExp = new RegExp('^${_toRegExp()}\$'); |
@@ -46,6 +54,87 @@ class SequenceNode extends AstNode { |
SequenceNode(Iterable<AstNode> nodes) |
: nodes = nodes.toList(); |
+ OptionsNode expand() { |
+ if (nodes.isEmpty) return new OptionsNode([this]); |
+ |
+ var sequences = nodes.first.expand().options; |
+ for (var node in nodes.skip(1)) { |
+ var nextSequences = node.expand().options; |
+ sequences = sequences.expand((sequence) { |
+ return nextSequences.map((nextSequence) { |
Bob Nystrom
2014/09/18 22:17:39
A loop containing an expand call that's mapping ea
nweiz
2014/09/22 23:48:39
Done.
|
+ var next = nextSequence.nodes.toList(); |
+ var combined = sequence.nodes.toList(); |
+ |
+ // Combine adjacent LiteralNodes. |
+ if (!combined.isEmpty && !next.isEmpty && |
+ combined.last is LiteralNode && next.first is LiteralNode) { |
+ combined[combined.length - 1] = |
+ new LiteralNode(combined.last.text + next.removeAt(0).text); |
+ } |
+ |
+ return new SequenceNode(combined..addAll(next)); |
+ }); |
+ }); |
+ } |
+ return new OptionsNode(sequences.toList()); |
+ } |
+ |
+ /// Splits this glob into components along its path separators. |
Bob Nystrom
2014/09/18 22:17:39
A before/after example would help a lot here.
nweiz
2014/09/22 23:48:39
Done.
|
+ /// |
+ /// This should only be called once the glob has been [expand]ed. [context] is |
Bob Nystrom
2014/09/18 22:17:39
If this is only valid on certain states, maybe it
nweiz
2014/09/22 23:48:39
Rather than doing that (which I think would add a
|
+ /// used to determine what absolute roots look like for this glob. |
+ List<SequenceNode> split(p.Context context) { |
+ // The inner list represents a [SequenceNode] for a single path component; |
+ // the outer list is the list of components. |
+ var sequences = [[]]; |
Bob Nystrom
2014/09/18 22:17:39
I think this code will be easier to read if you ad
nweiz
2014/09/22 23:48:39
Done.
|
+ |
+ for (var node in nodes) { |
+ // This should only be called after [expand]. |
+ assert(node is! OptionsNode); |
+ |
+ if (node is! LiteralNode || !node.text.contains('/')) { |
+ sequences.last.add(node); |
+ continue; |
+ } |
+ |
+ var text = node.text; |
+ if (context.style == p.Style.windows) text.replaceAll("/", "\\"); |
+ var components = context.split(text); |
Bob Nystrom
2014/09/18 22:17:39
"nodeComponents"?
nweiz
2014/09/22 23:48:39
This is referenced a lot more often than the previ
|
+ |
+ // If the first component is absolute, that means it's a separator (on |
+ // Windows some non-separator things are also absolute, but it's invalid |
+ // to have "C:" show up in the middle of a path anyway). |
+ if (context.isAbsolute(components.first)) { |
+ // If this is the first component, it's the root. |
+ if (sequences.length == 1 && sequences.first.isEmpty) { |
+ var root = components.first; |
+ if (context.style == p.Style.windows) { |
+ root = root.replaceAll("\\", "/"); |
Bob Nystrom
2014/09/18 22:17:39
Why switch to forward slashes on Windows?
nweiz
2014/09/22 23:48:39
Above, we switched to backslashes to make [context
|
+ } |
+ sequences.last.add(new LiteralNode(root)); |
+ } |
+ sequences.add([]); |
+ components = components.skip(1); |
+ if (components.isEmpty) continue; |
+ } |
+ |
+ // For each component except the last one, add a separate sequence to |
+ // [sequences] containing only that component. |
+ for (var component in components.take(components.length - 1)) { |
+ sequences.last.add(new LiteralNode(component)); |
+ sequences.add([]); |
+ } |
+ |
+ // For the final component, only end its sequence (by adding a new empty |
+ // sequence) if it ends with a separator. |
+ sequences.last.add(new LiteralNode(components.last)); |
+ if (node.text.endsWith('/')) sequences.add([]); |
+ } |
+ |
+ if (sequences.last.isEmpty) sequences.removeLast(); |
+ return sequences.map((sequence) => new SequenceNode(sequence)).toList(); |
+ } |
+ |
String _toRegExp() => nodes.map((node) => node._toRegExp()).join(); |
String toString() => nodes.join(); |
@@ -119,6 +208,20 @@ class RangeNode extends AstNode { |
RangeNode(Iterable<Range> ranges, {this.negated}) |
: ranges = ranges.toSet(); |
+ OptionsNode expand() { |
+ if (negated || ranges.any((range) => !range.isSingleton)) { |
+ return super.expand(); |
+ } |
+ |
+ // If a range explicitly lists a set of characters, return each character as |
+ // a separate expansion. |
+ return new OptionsNode(ranges.map((range) { |
+ return new SequenceNode([ |
+ new LiteralNode(new String.fromCharCodes([range.min])) |
+ ]); |
+ })); |
+ } |
+ |
String _toRegExp() { |
var buffer = new StringBuffer(); |
@@ -172,6 +275,9 @@ class OptionsNode extends AstNode { |
OptionsNode(Iterable<SequenceNode> options) |
: options = options.toList(); |
+ OptionsNode expand() => |
+ new OptionsNode(options.expand((option) => option.expand().options)); |
+ |
String _toRegExp() => |
'(?:${options.map((option) => option._toRegExp()).join("|")})'; |
@@ -196,7 +302,7 @@ class LiteralNode extends AstNode { |
bool get canMatchRelative => !canMatchAbsolute; |
- LiteralNode(this.text, this._context); |
+ LiteralNode(this.text, [this._context]); |
String _toRegExp() => regExpQuote(text); |