| Index: glob/lib/src/list_tree.dart
|
| diff --git a/glob/lib/src/list_tree.dart b/glob/lib/src/list_tree.dart
|
| deleted file mode 100644
|
| index 3667d63c1abbd2a1a9492af9601203717604f736..0000000000000000000000000000000000000000
|
| --- a/glob/lib/src/list_tree.dart
|
| +++ /dev/null
|
| @@ -1,420 +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.list_tree;
|
| -
|
| -import 'dart:io';
|
| -import 'dart:async';
|
| -
|
| -import 'package:path/path.dart' as p;
|
| -
|
| -import 'ast.dart';
|
| -import 'stream_pool.dart';
|
| -import 'utils.dart';
|
| -
|
| -/// The errno for a file or directory not existing on Mac and Linux.
|
| -const _ENOENT = 2;
|
| -
|
| -/// Another errno we see on Windows when trying to list a non-existent
|
| -/// directory.
|
| -const _ENOENT_WIN = 3;
|
| -
|
| -/// A structure built from a glob that efficiently lists filesystem entities
|
| -/// that match that glob.
|
| -///
|
| -/// This structure is designed to list the minimal number of physical
|
| -/// directories necessary to find everything that matches the glob. For example,
|
| -/// for the glob `foo/{bar,baz}/*`, there's no need to list the working
|
| -/// directory or even `foo/`; only `foo/bar` and `foo/baz` should be listed.
|
| -///
|
| -/// This works by creating a tree of [_ListTreeNode]s, each of which corresponds
|
| -/// to a single of directory nesting in the source glob. Each node has child
|
| -/// nodes associated with globs ([_ListTreeNode.children]), as well as its own
|
| -/// glob ([_ListTreeNode._validator]) that indicates which entities within that
|
| -/// node's directory should be returned.
|
| -///
|
| -/// For example, the glob `foo/{*.dart,b*/*.txt}` creates the following tree:
|
| -///
|
| -/// .
|
| -/// '-- "foo" (validator: "*.dart")
|
| -/// '-- "b*" (validator: "*.txt"
|
| -///
|
| -/// If a node doesn't have a validator, we know we don't have to list it
|
| -/// explicitly.
|
| -///
|
| -/// Nodes can also be marked as "recursive", which means they need to be listed
|
| -/// recursively (usually to support `**`). In this case, they will have no
|
| -/// children; instead, their validator will just encompass the globs that would
|
| -/// otherwise be in their children. For example, the glob
|
| -/// `foo/{**.dart,bar/*.txt}` creates a recursive node for `foo` with the
|
| -/// validator `**.dart,bar/*.txt`.
|
| -///
|
| -/// If the glob contains multiple filesystem roots (e.g. `{C:/,D:/}*.dart`),
|
| -/// each root will have its own tree of nodes. Relative globs use `.` as their
|
| -/// root instead.
|
| -class ListTree {
|
| - /// A map from filesystem roots to the list tree for those roots.
|
| - ///
|
| - /// A relative glob will use `.` as its root.
|
| - final _trees = new Map<String, _ListTreeNode>();
|
| -
|
| - /// Whether paths listed might overlap.
|
| - ///
|
| - /// If they do, we need to filter out overlapping paths.
|
| - bool _canOverlap;
|
| -
|
| - ListTree(AstNode glob) {
|
| - // The first step in constructing a tree from the glob is to simplify the
|
| - // problem by eliminating options. [glob.flattenOptions] bubbles all options
|
| - // (and certain ranges) up to the top level of the glob so we can deal with
|
| - // them one at a time.
|
| - var options = glob.flattenOptions();
|
| -
|
| - for (var option in options.options) {
|
| - // Since each option doesn't include its own options, we can safely split
|
| - // it into path components.
|
| - var components = option.split(p.context);
|
| - var firstNode = components.first.nodes.first;
|
| - var root = '.';
|
| -
|
| - // Determine the root for this option, if it's absolute. If it's not, the
|
| - // root's just ".".
|
| - if (firstNode is LiteralNode) {
|
| - var text = firstNode.text;
|
| - if (Platform.isWindows) text.replaceAll("/", "\\");
|
| - if (p.isAbsolute(text)) {
|
| - // If the path is absolute, the root should be the only thing in the
|
| - // first component.
|
| - assert(components.first.nodes.length == 1);
|
| - root = firstNode.text;
|
| - components.removeAt(0);
|
| - }
|
| - }
|
| -
|
| - _addGlob(root, components);
|
| - }
|
| -
|
| - _canOverlap = _computeCanOverlap();
|
| - }
|
| -
|
| - /// Add the glob represented by [components] to the tree under [root].
|
| - void _addGlob(String root, List<AstNode> components) {
|
| - // The first [parent] represents the root directory itself. It may be null
|
| - // here if this is the first option with this particular [root]. If so,
|
| - // we'll create it below.
|
| - //
|
| - // As we iterate through [components], [parent] will be set to
|
| - // progressively more nested nodes.
|
| - var parent = _trees[root];
|
| - for (var i = 0; i < components.length; i++) {
|
| - var component = components[i];
|
| - var recursive = component.nodes.any((node) => node is DoubleStarNode);
|
| - var complete = i == components.length - 1;
|
| -
|
| - // If the parent node for this level of nesting already exists, the new
|
| - // option will be added to it as additional validator options and/or
|
| - // additional children.
|
| - //
|
| - // If the parent doesn't exist, we'll create it in one of the else
|
| - // clauses below.
|
| - if (parent != null) {
|
| - if (parent.isRecursive || recursive) {
|
| - // If [component] is recursive, mark [parent] as recursive. This
|
| - // will cause all of its children to be folded into its validator.
|
| - // If [parent] was already recursive, this is a no-op.
|
| - parent.makeRecursive();
|
| -
|
| - // Add [component] and everything nested beneath it as an option to
|
| - // [parent]. Since [parent] is recursive, it will recursively list
|
| - // everything beneath it and filter them with one big glob.
|
| - parent.addOption(_join(components.sublist(i)));
|
| - return;
|
| - } else if (complete) {
|
| - // If [component] is the last component, add it to [parent]'s
|
| - // validator but not to its children.
|
| - parent.addOption(component);
|
| - } else {
|
| - // On the other hand if there are more components, add [component]
|
| - // to [parent]'s children and not its validator. Since we process
|
| - // each option's components separately, the same component is never
|
| - // both a validator and a child.
|
| - if (!parent.children.containsKey(component)) {
|
| - parent.children[component] = new _ListTreeNode();
|
| - }
|
| - parent = parent.children[component];
|
| - }
|
| - } else if (recursive) {
|
| - _trees[root] = new _ListTreeNode.recursive(
|
| - _join(components.sublist(i)));
|
| - return;
|
| - } else if (complete) {
|
| - _trees[root] = new _ListTreeNode()..addOption(component);
|
| - } else {
|
| - _trees[root] = new _ListTreeNode();
|
| - _trees[root].children[component] = new _ListTreeNode();
|
| - parent = _trees[root].children[component];
|
| - }
|
| - }
|
| - }
|
| -
|
| - /// Computes the value for [_canOverlap].
|
| - bool _computeCanOverlap() {
|
| - // If this can list a relative path and an absolute path, the former may be
|
| - // contained within the latter.
|
| - if (_trees.length > 1 && _trees.containsKey('.')) return true;
|
| -
|
| - // Otherwise, this can only overlap if the tree beneath any given root could
|
| - // overlap internally.
|
| - return _trees.values.any((node) => node.canOverlap);
|
| - }
|
| -
|
| - /// List all entities that match this glob beneath [root].
|
| - Stream<FileSystemEntity> list({String root, bool followLinks: true}) {
|
| - if (root == null) root = '.';
|
| - var pool = new StreamPool();
|
| - for (var rootDir in _trees.keys) {
|
| - var dir = rootDir == '.' ? root : rootDir;
|
| - pool.add(_trees[rootDir].list(dir, followLinks: followLinks));
|
| - }
|
| - pool.closeWhenEmpty();
|
| -
|
| - if (!_canOverlap) return pool.stream;
|
| -
|
| - // TODO(nweiz): Rather than filtering here, avoid double-listing directories
|
| - // in the first place.
|
| - var seen = new Set();
|
| - return pool.stream.where((entity) {
|
| - if (seen.contains(entity.path)) return false;
|
| - seen.add(entity.path);
|
| - return true;
|
| - });
|
| - }
|
| -
|
| - /// Synchronosuly list all entities that match this glob beneath [root].
|
| - List<FileSystemEntity> listSync({String root, bool followLinks: true}) {
|
| - if (root == null) root = '.';
|
| -
|
| - var result = _trees.keys.expand((rootDir) {
|
| - var dir = rootDir == '.' ? root : rootDir;
|
| - return _trees[rootDir].listSync(dir, followLinks: followLinks);
|
| - });
|
| -
|
| - if (!_canOverlap) return result.toList();
|
| -
|
| - // TODO(nweiz): Rather than filtering here, avoid double-listing directories
|
| - // in the first place.
|
| - var seen = new Set();
|
| - return result.where((entity) {
|
| - if (seen.contains(entity.path)) return false;
|
| - seen.add(entity.path);
|
| - return true;
|
| - }).toList();
|
| - }
|
| -}
|
| -
|
| -/// A single node in a [ListTree].
|
| -class _ListTreeNode {
|
| - /// This node's child nodes, by their corresponding globs.
|
| - ///
|
| - /// Each child node will only be listed on directories that match its glob.
|
| - ///
|
| - /// This may be `null`, indicating that this node should be listed
|
| - /// recursively.
|
| - Map<SequenceNode, _ListTreeNode> children;
|
| -
|
| - /// This node's validator.
|
| - ///
|
| - /// This determines which entities will ultimately be emitted when [list] is
|
| - /// called.
|
| - OptionsNode _validator;
|
| -
|
| - /// Whether this node is recursive.
|
| - ///
|
| - /// A recursive node has no children and is listed recursively.
|
| - bool get isRecursive => children == null;
|
| -
|
| - /// Whether this node doesn't itself need to be listed.
|
| - ///
|
| - /// If a node has no validator and all of its children are literal filenames,
|
| - /// there's no need to list its contents. We can just directly traverse into
|
| - /// its children.
|
| - bool get _isIntermediate {
|
| - if (_validator != null) return false;
|
| - return children.keys.every((sequence) =>
|
| - sequence.nodes.length == 1 && sequence.nodes.first is LiteralNode);
|
| - }
|
| -
|
| - /// Returns whether listing this node might return overlapping results.
|
| - bool get canOverlap {
|
| - // A recusive node can never overlap with itself, because it will only ever
|
| - // involve a single call to [Directory.list] that's then filtered with
|
| - // [_validator].
|
| - if (isRecursive) return false;
|
| -
|
| - // If there's more than one child node and at least one of the children is
|
| - // dynamic (that is, matches more than just a literal string), there may be
|
| - // overlap.
|
| - if (children.length > 1 && children.keys.any((sequence) =>
|
| - sequence.nodes.length > 1 || sequence.nodes.single is! LiteralNode)) {
|
| - return true;
|
| - }
|
| -
|
| - return children.values.any((node) => node.canOverlap);
|
| - }
|
| -
|
| - /// Creates a node with no children and no validator.
|
| - _ListTreeNode()
|
| - : children = new Map<SequenceNode, _ListTreeNode>(),
|
| - _validator = null;
|
| -
|
| - /// Creates a recursive node the given [validator].
|
| - _ListTreeNode.recursive(SequenceNode validator)
|
| - : children = null,
|
| - _validator = new OptionsNode([validator]);
|
| -
|
| - /// Transforms this into recursive node, folding all its children into its
|
| - /// validator.
|
| - void makeRecursive() {
|
| - if (isRecursive) return;
|
| - _validator = new OptionsNode(children.keys.map((sequence) {
|
| - var child = children[sequence];
|
| - child.makeRecursive();
|
| - return _join([sequence, child._validator]);
|
| - }));
|
| - children = null;
|
| - }
|
| -
|
| - /// Adds [validator] to this node's existing validator.
|
| - void addOption(SequenceNode validator) {
|
| - if (_validator == null) {
|
| - _validator = new OptionsNode([validator]);
|
| - } else {
|
| - _validator.options.add(validator);
|
| - }
|
| - }
|
| -
|
| - /// Lists all entities within [dir] matching this node or its children.
|
| - ///
|
| - /// This may return duplicate entities. These will be filtered out in
|
| - /// [ListTree.list].
|
| - Stream<FileSystemEntity> list(String dir, {bool followLinks: true}) {
|
| - if (isRecursive) {
|
| - return new Directory(dir).list(recursive: true, followLinks: followLinks)
|
| - .where((entity) => _matches(entity.path.substring(dir.length + 1)));
|
| - }
|
| -
|
| - var resultPool = new StreamPool();
|
| -
|
| - // Don't spawn extra [Directory.list] calls when we already know exactly
|
| - // which subdirectories we're interested in.
|
| - if (_isIntermediate) {
|
| - children.forEach((sequence, child) {
|
| - resultPool.add(child.list(p.join(dir, sequence.nodes.single.text),
|
| - followLinks: followLinks));
|
| - });
|
| - resultPool.closeWhenEmpty();
|
| - return resultPool.stream;
|
| - }
|
| -
|
| - var resultController = new StreamController(sync: true);
|
| - resultPool.add(resultController.stream);
|
| - new Directory(dir).list(followLinks: followLinks).listen((entity) {
|
| - var basename = entity.path.substring(dir.length + 1);
|
| - if (_matches(basename)) resultController.add(entity);
|
| -
|
| - children.forEach((sequence, child) {
|
| - if (entity is! Directory) return;
|
| - if (!sequence.matches(basename)) return;
|
| - var stream = child.list(p.join(dir, basename), followLinks: followLinks)
|
| - .handleError((_) {}, test: (error) {
|
| - // Ignore errors from directories not existing. We do this here so
|
| - // that we only ignore warnings below wild cards. For example, the
|
| - // glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but
|
| - // succeed if "foo/bar/qux/baz" doesn't exist.
|
| - return error is FileSystemException &&
|
| - (error.osError.errorCode == _ENOENT ||
|
| - error.osError.errorCode == _ENOENT_WIN);
|
| - });
|
| - resultPool.add(stream);
|
| - });
|
| - },
|
| - onError: resultController.addError,
|
| - onDone: resultController.close);
|
| -
|
| - resultPool.closeWhenEmpty();
|
| - return resultPool.stream;
|
| - }
|
| -
|
| - /// Synchronously lists all entities within [dir] matching this node or its
|
| - /// children.
|
| - ///
|
| - /// This may return duplicate entities. These will be filtered out in
|
| - /// [ListTree.listSync].
|
| - Iterable<FileSystemEntity> listSync(String dir, {bool followLinks: true}) {
|
| - if (isRecursive) {
|
| - return new Directory(dir)
|
| - .listSync(recursive: true, followLinks: followLinks)
|
| - .where((entity) => _matches(entity.path.substring(dir.length + 1)));
|
| - }
|
| -
|
| - // Don't spawn extra [Directory.listSync] calls when we already know exactly
|
| - // which subdirectories we're interested in.
|
| - if (_isIntermediate) {
|
| - return children.keys.expand((sequence) {
|
| - return children[sequence].listSync(
|
| - p.join(dir, sequence.nodes.single.text), followLinks: followLinks);
|
| - });
|
| - }
|
| -
|
| - return new Directory(dir).listSync(followLinks: followLinks)
|
| - .expand((entity) {
|
| - var entities = [];
|
| - var basename = entity.path.substring(dir.length + 1);
|
| - if (_matches(basename)) entities.add(entity);
|
| - if (entity is! Directory) return entities;
|
| -
|
| - entities.addAll(children.keys
|
| - .where((sequence) => sequence.matches(basename))
|
| - .expand((sequence) {
|
| - try {
|
| - return children[sequence].listSync(
|
| - p.join(dir, basename), followLinks: followLinks).toList();
|
| - } on FileSystemException catch (error) {
|
| - // Ignore errors from directories not existing. We do this here so
|
| - // that we only ignore warnings below wild cards. For example, the
|
| - // glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but
|
| - // succeed if "foo/bar/qux/baz" doesn't exist.
|
| - if (error.osError.errorCode == _ENOENT ||
|
| - error.osError.errorCode == _ENOENT_WIN) {
|
| - return const [];
|
| - } else {
|
| - rethrow;
|
| - }
|
| - }
|
| - }));
|
| -
|
| - return entities;
|
| - });
|
| - }
|
| -
|
| - /// Returns whether the native [path] matches [_validator].
|
| - bool _matches(String path) {
|
| - if (_validator == null) return false;
|
| - return _validator.matches(toPosixPath(p.context, path));
|
| - }
|
| -
|
| - String toString() => "($_validator) $children";
|
| -}
|
| -
|
| -/// Joins each [components] into a new glob where each component is separated by
|
| -/// a path separator.
|
| -SequenceNode _join(Iterable<AstNode> components) {
|
| - var componentsList = components.toList();
|
| - var nodes = [componentsList.removeAt(0)];
|
| - for (var component in componentsList) {
|
| - nodes.add(new LiteralNode('/'));
|
| - nodes.add(component);
|
| - }
|
| - return new SequenceNode(nodes);
|
| -}
|
|
|