| Index: packages/watcher/lib/src/path_set.dart
|
| diff --git a/packages/watcher/lib/src/path_set.dart b/packages/watcher/lib/src/path_set.dart
|
| index e9f7d32d25c294ea697cb702752e63ac21112584..3726e1fd86b440142123029e8892db46c87e725c 100644
|
| --- a/packages/watcher/lib/src/path_set.dart
|
| +++ b/packages/watcher/lib/src/path_set.dart
|
| @@ -2,8 +2,6 @@
|
| // 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 watcher.path_dart;
|
| -
|
| import 'dart:collection';
|
|
|
| import 'package:path/path.dart' as p;
|
| @@ -21,32 +19,24 @@ class PathSet {
|
|
|
| /// The path set's directory hierarchy.
|
| ///
|
| - /// Each level of this hierarchy has the same structure: a map from strings to
|
| - /// other maps, which are further levels of the hierarchy. A map with no
|
| - /// elements indicates a path that was added to the set that has no paths
|
| - /// beneath it. Such a path should not be treated as a directory by
|
| - /// [containsDir].
|
| - final _entries = new Map<String, Map>();
|
| -
|
| - /// The set of paths that were explicitly added to this set.
|
| - ///
|
| - /// This is needed to disambiguate a directory that was explicitly added to
|
| - /// the set from a directory that was implicitly added by adding a path
|
| - /// beneath it.
|
| - final _paths = new Set<String>();
|
| + /// Each entry represents a directory or file. It may be a file or directory
|
| + /// that was explicitly added, or a parent directory that was implicitly
|
| + /// added in order to add a child.
|
| + final _Entry _entries = new _Entry();
|
|
|
| PathSet(this.root);
|
|
|
| /// Adds [path] to the set.
|
| void add(String path) {
|
| path = _normalize(path);
|
| - _paths.add(path);
|
|
|
| - var parts = _split(path);
|
| - var dir = _entries;
|
| + var parts = p.split(path);
|
| + var entry = _entries;
|
| for (var part in parts) {
|
| - dir = dir.putIfAbsent(part, () => {});
|
| + entry = entry.contents.putIfAbsent(part, () => new _Entry());
|
| }
|
| +
|
| + entry.isExplicit = true;
|
| }
|
|
|
| /// Removes [path] and any paths beneath it from the set and returns the
|
| @@ -59,110 +49,140 @@ class PathSet {
|
| /// empty set.
|
| Set<String> remove(String path) {
|
| path = _normalize(path);
|
| - var parts = new Queue.from(_split(path));
|
| + var parts = new Queue.from(p.split(path));
|
|
|
| // Remove the children of [dir], as well as [dir] itself if necessary.
|
| //
|
| // [partialPath] is the path to [dir], and a prefix of [path]; the remaining
|
| // components of [path] are in [parts].
|
| - recurse(dir, partialPath) {
|
| + Set<String> recurse(dir, partialPath) {
|
| if (parts.length > 1) {
|
| // If there's more than one component left in [path], recurse down to
|
| // the next level.
|
| var part = parts.removeFirst();
|
| - var entry = dir[part];
|
| - if (entry == null || entry.isEmpty) return new Set();
|
| + var entry = dir.contents[part];
|
| + if (entry == null || entry.contents.isEmpty) return new Set();
|
|
|
| partialPath = p.join(partialPath, part);
|
| var paths = recurse(entry, partialPath);
|
| // After removing this entry's children, if it has no more children and
|
| // it's not in the set in its own right, remove it as well.
|
| - if (entry.isEmpty && !_paths.contains(partialPath)) dir.remove(part);
|
| + if (entry.contents.isEmpty && !entry.isExplicit) {
|
| + dir.contents.remove(part);
|
| + }
|
| return paths;
|
| }
|
|
|
| // If there's only one component left in [path], we should remove it.
|
| - var entry = dir.remove(parts.first);
|
| + var entry = dir.contents.remove(parts.first);
|
| if (entry == null) return new Set();
|
|
|
| - if (entry.isEmpty) {
|
| - _paths.remove(path);
|
| - return new Set.from([path]);
|
| + if (entry.contents.isEmpty) {
|
| + return new Set.from([p.join(root, path)]);
|
| }
|
|
|
| - var set = _removePathsIn(entry, path);
|
| - if (_paths.contains(path)) {
|
| - _paths.remove(path);
|
| - set.add(path);
|
| + var set = _explicitPathsWithin(entry, path);
|
| + if (entry.isExplicit) {
|
| + set.add(p.join(root, path));
|
| }
|
| +
|
| return set;
|
| }
|
|
|
| return recurse(_entries, root);
|
| }
|
|
|
| - /// Recursively removes and returns all paths in [dir].
|
| + /// Recursively lists all of the explicit paths within [dir].
|
| ///
|
| - /// [root] should be the path to [dir].
|
| - Set<String> _removePathsIn(Map dir, String root) {
|
| - var removedPaths = new Set();
|
| + /// [dirPath] should be the path to [dir].
|
| + Set<String> _explicitPathsWithin(_Entry dir, String dirPath) {
|
| + var paths = new Set<String>();
|
| recurse(dir, path) {
|
| - dir.forEach((name, entry) {
|
| + dir.contents.forEach((name, entry) {
|
| var entryPath = p.join(path, name);
|
| - if (_paths.remove(entryPath)) removedPaths.add(entryPath);
|
| + if (entry.isExplicit) paths.add(p.join(root, entryPath));
|
| +
|
| recurse(entry, entryPath);
|
| });
|
| }
|
|
|
| - recurse(dir, root);
|
| - return removedPaths;
|
| + recurse(dir, dirPath);
|
| + return paths;
|
| }
|
|
|
| /// Returns whether [this] contains [path].
|
| ///
|
| /// This only returns true for paths explicitly added to [this].
|
| /// Implicitly-added directories can be inspected using [containsDir].
|
| - bool contains(String path) => _paths.contains(_normalize(path));
|
| + bool contains(String path) {
|
| + path = _normalize(path);
|
| + var entry = _entries;
|
| +
|
| + for (var part in p.split(path)) {
|
| + entry = entry.contents[part];
|
| + if (entry == null) return false;
|
| + }
|
| +
|
| + return entry.isExplicit;
|
| + }
|
|
|
| /// Returns whether [this] contains paths beneath [path].
|
| bool containsDir(String path) {
|
| path = _normalize(path);
|
| - var dir = _entries;
|
| + var entry = _entries;
|
|
|
| - for (var part in _split(path)) {
|
| - dir = dir[part];
|
| - if (dir == null) return false;
|
| + for (var part in p.split(path)) {
|
| + entry = entry.contents[part];
|
| + if (entry == null) return false;
|
| }
|
|
|
| - return !dir.isEmpty;
|
| + return !entry.contents.isEmpty;
|
| }
|
|
|
| - /// Returns a [Set] of all paths in [this].
|
| - Set<String> toSet() => _paths.toSet();
|
| + /// All of the paths explicitly added to this set.
|
| + List<String> get paths {
|
| + var result = <String>[];
|
| +
|
| + recurse(dir, path) {
|
| + for (var name in dir.contents.keys) {
|
| + var entry = dir.contents[name];
|
| + var entryPath = p.join(path, name);
|
| + if (entry.isExplicit) result.add(entryPath);
|
| + recurse(entry, entryPath);
|
| + }
|
| + }
|
| +
|
| + recurse(_entries, root);
|
| + return result;
|
| + }
|
|
|
| /// Removes all paths from [this].
|
| void clear() {
|
| - _paths.clear();
|
| - _entries.clear();
|
| + _entries.contents.clear();
|
| }
|
|
|
| - String toString() => _paths.toString();
|
| -
|
| /// Returns a normalized version of [path].
|
| ///
|
| /// This removes any extra ".." or "."s and ensure that the returned path
|
| /// begins with [root]. It's an error if [path] isn't within [root].
|
| String _normalize(String path) {
|
| - var relative = p.relative(p.normalize(path), from: root);
|
| - var parts = p.split(relative);
|
| - // TODO(nweiz): replace this with [p.isWithin] when that exists (issue
|
| - // 14980).
|
| - if (!p.isRelative(relative) || parts.first == '..' || parts.first == '.') {
|
| - throw new ArgumentError('Path "$path" is not inside "$root".');
|
| - }
|
| - return p.join(root, relative);
|
| + assert(p.isWithin(root, path));
|
| +
|
| + return p.relative(p.normalize(path), from: root);
|
| }
|
| +}
|
|
|
| - /// Returns the segments of [path] beneath [root].
|
| - List<String> _split(String path) => p.split(p.relative(path, from: root));
|
| +/// A virtual file system entity tracked by the [PathSet].
|
| +///
|
| +/// It may have child entries in [contents], which implies it's a directory.
|
| +class _Entry {
|
| + /// The child entries contained in this directory.
|
| + final Map<String, _Entry> contents = {};
|
| +
|
| + /// If this entry was explicitly added as a leaf file system entity, this
|
| + /// will be true.
|
| + ///
|
| + /// Otherwise, it represents a parent directory that was implicitly added
|
| + /// when added some child of it.
|
| + bool isExplicit = false;
|
| }
|
|
|