OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library watcher.path_dart; |
| 6 |
| 7 import 'dart:collection'; |
| 8 |
| 9 import 'package:path/path.dart' as p; |
| 10 |
| 11 /// A set of paths, organized into a directory hierarchy. |
| 12 /// |
| 13 /// When a path is [add]ed, it creates an implicit directory structure above |
| 14 /// that path. Directories can be inspected using [containsDir] and removed |
| 15 /// using [remove]. If they're removed, their contents are removed as well. |
| 16 /// |
| 17 /// The paths in the set are normalized so that they all begin with [root]. |
| 18 class PathSet { |
| 19 /// The root path, which all paths in the set must be under. |
| 20 final String root; |
| 21 |
| 22 /// The path set's directory hierarchy. |
| 23 /// |
| 24 /// Each level of this hierarchy has the same structure: a map from strings to |
| 25 /// other maps, which are further levels of the hierarchy. A map with no |
| 26 /// elements indicates a path that was added to the set that has no paths |
| 27 /// beneath it. Such a path should not be treated as a directory by |
| 28 /// [containsDir]. |
| 29 final _entries = new Map<String, Map>(); |
| 30 |
| 31 /// The set of paths that were explicitly added to this set. |
| 32 /// |
| 33 /// This is needed to disambiguate a directory that was explicitly added to |
| 34 /// the set from a directory that was implicitly added by adding a path |
| 35 /// beneath it. |
| 36 final _paths = new Set<String>(); |
| 37 |
| 38 PathSet(this.root); |
| 39 |
| 40 /// Adds [path] to the set. |
| 41 void add(String path) { |
| 42 path = _normalize(path); |
| 43 _paths.add(path); |
| 44 |
| 45 var parts = _split(path); |
| 46 var dir = _entries; |
| 47 for (var part in parts) { |
| 48 dir = dir.putIfAbsent(part, () => {}); |
| 49 } |
| 50 } |
| 51 |
| 52 /// Removes [path] and any paths beneath it from the set and returns the |
| 53 /// removed paths. |
| 54 /// |
| 55 /// Even if [path] itself isn't in the set, if it's a directory containing |
| 56 /// paths that are in the set those paths will be removed and returned. |
| 57 /// |
| 58 /// If neither [path] nor any paths beneath it are in the set, returns an |
| 59 /// empty set. |
| 60 Set<String> remove(String path) { |
| 61 path = _normalize(path); |
| 62 var parts = new Queue.from(_split(path)); |
| 63 |
| 64 // Remove the children of [dir], as well as [dir] itself if necessary. |
| 65 // |
| 66 // [partialPath] is the path to [dir], and a prefix of [path]; the remaining |
| 67 // components of [path] are in [parts]. |
| 68 recurse(dir, partialPath) { |
| 69 if (parts.length > 1) { |
| 70 // If there's more than one component left in [path], recurse down to |
| 71 // the next level. |
| 72 var part = parts.removeFirst(); |
| 73 var entry = dir[part]; |
| 74 if (entry.isEmpty) return new Set(); |
| 75 |
| 76 partialPath = p.join(partialPath, part); |
| 77 var paths = recurse(entry, partialPath); |
| 78 // After removing this entry's children, if it has no more children and |
| 79 // it's not in the set in its own right, remove it as well. |
| 80 if (entry.isEmpty && !_paths.contains(partialPath)) dir.remove(part); |
| 81 return paths; |
| 82 } |
| 83 |
| 84 // If there's only one component left in [path], we should remove it. |
| 85 var entry = dir.remove(parts.first); |
| 86 if (entry == null) return new Set(); |
| 87 |
| 88 if (entry.isEmpty) { |
| 89 _paths.remove(path); |
| 90 return new Set.from([path]); |
| 91 } |
| 92 |
| 93 var set = _removePathsIn(entry, path); |
| 94 if (_paths.contains(path)) { |
| 95 _paths.remove(path); |
| 96 set.add(path); |
| 97 } |
| 98 return set; |
| 99 } |
| 100 |
| 101 return recurse(_entries, root); |
| 102 } |
| 103 |
| 104 /// Recursively removes and returns all paths in [dir]. |
| 105 /// |
| 106 /// [root] should be the path to [dir]. |
| 107 Set<String> _removePathsIn(Map dir, String root) { |
| 108 var removedPaths = new Set(); |
| 109 recurse(dir, path) { |
| 110 dir.forEach((name, entry) { |
| 111 var entryPath = p.join(path, name); |
| 112 if (_paths.remove(entryPath)) removedPaths.add(entryPath); |
| 113 recurse(entry, entryPath); |
| 114 }); |
| 115 } |
| 116 |
| 117 recurse(dir, root); |
| 118 return removedPaths; |
| 119 } |
| 120 |
| 121 /// Returns whether [this] contains [path]. |
| 122 /// |
| 123 /// This only returns true for paths explicitly added to [this]. |
| 124 /// Implicitly-added directories can be inspected using [containsDir]. |
| 125 bool contains(String path) => _paths.contains(_normalize(path)); |
| 126 |
| 127 /// Returns whether [this] contains paths beneath [path]. |
| 128 bool containsDir(String path) { |
| 129 path = _normalize(path); |
| 130 var dir = _entries; |
| 131 |
| 132 for (var part in _split(path)) { |
| 133 dir = dir[part]; |
| 134 if (dir == null) return false; |
| 135 } |
| 136 |
| 137 return !dir.isEmpty; |
| 138 } |
| 139 |
| 140 /// Returns a [Set] of all paths in [this]. |
| 141 Set<String> toSet() => _paths.toSet(); |
| 142 |
| 143 /// Removes all paths from [this]. |
| 144 void clear() { |
| 145 _paths.clear(); |
| 146 _entries.clear(); |
| 147 } |
| 148 |
| 149 String toString() => _paths.toString(); |
| 150 |
| 151 /// Returns a normalized version of [path]. |
| 152 /// |
| 153 /// This removes any extra ".." or "."s and ensure that the returned path |
| 154 /// begins with [root]. It's an error if [path] isn't within [root]. |
| 155 String _normalize(String path) { |
| 156 var relative = p.relative(p.normalize(path), from: root); |
| 157 var parts = p.split(relative); |
| 158 // TODO(nweiz): replace this with [p.isWithin] when that exists (issue |
| 159 // 14980). |
| 160 if (!p.isRelative(relative) || parts.first == '..' || parts.first == '.') { |
| 161 throw new ArgumentError('Path "$path" is not inside "$root".'); |
| 162 } |
| 163 return p.join(root, relative); |
| 164 } |
| 165 |
| 166 /// Returns the segments of [path] beneath [root]. |
| 167 List<String> _split(String path) => p.split(p.relative(path, from: root)); |
| 168 } |
OLD | NEW |