Merge branch 'v1_x'
diff --git a/pkgs/glob/.github/workflows/test-package.yml b/pkgs/glob/.github/workflows/test-package.yml
new file mode 100644
index 0000000..df0041d
--- /dev/null
+++ b/pkgs/glob/.github/workflows/test-package.yml
@@ -0,0 +1,67 @@
+name: Dart CI
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@v2
+ - uses: dart-lang/setup-dart@v1.0
+ with:
+ channel: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [2.15.0, dev]
+ steps:
+ - uses: actions/checkout@v2
+ - uses: dart-lang/setup-dart@v1.0
+ with:
+ channel: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Node tests
+ run: dart test --platform node
+ if: always() && steps.install.outcome == 'success'
diff --git a/pkgs/glob/.test_config b/pkgs/glob/.test_config
deleted file mode 100644
index 531426a..0000000
--- a/pkgs/glob/.test_config
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "test_package": {
- "platforms": ["vm"]
- }
-}
\ No newline at end of file
diff --git a/pkgs/glob/.travis.yml b/pkgs/glob/.travis.yml
deleted file mode 100644
index 9adf49d..0000000
--- a/pkgs/glob/.travis.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-language: dart
-
-dart:
- - dev
- - 2.2.0
-
-# See https://docs.travis-ci.com/user/languages/dart/ for details.
-dart_task:
- - test: --platform vm,chrome
- - dartanalyzer: --fatal-warnings --fatal-infos .
-
-jobs:
- include:
- - dart: 2.2.0
- dart_task:
- test: -p node
- - dart: dev
- dart_task:
- test: -p node
- - dart: dev
- dart_task: dartfmt
- allow_failures:
- # The node package needs to be updated for breaking changes in Dart 2.8
- - dart: dev
- dart_task:
- test: -p node
-
-# Only building master means that we don't run two builds for each pull request.
-branches:
- only: [master]
-
-cache:
- directories:
- - $HOME/.pub-cache
diff --git a/pkgs/glob/CHANGELOG.md b/pkgs/glob/CHANGELOG.md
index 98ab5c2..391fe8b 100644
--- a/pkgs/glob/CHANGELOG.md
+++ b/pkgs/glob/CHANGELOG.md
@@ -1,3 +1,38 @@
+## 2.1.1-dev
+
+* Bump dependencies
+
+## 2.1.0
+
+* Return empty results instead of throwing when trying to list a path that does
+ not exist.
+
+## 2.0.2
+
+* Drop package:pedantic dependency, use package:lints instead.
+* Update SDK lower bound to `2.15.0`
+
+## 2.0.1
+
+* Update example in README for new import.
+
+## 2.0.0
+
+* Stable null safety release.
+
+## 2.0.0-nullsafety.0
+
+* Migrate to null_safety
+
+### Breaking Change
+
+The `list*` apis on `Glob` have been renamed to `listFileSystem*` and they now
+require a `FileSystem` object from `package:file`.
+
+There is a new convenience import, `package:glob/list_local_fs.dart` which
+provides the old methods as extensions, and automatically passes a
+`LocalFileSystem`.
+
## 1.2.1
* Add an empty list_local_fs.dart to ease upgrade from 1x to 2x
diff --git a/pkgs/glob/LICENSE b/pkgs/glob/LICENSE
index 5c60afe..000cd7b 100644
--- a/pkgs/glob/LICENSE
+++ b/pkgs/glob/LICENSE
@@ -1,4 +1,5 @@
-Copyright 2014, the Dart project authors. All rights reserved.
+Copyright 2014, the Dart project authors.
+
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
@@ -9,7 +10,7 @@
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
- * Neither the name of Google Inc. nor the names of its
+ * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
diff --git a/pkgs/glob/README.md b/pkgs/glob/README.md
index 300e73e..75f2b00 100644
--- a/pkgs/glob/README.md
+++ b/pkgs/glob/README.md
@@ -6,7 +6,7 @@
## Usage
-To construct a glob, just use `new Glob()`. As with `RegExp`s, it's a good idea
+To construct a glob, just use `Glob()`. As with `RegExp`s, it's a good idea
to keep around a glob if you'll be using it more than once so that it doesn't
have to be compiled over and over. You can check whether a path matches the glob
using `Glob.matches()`:
@@ -14,7 +14,7 @@
```dart
import 'package:glob/glob.dart';
-final dartFile = new Glob("**.dart");
+final dartFile = Glob("**.dart");
// Print all command-line arguments that are Dart files.
void main(List<String> arguments) {
@@ -29,8 +29,9 @@
```dart
import 'package:glob/glob.dart';
+import 'package:glob/list_local_fs.dart';
-final dartFile = new Glob("**.dart");
+final dartFile = Glob("**.dart");
// Recursively list all Dart files in the current directory.
void main(List<String> arguments) {
diff --git a/pkgs/glob/analysis_options.yaml b/pkgs/glob/analysis_options.yaml
index aefc435..fe4989b 100644
--- a/pkgs/glob/analysis_options.yaml
+++ b/pkgs/glob/analysis_options.yaml
@@ -1,4 +1,4 @@
-include: package:pedantic/analysis_options.yaml
+include: package:lints/recommended.yaml
analyzer:
strong-mode:
implicit-casts: false
@@ -13,6 +13,7 @@
- cancel_subscriptions
- constant_identifier_names
- control_flow_in_finally
+ - depend_on_referenced_packages
- directives_ordering
- empty_catches
- empty_constructor_bodies
diff --git a/pkgs/glob/lib/glob.dart b/pkgs/glob/lib/glob.dart
index 75bedd6..a7b92f6 100644
--- a/pkgs/glob/lib/glob.dart
+++ b/pkgs/glob/lib/glob.dart
@@ -2,10 +2,11 @@
// 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 'package:file/file.dart';
+import 'package:file/memory.dart';
import 'package:path/path.dart' as p;
import 'src/ast.dart';
-import 'src/io.dart';
import 'src/list_tree.dart';
import 'src/parser.dart';
import 'src/utils.dart';
@@ -17,7 +18,7 @@
///
/// A glob matches an entire string as a path. Although the glob pattern uses
/// POSIX syntax, it can match against POSIX, Windows, or URL paths. The format
-/// it expects paths to use is based on the `context` parameter to [new Glob];
+/// it expects paths to use is based on the `context` parameter to [Glob.new];
/// it defaults to the current system's syntax.
///
/// Paths are normalized before being matched against a glob, so for example the
@@ -47,25 +48,34 @@
/// The parsed AST of the glob.
final AstNode _ast;
- ListTree _listTree;
+ /// The underlying object used to implement [list] and [listSync].
+ ///
+ /// This should not be read directly outside of [_listTreeForFileSystem].
+ ListTree? _listTree;
+
+ /// Keeps track of the previous file system used. If this changes then the
+ /// [_listTree] must be invalidated.
+ ///
+ /// This is handled inside of [_listTreeForFileSystem].
+ FileSystem? _previousFileSystem;
/// Whether [context]'s current directory is absolute.
bool get _contextIsAbsolute =>
_contextIsAbsoluteCache ??= context.isAbsolute(context.current);
- bool _contextIsAbsoluteCache;
+ bool? _contextIsAbsoluteCache;
/// Whether [pattern] could match absolute paths.
bool get _patternCanMatchAbsolute =>
_patternCanMatchAbsoluteCache ??= _ast.canMatchAbsolute;
- bool _patternCanMatchAbsoluteCache;
+ bool? _patternCanMatchAbsoluteCache;
/// Whether [pattern] could match relative paths.
bool get _patternCanMatchRelative =>
_patternCanMatchRelativeCache ??= _ast.canMatchRelative;
- bool _patternCanMatchRelativeCache;
+ bool? _patternCanMatchRelativeCache;
/// Returns [contents] with characters that are meaningful in globs
/// backslash-escaped.
@@ -85,7 +95,7 @@
/// regardless of case. This defaults to `false` when [context] is Windows and
/// `true` otherwise.
factory Glob(String pattern,
- {p.Context context, bool recursive = false, bool caseSensitive}) {
+ {p.Context? context, bool recursive = false, bool? caseSensitive}) {
context ??= p.context;
caseSensitive ??= context.style == p.Style.windows ? false : true;
if (recursive) pattern += '{,/**}';
@@ -96,7 +106,8 @@
Glob._(this.pattern, this.context, this._ast, this.recursive);
- /// Lists all [FileSystemEntity]s beneath [root] that match the glob.
+ /// Lists all [FileSystemEntity]s beneath [root] that match the glob in the
+ /// provided [fileSystem].
///
/// This works much like [Directory.list], but it only lists directories that
/// could contain entities that match the glob. It provides no guarantees
@@ -106,18 +117,19 @@
/// [root] defaults to the current working directory.
///
/// [followLinks] works the same as for [Directory.list].
- Stream<FileSystemEntity> list({String root, bool followLinks = true}) {
+ Stream<FileSystemEntity> listFileSystem(FileSystem fileSystem,
+ {String? root, bool followLinks = true}) {
if (context.style != p.style) {
throw StateError("Can't list glob \"$this\"; it matches "
'${context.style} paths, but this platform uses ${p.style} paths.');
}
- _listTree ??= ListTree(_ast);
- return _listTree.list(root: root, followLinks: followLinks);
+ return _listTreeForFileSystem(fileSystem)
+ .list(root: root, followLinks: followLinks);
}
/// Synchronously lists all [FileSystemEntity]s beneath [root] that match the
- /// glob.
+ /// glob in the provided [fileSystem].
///
/// This works much like [Directory.listSync], but it only lists directories
/// that could contain entities that match the glob. It provides no guarantees
@@ -127,21 +139,22 @@
/// [root] defaults to the current working directory.
///
/// [followLinks] works the same as for [Directory.list].
- List<FileSystemEntity> listSync({String root, bool followLinks = true}) {
+ List<FileSystemEntity> listFileSystemSync(FileSystem fileSystem,
+ {String? root, bool followLinks = true}) {
if (context.style != p.style) {
throw StateError("Can't list glob \"$this\"; it matches "
'${context.style} paths, but this platform uses ${p.style} paths.');
}
- _listTree ??= ListTree(_ast);
- return _listTree.listSync(root: root, followLinks: followLinks);
+ return _listTreeForFileSystem(fileSystem)
+ .listSync(root: root, followLinks: followLinks);
}
/// Returns whether this glob matches [path].
bool matches(String path) => matchAsPrefix(path) != null;
@override
- Match matchAsPrefix(String path, [int start = 0]) {
+ Match? matchAsPrefix(String path, [int start = 0]) {
// Globs are like anchored RegExps in that they only match entire paths, so
// if the match starts anywhere after the first character it can't succeed.
if (start != 0) return null;
@@ -172,4 +185,18 @@
@override
String toString() => pattern;
+
+ /// Handles getting a possibly cached [ListTree] for a [fileSystem].
+ ListTree _listTreeForFileSystem(FileSystem fileSystem) {
+ // Don't use cached trees for in memory file systems to avoid memory leaks.
+ if (fileSystem is MemoryFileSystem) return ListTree(_ast, fileSystem);
+
+ // Throw away our cached `_listTree` if the file system is different.
+ if (fileSystem != _previousFileSystem) {
+ _listTree = null;
+ _previousFileSystem = fileSystem;
+ }
+
+ return _listTree ??= ListTree(_ast, fileSystem);
+ }
}
diff --git a/pkgs/glob/lib/list_local_fs.dart b/pkgs/glob/lib/list_local_fs.dart
index d59e216..946596a 100644
--- a/pkgs/glob/lib/list_local_fs.dart
+++ b/pkgs/glob/lib/list_local_fs.dart
@@ -1,4 +1,23 @@
-/// This file is intentionally empty. It exists to ease transition from
-/// glob 1x to 2x. By having this file here you can add the import to
-/// it that provides backward compatibility in 2x while still on glob 1x.
-library list_local_fs;
+// Copyright (c) 2020, 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.
+
+import 'package:file/file.dart';
+import 'package:file/local.dart';
+import 'package:glob/glob.dart';
+
+/// Platform specific extensions for where `dart:io` exists, which use the
+/// local file system.
+extension ListLocalFileSystem on Glob {
+ /// Convenience method for [Glob.listFileSystem] which uses the local file
+ /// system.
+ Stream<FileSystemEntity> list({String? root, bool followLinks = true}) =>
+ listFileSystem(const LocalFileSystem(),
+ root: root, followLinks: followLinks);
+
+ /// Convenience method for [Glob.listFileSystemSync] which uses the local
+ /// file system.
+ List<FileSystemEntity> listSync({String? root, bool followLinks = true}) =>
+ listFileSystemSync(const LocalFileSystem(),
+ root: root, followLinks: followLinks);
+}
diff --git a/pkgs/glob/lib/src/ast.dart b/pkgs/glob/lib/src/ast.dart
index 1ee0fcf..ac283d5 100644
--- a/pkgs/glob/lib/src/ast.dart
+++ b/pkgs/glob/lib/src/ast.dart
@@ -12,7 +12,7 @@
/// 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;
+ RegExp? _regExp;
/// Whether this node matches case-sensitively or not.
final bool caseSensitive;
@@ -43,10 +43,9 @@
], caseSensitive: caseSensitive);
/// Returns whether this glob matches [string].
- bool matches(String string) {
- _regExp ??= RegExp('^${_toRegExp()}\$', caseSensitive: caseSensitive);
- return _regExp.hasMatch(string);
- }
+ bool matches(String string) =>
+ (_regExp ??= RegExp('^${_toRegExp()}\$', caseSensitive: caseSensitive))
+ .hasMatch(string);
/// Subclasses should override this to return a regular expression component.
String _toRegExp();
@@ -97,9 +96,7 @@
}
combined[combined.length - 1] = LiteralNode(
- // TODO(nweiz): Avoid casting when sdk#25565 is fixed.
- (combined.last as LiteralNode).text +
- (node as LiteralNode).text,
+ (combined.last as LiteralNode).text + node.text,
caseSensitive: caseSensitive);
return combined;
}),
@@ -120,17 +117,16 @@
/// glob.
List<SequenceNode> split(p.Context context) {
var componentsToReturn = <SequenceNode>[];
- List<AstNode> currentComponent;
+ List<AstNode>? currentComponent;
void addNode(AstNode node) {
- currentComponent ??= [];
- currentComponent.add(node);
+ (currentComponent ??= []).add(node);
}
void finishComponent() {
if (currentComponent == null) return;
componentsToReturn
- .add(SequenceNode(currentComponent, caseSensitive: caseSensitive));
+ .add(SequenceNode(currentComponent!, caseSensitive: caseSensitive));
currentComponent = null;
}
@@ -140,14 +136,12 @@
continue;
}
- // TODO(nweiz): Avoid casting when sdk#25565 is fixed.
- var literal = node as LiteralNode;
- if (!literal.text.contains('/')) {
- addNode(literal);
+ if (!node.text.contains('/')) {
+ addNode(node);
continue;
}
- var text = literal.text;
+ var text = node.text;
if (context.style == p.Style.windows) text = text.replaceAll('/', '\\');
Iterable<String> components = context.split(text);
@@ -182,7 +176,7 @@
// For the final component, only end its sequence (by adding a new empty
// sequence) if it ends with a separator.
addNode(LiteralNode(components.last, caseSensitive: caseSensitive));
- if (literal.text.endsWith('/')) finishComponent();
+ if (node.text.endsWith('/')) finishComponent();
}
finishComponent();
@@ -294,7 +288,8 @@
/// Whether this range was negated.
final bool negated;
- RangeNode(Iterable<Range> ranges, {this.negated, bool caseSensitive = true})
+ RangeNode(Iterable<Range> ranges,
+ {required this.negated, bool caseSensitive = true})
: ranges = ranges.toSet(),
super._(caseSensitive);
@@ -411,19 +406,19 @@
/// The path context for the glob.
///
/// This is used to determine whether this could match an absolute path.
- final p.Context _context;
+ final p.Context? _context;
@override
bool get canMatchAbsolute {
var nativeText =
- _context.style == p.Style.windows ? text.replaceAll('/', '\\') : text;
- return _context.isAbsolute(nativeText);
+ _context!.style == p.Style.windows ? text.replaceAll('/', '\\') : text;
+ return _context!.isAbsolute(nativeText);
}
@override
bool get canMatchRelative => !canMatchAbsolute;
- LiteralNode(this.text, {p.Context context, bool caseSensitive = true})
+ LiteralNode(this.text, {p.Context? context, bool caseSensitive = true})
: _context = context,
super._(caseSensitive);
diff --git a/pkgs/glob/lib/src/io.dart b/pkgs/glob/lib/src/io.dart
deleted file mode 100644
index 20e532b..0000000
--- a/pkgs/glob/lib/src/io.dart
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2019, 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.
-
-// These libraries don't expose *exactly* the same API, but they overlap in all
-// the cases we care about.
-export 'io_export.dart'
- // We don't actually support the web - exporting dart:io gives a reasonably
- // clear signal to users about that since it doesn't exist.
- if (dart.library.html) 'io_export.dart'
- if (dart.library.js) 'package:node_io/node_io.dart';
diff --git a/pkgs/glob/lib/src/io_export.dart b/pkgs/glob/lib/src/io_export.dart
deleted file mode 100644
index 7f339cf..0000000
--- a/pkgs/glob/lib/src/io_export.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) 2019, 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.
-
-/// This library exists only to satisfy build_runner, which doesn't allow
-/// sdk libraries to be conditional imported or exported directly.
-library glob.src.io_export;
-
-export 'dart:io';
diff --git a/pkgs/glob/lib/src/list_tree.dart b/pkgs/glob/lib/src/list_tree.dart
index 82f12c5..2a6c020 100644
--- a/pkgs/glob/lib/src/list_tree.dart
+++ b/pkgs/glob/lib/src/list_tree.dart
@@ -5,11 +5,10 @@
import 'dart:async';
import 'package:async/async.dart';
+import 'package:file/file.dart';
import 'package:path/path.dart' as p;
-import 'package:pedantic/pedantic.dart';
import 'ast.dart';
-import 'io.dart';
import 'utils.dart';
/// The errno for a file or directory not existing on Mac and Linux.
@@ -56,19 +55,26 @@
/// A map from filesystem roots to the list tree for those roots.
///
/// A relative glob will use `.` as its root.
- final _trees = <String, _ListTreeNode>{};
+ final Map<String, _ListTreeNode> _trees;
/// Whether paths listed might overlap.
///
/// If they do, we need to filter out overlapping paths.
- bool _canOverlap;
+ final bool _canOverlap;
- ListTree(AstNode glob) {
+ /// The file system to operate on.
+ final FileSystem _fileSystem;
+
+ ListTree._(this._trees, this._fileSystem)
+ : _canOverlap = _computeCanOverlap(_trees);
+
+ factory ListTree(AstNode glob, FileSystem fileSystem) {
// 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();
+ var trees = <String, _ListTreeNode>{};
for (var option in options.options) {
// Since each option doesn't include its own options, we can safely split
@@ -81,7 +87,8 @@
// root's just ".".
if (firstNode is LiteralNode) {
var text = firstNode.text;
- if (Platform.isWindows) text.replaceAll('/', '\\');
+ // Platform agnostic way of checking for Windows without `dart:io`.
+ if (p.context == p.windows) text.replaceAll('/', '\\');
if (p.isAbsolute(text)) {
// If the path is absolute, the root should be the only thing in the
// first component.
@@ -91,21 +98,22 @@
}
}
- _addGlob(root, components);
+ _addGlob(root, components, trees);
}
- _canOverlap = _computeCanOverlap();
+ return ListTree._(trees, fileSystem);
}
/// Add the glob represented by [components] to the tree under [root].
- void _addGlob(String root, List<SequenceNode> components) {
+ static void _addGlob(String root, List<SequenceNode> components,
+ Map<String, _ListTreeNode> trees) {
// 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];
+ 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);
@@ -138,42 +146,46 @@
// 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] = _ListTreeNode();
+ var children = parent.children!;
+ if (!children.containsKey(component)) {
+ children[component] = _ListTreeNode();
}
- parent = parent.children[component];
+ parent = children[component];
}
} else if (recursive) {
- _trees[root] = _ListTreeNode.recursive(_join(components.sublist(i)));
+ trees[root] = _ListTreeNode.recursive(_join(components.sublist(i)));
return;
} else if (complete) {
- _trees[root] = _ListTreeNode()..addOption(component);
+ trees[root] = _ListTreeNode()..addOption(component);
} else {
- _trees[root] = _ListTreeNode();
- _trees[root].children[component] = _ListTreeNode();
- parent = _trees[root].children[component];
+ var rootNode = _ListTreeNode();
+ trees[root] = rootNode;
+ var rootChildren = rootNode.children!;
+ rootChildren[component] = _ListTreeNode();
+ parent = rootChildren[component];
}
}
}
/// Computes the value for [_canOverlap].
- bool _computeCanOverlap() {
+ static bool _computeCanOverlap(Map<String, _ListTreeNode> trees) {
// 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;
+ 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);
+ return trees.values.any((node) => node.canOverlap);
}
/// List all entities that match this glob beneath [root].
- Stream<FileSystemEntity> list({String root, bool followLinks = true}) {
+ Stream<FileSystemEntity> list({String? root, bool followLinks = true}) {
root ??= '.';
var group = StreamGroup<FileSystemEntity>();
for (var rootDir in _trees.keys) {
var dir = rootDir == '.' ? root : rootDir;
- group.add(_trees[rootDir].list(dir, followLinks: followLinks));
+ group.add(
+ _trees[rootDir]!.list(dir, _fileSystem, followLinks: followLinks));
}
group.close();
@@ -186,11 +198,12 @@
}
/// Synchronosuly list all entities that match this glob beneath [root].
- List<FileSystemEntity> listSync({String root, bool followLinks = true}) {
+ List<FileSystemEntity> listSync({String? root, bool followLinks = true}) {
root ??= '.';
var result = _trees.keys.expand((rootDir) {
- var dir = rootDir == '.' ? root : rootDir;
- return _trees[rootDir].listSync(dir, followLinks: followLinks);
+ var dir = rootDir == '.' ? root! : rootDir;
+ return _trees[rootDir]!
+ .listSync(dir, _fileSystem, followLinks: followLinks);
});
if (!_canOverlap) return result.toList();
@@ -210,13 +223,13 @@
///
/// This may be `null`, indicating that this node should be listed
/// recursively.
- Map<SequenceNode, _ListTreeNode> children;
+ Map<SequenceNode, _ListTreeNode>? children;
/// This node's validator.
///
/// This determines which entities will ultimately be emitted when [list] is
/// called.
- OptionsNode _validator;
+ OptionsNode? _validator;
/// Whether this node is recursive.
///
@@ -224,10 +237,9 @@
bool get isRecursive => children == null;
bool get _caseSensitive {
- if (_validator != null) return _validator.caseSensitive;
- if (children == null) return true;
- if (children.isEmpty) return true;
- return children.keys.first.caseSensitive;
+ if (_validator != null) return _validator!.caseSensitive;
+ if (children?.isEmpty != false) return true;
+ return children!.keys.first.caseSensitive;
}
/// Whether this node doesn't itself need to be listed.
@@ -237,7 +249,7 @@
/// its children.
bool get _isIntermediate {
if (_validator != null) return false;
- return children.keys.every((sequence) =>
+ return children!.keys.every((sequence) =>
sequence.nodes.length == 1 && sequence.nodes.first is LiteralNode);
}
@@ -251,17 +263,17 @@
// 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) {
+ if (children!.length > 1) {
// Case-insensitivity means that even literals may match multiple entries.
if (!_caseSensitive) return true;
- if (children.keys.any((sequence) =>
+ if (children!.keys.any((sequence) =>
sequence.nodes.length > 1 || sequence.nodes.single is! LiteralNode)) {
return true;
}
}
- return children.values.any((node) => node.canOverlap);
+ return children!.values.any((node) => node.canOverlap);
}
/// Creates a node with no children and no validator.
@@ -279,12 +291,12 @@
/// validator.
void makeRecursive() {
if (isRecursive) return;
- _validator = OptionsNode(children.keys.map((sequence) {
- var child = children[sequence];
- child.makeRecursive();
- return _join([sequence, child._validator]);
+ var children = this.children!;
+ _validator = OptionsNode(children.entries.map((entry) {
+ entry.value.makeRecursive();
+ return _join([entry.key, entry.value._validator!]);
}), caseSensitive: _caseSensitive);
- children = null;
+ this.children = null;
}
/// Adds [validator] to this node's existing validator.
@@ -293,7 +305,7 @@
_validator =
OptionsNode([validator], caseSensitive: validator.caseSensitive);
} else {
- _validator.options.add(validator);
+ _validator!.options.add(validator);
}
}
@@ -301,10 +313,13 @@
///
/// This may return duplicate entities. These will be filtered out in
/// [ListTree.list].
- Stream<FileSystemEntity> list(String dir, {bool followLinks = true}) {
+ Stream<FileSystemEntity> list(String dir, FileSystem fileSystem,
+ {bool followLinks = true}) {
if (isRecursive) {
- return Directory(dir)
+ return fileSystem
+ .directory(dir)
.list(recursive: true, followLinks: followLinks)
+ .ignoreMissing()
.where((entity) => _matches(p.relative(entity.path, from: dir)));
}
@@ -312,9 +327,10 @@
// which subdirectories we're interested in.
if (_isIntermediate && _caseSensitive) {
var resultGroup = StreamGroup<FileSystemEntity>();
- children.forEach((sequence, child) {
+ children!.forEach((sequence, child) {
resultGroup.add(child.list(
p.join(dir, (sequence.nodes.single as LiteralNode).text),
+ fileSystem,
followLinks: followLinks));
});
resultGroup.close();
@@ -322,9 +338,12 @@
}
return StreamCompleter.fromFuture(() async {
- var entities =
- await Directory(dir).list(followLinks: followLinks).toList();
- await _validateIntermediateChildrenAsync(dir, entities);
+ var entities = await fileSystem
+ .directory(dir)
+ .list(followLinks: followLinks)
+ .ignoreMissing()
+ .toList();
+ await _validateIntermediateChildrenAsync(dir, entities, fileSystem);
var resultGroup = StreamGroup<FileSystemEntity>();
var resultController = StreamController<FileSystemEntity>(sync: true);
@@ -333,20 +352,11 @@
var basename = p.relative(entity.path, from: dir);
if (_matches(basename)) resultController.add(entity);
- children.forEach((sequence, child) {
+ 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 == _enoentWin);
- });
+ var stream = child.list(p.join(dir, basename), fileSystem,
+ followLinks: followLinks);
resultGroup.add(stream);
});
}
@@ -362,12 +372,13 @@
///
/// 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 {
+ Future _validateIntermediateChildrenAsync(String dir,
+ List<FileSystemEntity> entities, FileSystem fileSystem) async {
if (_caseSensitive) return;
- for (var sequence in children.keys) {
- var child = children[sequence];
+ for (var entry in children!.entries) {
+ var child = entry.value;
+ var sequence = entry.key;
if (!child._isIntermediate) continue;
if (entities.any(
(entity) => sequence.matches(p.relative(entity.path, from: dir)))) {
@@ -377,7 +388,8 @@
// 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))
+ .list(p.join(dir, (sequence.nodes.single as LiteralNode).text),
+ fileSystem)
.toList();
}
}
@@ -387,25 +399,41 @@
///
/// This may return duplicate entities. These will be filtered out in
/// [ListTree.listSync].
- Iterable<FileSystemEntity> listSync(String dir, {bool followLinks = true}) {
+ Iterable<FileSystemEntity> listSync(String dir, FileSystem fileSystem,
+ {bool followLinks = true}) {
if (isRecursive) {
- return Directory(dir)
- .listSync(recursive: true, followLinks: followLinks)
- .where((entity) => _matches(p.relative(entity.path, from: dir)));
+ try {
+ return fileSystem
+ .directory(dir)
+ .listSync(recursive: true, followLinks: followLinks)
+ .where((entity) => _matches(p.relative(entity.path, from: dir)));
+ } on FileSystemException catch (error) {
+ if (error.isMissing) return const [];
+ rethrow;
+ }
}
// Don't spawn extra [Directory.listSync] calls when we already know exactly
// which subdirectories we're interested in.
if (_isIntermediate && _caseSensitive) {
- return children.keys.expand((sequence) {
- return children[sequence].listSync(
+ return children!.entries.expand((entry) {
+ var sequence = entry.key;
+ var child = entry.value;
+ return child.listSync(
p.join(dir, (sequence.nodes.single as LiteralNode).text),
+ fileSystem,
followLinks: followLinks);
});
}
- var entities = Directory(dir).listSync(followLinks: followLinks);
- _validateIntermediateChildrenSync(dir, entities);
+ List<FileSystemEntity> entities;
+ try {
+ entities = fileSystem.directory(dir).listSync(followLinks: followLinks);
+ } on FileSystemException catch (error) {
+ if (error.isMissing) return const [];
+ rethrow;
+ }
+ _validateIntermediateChildrenSync(dir, entities, fileSystem);
return entities.expand((entity) {
var entities = <FileSystemEntity>[];
@@ -413,25 +441,13 @@
if (_matches(basename)) entities.add(entity);
if (entity is! Directory) return entities;
- entities.addAll(children.keys
+ 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 == _enoentWin) {
- return const [];
- } else {
- rethrow;
- }
- }
+ return children![sequence]!
+ .listSync(p.join(dir, basename), fileSystem,
+ followLinks: followLinks)
+ .toList();
}));
return entities;
@@ -445,10 +461,10 @@
/// This ensures that listing "foo/bar/*" fails on case-sensitive systems if
/// "foo/bar" doesn't exist.
void _validateIntermediateChildrenSync(
- String dir, List<FileSystemEntity> entities) {
+ String dir, List<FileSystemEntity> entities, FileSystem fileSystem) {
if (_caseSensitive) return;
- children.forEach((sequence, child) {
+ children!.forEach((sequence, child) {
if (!child._isIntermediate) return;
if (entities.any(
(entity) => sequence.matches(p.relative(entity.path, from: dir)))) {
@@ -459,15 +475,14 @@
// 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));
+ child.listSync(
+ p.join(dir, (sequence.nodes.single as LiteralNode).text), fileSystem);
});
}
/// Returns whether the native [path] matches [_validator].
- bool _matches(String path) {
- if (_validator == null) return false;
- return _validator.matches(toPosixPath(p.context, path));
- }
+ bool _matches(String path) =>
+ _validator?.matches(toPosixPath(p.context, path)) ?? false;
@override
String toString() => '($_validator) $children';
@@ -485,3 +500,15 @@
}
return SequenceNode(nodes, caseSensitive: first.caseSensitive);
}
+
+extension on Stream<FileSystemEntity> {
+ Stream<FileSystemEntity> ignoreMissing() => handleError((_) {},
+ test: (error) => error is FileSystemException && error.isMissing);
+}
+
+extension on FileSystemException {
+ bool get isMissing {
+ final errorCode = osError?.errorCode;
+ return errorCode == _enoent || errorCode == _enoentWin;
+ }
+}
diff --git a/pkgs/glob/lib/src/parser.dart b/pkgs/glob/lib/src/parser.dart
index 20ade31..e76f7c5 100644
--- a/pkgs/glob/lib/src/parser.dart
+++ b/pkgs/glob/lib/src/parser.dart
@@ -69,7 +69,7 @@
/// Tries to parse a [StarNode] or a [DoubleStarNode].
///
/// Returns `null` if there's not one to parse.
- AstNode _parseStar() {
+ AstNode? _parseStar() {
if (!_scanner.scan('*')) return null;
return _scanner.scan('*')
? DoubleStarNode(_context, caseSensitive: _caseSensitive)
@@ -79,7 +79,7 @@
/// Tries to parse an [AnyCharNode].
///
/// Returns `null` if there's not one to parse.
- AstNode _parseAnyChar() {
+ AstNode? _parseAnyChar() {
if (!_scanner.scan('?')) return null;
return AnyCharNode(caseSensitive: _caseSensitive);
}
@@ -87,7 +87,7 @@
/// Tries to parse an [RangeNode].
///
/// Returns `null` if there's not one to parse.
- AstNode _parseRange() {
+ AstNode? _parseRange() {
if (!_scanner.scan('[')) return null;
if (_scanner.matches(']')) _scanner.error('unexpected "]".');
var negated = _scanner.scan('!') || _scanner.scan('^');
@@ -134,7 +134,7 @@
/// Tries to parse an [OptionsNode].
///
/// Returns `null` if there's not one to parse.
- AstNode _parseOptions() {
+ AstNode? _parseOptions() {
if (!_scanner.scan('{')) return null;
if (_scanner.matches('}')) _scanner.error('unexpected "}".');
@@ -157,12 +157,12 @@
var regExp = RegExp(inOptions ? r'[^*{[?\\}\],()]*' : r'[^*{[?\\}\]()]*');
_scanner.scan(regExp);
- var buffer = StringBuffer()..write(_scanner.lastMatch[0]);
+ var buffer = StringBuffer()..write(_scanner.lastMatch![0]);
while (_scanner.scan('\\')) {
buffer.writeCharCode(_scanner.readChar());
_scanner.scan(regExp);
- buffer.write(_scanner.lastMatch[0]);
+ buffer.write(_scanner.lastMatch![0]);
}
for (var char in const [']', '(', ')']) {
diff --git a/pkgs/glob/pubspec.yaml b/pkgs/glob/pubspec.yaml
index ef34d02..eae44a6 100644
--- a/pkgs/glob/pubspec.yaml
+++ b/pkgs/glob/pubspec.yaml
@@ -1,20 +1,20 @@
name: glob
-version: 1.2.1
+version: 2.1.0
description: Bash-style filename globbing.
-homepage: https://github.com/dart-lang/glob
+repository: https://github.com/dart-lang/glob
environment:
- sdk: '>=2.2.0 <3.0.0'
+ sdk: '>=2.15.0 <3.0.0'
dependencies:
- async: '>=1.2.0 <3.0.0'
- collection: ^1.1.0
- node_io: ^1.0.0
- path: ^1.3.0
- pedantic: ^1.2.0
- string_scanner: '>=0.1.0 <2.0.0'
+ async: ^2.5.0
+ collection: ^1.15.0
+ file: ^6.1.3
+ path: ^1.8.0
+ string_scanner: ^1.1.0
dev_dependencies:
- test: ^1.6.0
- test_descriptor: ^1.0.0
+ lints: ^1.0.0
+ test: ^1.17.0
+ test_descriptor: ^2.0.0
diff --git a/pkgs/glob/test/glob_test.dart b/pkgs/glob/test/glob_test.dart
index 281a413..85c3fac 100644
--- a/pkgs/glob/test/glob_test.dart
+++ b/pkgs/glob/test/glob_test.dart
@@ -63,7 +63,7 @@
group('GlobMatch', () {
var glob = Glob('foo*');
- var match = glob.matchAsPrefix('foobar');
+ var match = glob.matchAsPrefix('foobar')!;
test('returns the string as input', () {
expect(match.input, equals('foobar'));
diff --git a/pkgs/glob/test/list_test.dart b/pkgs/glob/test/list_test.dart
index 5fac113..5714f25 100644
--- a/pkgs/glob/test/list_test.dart
+++ b/pkgs/glob/test/list_test.dart
@@ -7,6 +7,7 @@
import 'dart:io';
import 'package:glob/glob.dart';
+import 'package:glob/list_local_fs.dart';
import 'package:glob/src/utils.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
@@ -25,15 +26,17 @@
expect(Glob('*', context: p.url).list, throwsStateError);
});
- test('reports exceptions for non-existent case-sensitive directories', () {
- expect(Glob('non/existent/**', caseSensitive: true).list().toList(),
- throwsA(isA<FileSystemException>()));
+ test('returns empty list for non-existent case-sensitive directories',
+ () async {
+ expect(await Glob('non/existent/**', caseSensitive: true).list().toList(),
+ []);
});
- test('reports exceptions for non-existent case-insensitive directories',
- () {
- expect(Glob('non/existent/**', caseSensitive: false).list().toList(),
- throwsA(isA<FileSystemException>()));
+ test('returns empty list for non-existent case-insensitive directories',
+ () async {
+ expect(
+ await Glob('non/existent/**', caseSensitive: false).list().toList(),
+ []);
});
});
@@ -42,32 +45,27 @@
expect(Glob('*', context: p.url).listSync, throwsStateError);
});
- test('reports exceptions for non-existent case-sensitive directories', () {
- expect(Glob('non/existent/**', caseSensitive: true).listSync,
- throwsA(isA<FileSystemException>()));
+ test('returns empty list for non-existent case-sensitive directories', () {
+ expect(Glob('non/existent/**', caseSensitive: true).listSync(), []);
});
- test('reports exceptions for non-existent case-insensitive directories',
+ test('returns empty list for non-existent case-insensitive directories',
() {
- expect(Glob('non/existent/**', caseSensitive: false).listSync,
- throwsA(isA<FileSystemException>()));
+ expect(Glob('non/existent/**', caseSensitive: false).listSync(), []);
});
});
group('when case-sensitive', () {
test('lists literals case-sensitively', () {
- expect(Glob('foo/BAZ/qux', caseSensitive: true).listSync,
- throwsA(isA<FileSystemException>()));
+ expect(Glob('foo/BAZ/qux', caseSensitive: true).listSync(), []);
});
test('lists ranges case-sensitively', () {
- expect(Glob('foo/[BX][A-Z]z/qux', caseSensitive: true).listSync,
- throwsA(isA<FileSystemException>()));
+ expect(Glob('foo/[BX][A-Z]z/qux', caseSensitive: true).listSync(), []);
});
test('options preserve case-sensitivity', () {
- expect(Glob('foo/{BAZ,ZAP}/qux', caseSensitive: true).listSync,
- throwsA(isA<FileSystemException>()));
+ expect(Glob('foo/{BAZ,ZAP}/qux', caseSensitive: true).listSync(), []);
});
});
@@ -314,7 +312,7 @@
}
typedef ListFn = FutureOr<List<String>> Function(String glob,
- {bool recursive, bool followLinks, bool caseSensitive});
+ {bool recursive, bool followLinks, bool? caseSensitive});
/// Runs [callback] in two groups with two values of [listFn]: one that uses
/// [Glob.list], one that uses [Glob.listSync].