| Index: glob/lib/src/ast.dart
|
| diff --git a/glob/lib/src/ast.dart b/glob/lib/src/ast.dart
|
| deleted file mode 100644
|
| index 191347734238d536fa50ae04940b5447e7029f75..0000000000000000000000000000000000000000
|
| --- a/glob/lib/src/ast.dart
|
| +++ /dev/null
|
| @@ -1,372 +0,0 @@
|
| -// 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 'package:collection/collection.dart';
|
| -
|
| -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 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].
|
| - ///
|
| - /// For example, given the glob `{foo,bar}/{click/clack}`, this would return
|
| - /// `{foo/click,foo/clack,bar/click,bar/clack}`.
|
| - OptionsNode flattenOptions() => new OptionsNode([new SequenceNode([this])]);
|
| -
|
| - /// 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();
|
| -
|
| - OptionsNode flattenOptions() {
|
| - if (nodes.isEmpty) return new OptionsNode([this]);
|
| -
|
| - var sequences = nodes.first.flattenOptions().options
|
| - .map((sequence) => sequence.nodes);
|
| - for (var node in nodes.skip(1)) {
|
| - // Concatenate all sequences in the next options node ([nextSequences])
|
| - // onto all previous sequences ([sequences]).
|
| - var nextSequences = node.flattenOptions().options;
|
| - sequences = sequences.expand((sequence) {
|
| - return nextSequences.map((nextSequence) {
|
| - return sequence.toList()..addAll(nextSequence.nodes);
|
| - });
|
| - });
|
| - }
|
| -
|
| - return new OptionsNode(sequences.map((sequence) {
|
| - // Combine any adjacent LiteralNodes in [sequence].
|
| - return new SequenceNode(sequence.fold([], (combined, node) {
|
| - if (combined.isEmpty || combined.last is! LiteralNode ||
|
| - node is! LiteralNode) {
|
| - return combined..add(node);
|
| - }
|
| -
|
| - combined[combined.length - 1] =
|
| - new LiteralNode(combined.last.text + node.text);
|
| - return combined;
|
| - }));
|
| - }));
|
| - }
|
| -
|
| - /// Splits this glob into components along its path separators.
|
| - ///
|
| - /// For example, given the glob `foo/*/*.dart`, this would return three globs:
|
| - /// `foo`, `*`, and `*.dart`.
|
| - ///
|
| - /// Path separators within options nodes are not split. For example,
|
| - /// `foo/{bar,baz/bang}/qux` will return three globs: `foo`, `{bar,baz/bang}`,
|
| - /// and `qux`.
|
| - ///
|
| - /// [context] is used to determine what absolute roots look like for this
|
| - /// glob.
|
| - List<SequenceNode> split(p.Context context) {
|
| - var componentsToReturn = [];
|
| - var currentComponent;
|
| -
|
| - addNode(node) {
|
| - if (currentComponent == null) currentComponent = [];
|
| - currentComponent.add(node);
|
| - }
|
| -
|
| - finishComponent() {
|
| - if (currentComponent == null) return;
|
| - componentsToReturn.add(new SequenceNode(currentComponent));
|
| - currentComponent = null;
|
| - }
|
| -
|
| - for (var node in nodes) {
|
| - if (node is! LiteralNode || !node.text.contains('/')) {
|
| - addNode(node);
|
| - continue;
|
| - }
|
| -
|
| - var text = node.text;
|
| - if (context.style == p.Style.windows) text = text.replaceAll("/", "\\");
|
| - var components = context.split(text);
|
| -
|
| - // 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 (componentsToReturn.isEmpty && currentComponent == null) {
|
| - var root = components.first;
|
| - if (context.style == p.Style.windows) {
|
| - // Above, we switched to backslashes to make [context.split] handle
|
| - // roots properly. That means that if there is a root, it'll still
|
| - // have backslashes, where forward slashes are required for globs.
|
| - // So we switch it back here.
|
| - root = root.replaceAll("\\", "/");
|
| - }
|
| - addNode(new LiteralNode(root));
|
| - }
|
| - finishComponent();
|
| - 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)) {
|
| - addNode(new LiteralNode(component));
|
| - finishComponent();
|
| - }
|
| -
|
| - // For the final component, only end its sequence (by adding a new empty
|
| - // sequence) if it ends with a separator.
|
| - addNode(new LiteralNode(components.last));
|
| - if (node.text.endsWith('/')) finishComponent();
|
| - }
|
| -
|
| - finishComponent();
|
| - return componentsToReturn;
|
| - }
|
| -
|
| - String _toRegExp() => nodes.map((node) => node._toRegExp()).join();
|
| -
|
| - bool operator==(Object other) => other is SequenceNode &&
|
| - const IterableEquality().equals(nodes, other.nodes);
|
| -
|
| - int get hashCode => const IterableEquality().hash(nodes);
|
| -
|
| - String toString() => nodes.join();
|
| -}
|
| -
|
| -/// A node matching zero or more non-separator characters.
|
| -class StarNode extends AstNode {
|
| - StarNode();
|
| -
|
| - String _toRegExp() => '[^/]*';
|
| -
|
| - bool operator==(Object other) => other is StarNode;
|
| -
|
| - int get hashCode => 0;
|
| -
|
| - 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();
|
| - }
|
| -
|
| - bool operator==(Object other) => other is DoubleStarNode;
|
| -
|
| - int get hashCode => 1;
|
| -
|
| - String toString() => '**';
|
| -}
|
| -
|
| -/// A node matching a single non-separator character.
|
| -class AnyCharNode extends AstNode {
|
| - AnyCharNode();
|
| -
|
| - String _toRegExp() => '[^/]';
|
| -
|
| - bool operator==(Object other) => other is AnyCharNode;
|
| -
|
| - int get hashCode => 2;
|
| -
|
| - 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();
|
| -
|
| - OptionsNode flattenOptions() {
|
| - if (negated || ranges.any((range) => !range.isSingleton)) {
|
| - return super.flattenOptions();
|
| - }
|
| -
|
| - // 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();
|
| -
|
| - 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();
|
| - }
|
| -
|
| - bool operator==(Object other) {
|
| - if (other is! RangeNode) return false;
|
| - if ((other as RangeNode).negated != negated) return false;
|
| - return const SetEquality().equals(ranges, (other as RangeNode).ranges);
|
| - }
|
| -
|
| - int get hashCode => (negated ? 1 : 3) * const SetEquality().hash(ranges);
|
| -
|
| - 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();
|
| -
|
| - OptionsNode flattenOptions() => new OptionsNode(
|
| - options.expand((option) => option.flattenOptions().options));
|
| -
|
| - String _toRegExp() =>
|
| - '(?:${options.map((option) => option._toRegExp()).join("|")})';
|
| -
|
| - bool operator==(Object other) => other is OptionsNode &&
|
| - const UnorderedIterableEquality().equals(options, other.options);
|
| -
|
| - int get hashCode => const UnorderedIterableEquality().hash(options);
|
| -
|
| - 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);
|
| -
|
| - bool operator==(Object other) => other is LiteralNode && other.text == text;
|
| -
|
| - int get hashCode => text.hashCode;
|
| -
|
| - String toString() => text;
|
| -}
|
|
|