| Index: packages/glob/lib/src/list_tree.dart
|
| diff --git a/packages/glob/lib/src/list_tree.dart b/packages/glob/lib/src/list_tree.dart
|
| index 3cce642253c25b18d4593fcff67807ffd67f6be2..f51e4bb58018f7dd06f2f8df8dacaa6ee255b842 100644
|
| --- a/packages/glob/lib/src/list_tree.dart
|
| +++ b/packages/glob/lib/src/list_tree.dart
|
| @@ -2,8 +2,8 @@
|
| // 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.
|
|
|
| -import 'dart:io';
|
| import 'dart:async';
|
| +import 'dart:io';
|
|
|
| import 'package:async/async.dart';
|
| import 'package:path/path.dart' as p;
|
| @@ -143,8 +143,8 @@ class ListTree {
|
| parent = parent.children[component];
|
| }
|
| } else if (recursive) {
|
| - _trees[root] = new _ListTreeNode.recursive(
|
| - _join(components.sublist(i)));
|
| + _trees[root] =
|
| + new _ListTreeNode.recursive(_join(components.sublist(i)));
|
| return;
|
| } else if (complete) {
|
| _trees[root] = new _ListTreeNode()..addOption(component);
|
| @@ -193,8 +193,7 @@ class ListTree {
|
| List<FileSystemEntity> listSync({String root, bool followLinks: true}) {
|
| if (root == null) root = '.';
|
|
|
| - // TODO(nweiz): Remove the explicit annotation when sdk#26139 is fixed.
|
| - var result = _trees.keys.expand/*<FileSystemEntity>*/((rootDir) {
|
| + var result = _trees.keys.expand((rootDir) {
|
| var dir = rootDir == '.' ? root : rootDir;
|
| return _trees[rootDir].listSync(dir, followLinks: followLinks);
|
| });
|
| @@ -247,7 +246,6 @@ class _ListTreeNode {
|
| /// its children.
|
| bool get _isIntermediate {
|
| if (_validator != null) return false;
|
| - if (!_caseSensitive) return false;
|
| return children.keys.every((sequence) =>
|
| sequence.nodes.length == 1 && sequence.nodes.first is LiteralNode);
|
| }
|
| @@ -301,8 +299,8 @@ class _ListTreeNode {
|
| /// Adds [validator] to this node's existing validator.
|
| void addOption(SequenceNode validator) {
|
| if (_validator == null) {
|
| - _validator = new OptionsNode([validator],
|
| - caseSensitive: validator.caseSensitive);
|
| + _validator =
|
| + new OptionsNode([validator], caseSensitive: validator.caseSensitive);
|
| } else {
|
| _validator.options.add(validator);
|
| }
|
| @@ -314,15 +312,15 @@ class _ListTreeNode {
|
| /// [ListTree.list].
|
| Stream<FileSystemEntity> list(String dir, {bool followLinks: true}) {
|
| if (isRecursive) {
|
| - return new Directory(dir).list(recursive: true, followLinks: followLinks)
|
| + return new Directory(dir)
|
| + .list(recursive: true, followLinks: followLinks)
|
| .where((entity) => _matches(p.relative(entity.path, from: dir)));
|
| }
|
|
|
| - var resultGroup = new StreamGroup<FileSystemEntity>();
|
| -
|
| // Don't spawn extra [Directory.list] calls when we already know exactly
|
| // which subdirectories we're interested in.
|
| - if (_isIntermediate) {
|
| + if (_isIntermediate && _caseSensitive) {
|
| + var resultGroup = new StreamGroup<FileSystemEntity>();
|
| children.forEach((sequence, child) {
|
| resultGroup.add(child.list(
|
| p.join(dir, (sequence.nodes.single as LiteralNode).text),
|
| @@ -332,35 +330,65 @@ class _ListTreeNode {
|
| return resultGroup.stream;
|
| }
|
|
|
| - var resultController = new StreamController<FileSystemEntity>(sync: true);
|
| - resultGroup.add(resultController.stream);
|
| - new Directory(dir).list(followLinks: followLinks).listen((entity) {
|
| - var basename = p.relative(entity.path, from: dir);
|
| - 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);
|
| - });
|
| - resultGroup.add(stream);
|
| - });
|
| - },
|
| - onError: resultController.addError,
|
| - onDone: () {
|
| - resultController.close();
|
| - resultGroup.close();
|
| + return StreamCompleter.fromFuture(() async {
|
| + var entities =
|
| + await new Directory(dir).list(followLinks: followLinks).toList();
|
| + await _validateIntermediateChildrenAsync(dir, entities);
|
| +
|
| + var resultGroup = new StreamGroup<FileSystemEntity>();
|
| + var resultController = new StreamController<FileSystemEntity>(sync: true);
|
| + resultGroup.add(resultController.stream);
|
| + for (var entity in entities) {
|
| + var basename = p.relative(entity.path, from: dir);
|
| + 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);
|
| + });
|
| + resultGroup.add(stream);
|
| });
|
| + }
|
| + resultController.close();
|
| + resultGroup.close();
|
| + return resultGroup.stream;
|
| + }());
|
| + }
|
| +
|
| + /// If this is a case-insensitive list, validates that all intermediate
|
| + /// children (according to [_isIntermediate]) match at least one entity in
|
| + /// [entities].
|
| + ///
|
| + /// This ensures that listing "foo/bar/*" fails on case-sensitive systems if
|
| + /// "foo/bar" doesn't exist.
|
| + Future _validateIntermediateChildrenAsync(
|
| + String dir, List<FileSystemEntity> entities) async {
|
| + if (_caseSensitive) return;
|
| +
|
| + for (var sequence in children.keys) {
|
| + var child = children[sequence];
|
| + if (!child._isIntermediate) continue;
|
| + if (entities.any(
|
| + (entity) => sequence.matches(p.relative(entity.path, from: dir)))) {
|
| + continue;
|
| + }
|
|
|
| - return resultGroup.stream;
|
| + // We know this will fail, we're just doing it to force dart:io to emit
|
| + // the exception it would if we were listing case-sensitively.
|
| + await child
|
| + .list(p.join(dir, (sequence.nodes.single as LiteralNode).text))
|
| + .toList();
|
| + }
|
| }
|
|
|
| /// Synchronously lists all entities within [dir] matching this node or its
|
| @@ -377,7 +405,7 @@ class _ListTreeNode {
|
|
|
| // Don't spawn extra [Directory.listSync] calls when we already know exactly
|
| // which subdirectories we're interested in.
|
| - if (_isIntermediate) {
|
| + if (_isIntermediate && _caseSensitive) {
|
| return children.keys.expand((sequence) {
|
| return children[sequence].listSync(
|
| p.join(dir, (sequence.nodes.single as LiteralNode).text),
|
| @@ -385,8 +413,10 @@ class _ListTreeNode {
|
| });
|
| }
|
|
|
| - return new Directory(dir).listSync(followLinks: followLinks)
|
| - .expand((entity) {
|
| + var entities = new Directory(dir).listSync(followLinks: followLinks);
|
| + _validateIntermediateChildrenSync(dir, entities);
|
| +
|
| + return entities.expand((entity) {
|
| var entities = <FileSystemEntity>[];
|
| var basename = p.relative(entity.path, from: dir);
|
| if (_matches(basename)) entities.add(entity);
|
| @@ -396,8 +426,9 @@ class _ListTreeNode {
|
| .where((sequence) => sequence.matches(basename))
|
| .expand((sequence) {
|
| try {
|
| - return children[sequence].listSync(
|
| - p.join(dir, basename), followLinks: followLinks).toList();
|
| + 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
|
| @@ -416,6 +447,31 @@ class _ListTreeNode {
|
| });
|
| }
|
|
|
| + /// If this is a case-insensitive list, validates that all intermediate
|
| + /// children (according to [_isIntermediate]) match at least one entity in
|
| + /// [entities].
|
| + ///
|
| + /// This ensures that listing "foo/bar/*" fails on case-sensitive systems if
|
| + /// "foo/bar" doesn't exist.
|
| + void _validateIntermediateChildrenSync(
|
| + String dir, List<FileSystemEntity> entities) {
|
| + if (_caseSensitive) return;
|
| +
|
| + children.forEach((sequence, child) {
|
| + if (!child._isIntermediate) return;
|
| + if (entities.any(
|
| + (entity) => sequence.matches(p.relative(entity.path, from: dir)))) {
|
| + return;
|
| + }
|
| +
|
| + // If there are no [entities] that match [sequence], manually list the
|
| + // directory to force `dart:io` to throw an error. This allows us to
|
| + // ensure that listing "foo/bar/*" fails on case-sensitive systems if
|
| + // "foo/bar" doesn't exist.
|
| + child.listSync(p.join(dir, (sequence.nodes.single as LiteralNode).text));
|
| + });
|
| + }
|
| +
|
| /// Returns whether the native [path] matches [_validator].
|
| bool _matches(String path) {
|
| if (_validator == null) return false;
|
|
|