Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(274)

Side by Side Diff: packages/watcher/lib/src/path_set.dart

Issue 2989763002: Update charted to 0.4.8 and roll (Closed)
Patch Set: Removed Cutch from list of reviewers Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library watcher.path_dart;
6
7 import 'dart:collection'; 5 import 'dart:collection';
8 6
9 import 'package:path/path.dart' as p; 7 import 'package:path/path.dart' as p;
10 8
11 /// A set of paths, organized into a directory hierarchy. 9 /// A set of paths, organized into a directory hierarchy.
12 /// 10 ///
13 /// When a path is [add]ed, it creates an implicit directory structure above 11 /// When a path is [add]ed, it creates an implicit directory structure above
14 /// that path. Directories can be inspected using [containsDir] and removed 12 /// that path. Directories can be inspected using [containsDir] and removed
15 /// using [remove]. If they're removed, their contents are removed as well. 13 /// using [remove]. If they're removed, their contents are removed as well.
16 /// 14 ///
17 /// The paths in the set are normalized so that they all begin with [root]. 15 /// The paths in the set are normalized so that they all begin with [root].
18 class PathSet { 16 class PathSet {
19 /// The root path, which all paths in the set must be under. 17 /// The root path, which all paths in the set must be under.
20 final String root; 18 final String root;
21 19
22 /// The path set's directory hierarchy. 20 /// The path set's directory hierarchy.
23 /// 21 ///
24 /// Each level of this hierarchy has the same structure: a map from strings to 22 /// Each entry represents a directory or file. It may be a file or directory
25 /// other maps, which are further levels of the hierarchy. A map with no 23 /// that was explicitly added, or a parent directory that was implicitly
26 /// elements indicates a path that was added to the set that has no paths 24 /// added in order to add a child.
27 /// beneath it. Such a path should not be treated as a directory by 25 final _Entry _entries = new _Entry();
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 26
38 PathSet(this.root); 27 PathSet(this.root);
39 28
40 /// Adds [path] to the set. 29 /// Adds [path] to the set.
41 void add(String path) { 30 void add(String path) {
42 path = _normalize(path); 31 path = _normalize(path);
43 _paths.add(path);
44 32
45 var parts = _split(path); 33 var parts = p.split(path);
46 var dir = _entries; 34 var entry = _entries;
47 for (var part in parts) { 35 for (var part in parts) {
48 dir = dir.putIfAbsent(part, () => {}); 36 entry = entry.contents.putIfAbsent(part, () => new _Entry());
49 } 37 }
38
39 entry.isExplicit = true;
50 } 40 }
51 41
52 /// Removes [path] and any paths beneath it from the set and returns the 42 /// Removes [path] and any paths beneath it from the set and returns the
53 /// removed paths. 43 /// removed paths.
54 /// 44 ///
55 /// Even if [path] itself isn't in the set, if it's a directory containing 45 /// 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. 46 /// paths that are in the set those paths will be removed and returned.
57 /// 47 ///
58 /// If neither [path] nor any paths beneath it are in the set, returns an 48 /// If neither [path] nor any paths beneath it are in the set, returns an
59 /// empty set. 49 /// empty set.
60 Set<String> remove(String path) { 50 Set<String> remove(String path) {
61 path = _normalize(path); 51 path = _normalize(path);
62 var parts = new Queue.from(_split(path)); 52 var parts = new Queue.from(p.split(path));
63 53
64 // Remove the children of [dir], as well as [dir] itself if necessary. 54 // Remove the children of [dir], as well as [dir] itself if necessary.
65 // 55 //
66 // [partialPath] is the path to [dir], and a prefix of [path]; the remaining 56 // [partialPath] is the path to [dir], and a prefix of [path]; the remaining
67 // components of [path] are in [parts]. 57 // components of [path] are in [parts].
68 recurse(dir, partialPath) { 58 Set<String> recurse(dir, partialPath) {
69 if (parts.length > 1) { 59 if (parts.length > 1) {
70 // If there's more than one component left in [path], recurse down to 60 // If there's more than one component left in [path], recurse down to
71 // the next level. 61 // the next level.
72 var part = parts.removeFirst(); 62 var part = parts.removeFirst();
73 var entry = dir[part]; 63 var entry = dir.contents[part];
74 if (entry == null || entry.isEmpty) return new Set(); 64 if (entry == null || entry.contents.isEmpty) return new Set();
75 65
76 partialPath = p.join(partialPath, part); 66 partialPath = p.join(partialPath, part);
77 var paths = recurse(entry, partialPath); 67 var paths = recurse(entry, partialPath);
78 // After removing this entry's children, if it has no more children and 68 // 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. 69 // it's not in the set in its own right, remove it as well.
80 if (entry.isEmpty && !_paths.contains(partialPath)) dir.remove(part); 70 if (entry.contents.isEmpty && !entry.isExplicit) {
71 dir.contents.remove(part);
72 }
81 return paths; 73 return paths;
82 } 74 }
83 75
84 // If there's only one component left in [path], we should remove it. 76 // If there's only one component left in [path], we should remove it.
85 var entry = dir.remove(parts.first); 77 var entry = dir.contents.remove(parts.first);
86 if (entry == null) return new Set(); 78 if (entry == null) return new Set();
87 79
88 if (entry.isEmpty) { 80 if (entry.contents.isEmpty) {
89 _paths.remove(path); 81 return new Set.from([p.join(root, path)]);
90 return new Set.from([path]);
91 } 82 }
92 83
93 var set = _removePathsIn(entry, path); 84 var set = _explicitPathsWithin(entry, path);
94 if (_paths.contains(path)) { 85 if (entry.isExplicit) {
95 _paths.remove(path); 86 set.add(p.join(root, path));
96 set.add(path);
97 } 87 }
88
98 return set; 89 return set;
99 } 90 }
100 91
101 return recurse(_entries, root); 92 return recurse(_entries, root);
102 } 93 }
103 94
104 /// Recursively removes and returns all paths in [dir]. 95 /// Recursively lists all of the explicit paths within [dir].
105 /// 96 ///
106 /// [root] should be the path to [dir]. 97 /// [dirPath] should be the path to [dir].
107 Set<String> _removePathsIn(Map dir, String root) { 98 Set<String> _explicitPathsWithin(_Entry dir, String dirPath) {
108 var removedPaths = new Set(); 99 var paths = new Set<String>();
109 recurse(dir, path) { 100 recurse(dir, path) {
110 dir.forEach((name, entry) { 101 dir.contents.forEach((name, entry) {
111 var entryPath = p.join(path, name); 102 var entryPath = p.join(path, name);
112 if (_paths.remove(entryPath)) removedPaths.add(entryPath); 103 if (entry.isExplicit) paths.add(p.join(root, entryPath));
104
113 recurse(entry, entryPath); 105 recurse(entry, entryPath);
114 }); 106 });
115 } 107 }
116 108
117 recurse(dir, root); 109 recurse(dir, dirPath);
118 return removedPaths; 110 return paths;
119 } 111 }
120 112
121 /// Returns whether [this] contains [path]. 113 /// Returns whether [this] contains [path].
122 /// 114 ///
123 /// This only returns true for paths explicitly added to [this]. 115 /// This only returns true for paths explicitly added to [this].
124 /// Implicitly-added directories can be inspected using [containsDir]. 116 /// Implicitly-added directories can be inspected using [containsDir].
125 bool contains(String path) => _paths.contains(_normalize(path)); 117 bool contains(String path) {
118 path = _normalize(path);
119 var entry = _entries;
120
121 for (var part in p.split(path)) {
122 entry = entry.contents[part];
123 if (entry == null) return false;
124 }
125
126 return entry.isExplicit;
127 }
126 128
127 /// Returns whether [this] contains paths beneath [path]. 129 /// Returns whether [this] contains paths beneath [path].
128 bool containsDir(String path) { 130 bool containsDir(String path) {
129 path = _normalize(path); 131 path = _normalize(path);
130 var dir = _entries; 132 var entry = _entries;
131 133
132 for (var part in _split(path)) { 134 for (var part in p.split(path)) {
133 dir = dir[part]; 135 entry = entry.contents[part];
134 if (dir == null) return false; 136 if (entry == null) return false;
135 } 137 }
136 138
137 return !dir.isEmpty; 139 return !entry.contents.isEmpty;
138 } 140 }
139 141
140 /// Returns a [Set] of all paths in [this]. 142 /// All of the paths explicitly added to this set.
141 Set<String> toSet() => _paths.toSet(); 143 List<String> get paths {
144 var result = <String>[];
145
146 recurse(dir, path) {
147 for (var name in dir.contents.keys) {
148 var entry = dir.contents[name];
149 var entryPath = p.join(path, name);
150 if (entry.isExplicit) result.add(entryPath);
151 recurse(entry, entryPath);
152 }
153 }
154
155 recurse(_entries, root);
156 return result;
157 }
142 158
143 /// Removes all paths from [this]. 159 /// Removes all paths from [this].
144 void clear() { 160 void clear() {
145 _paths.clear(); 161 _entries.contents.clear();
146 _entries.clear();
147 } 162 }
148 163
149 String toString() => _paths.toString();
150
151 /// Returns a normalized version of [path]. 164 /// Returns a normalized version of [path].
152 /// 165 ///
153 /// This removes any extra ".." or "."s and ensure that the returned path 166 /// 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]. 167 /// begins with [root]. It's an error if [path] isn't within [root].
155 String _normalize(String path) { 168 String _normalize(String path) {
156 var relative = p.relative(p.normalize(path), from: root); 169 assert(p.isWithin(root, path));
157 var parts = p.split(relative); 170
158 // TODO(nweiz): replace this with [p.isWithin] when that exists (issue 171 return p.relative(p.normalize(path), from: root);
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 } 172 }
173 }
165 174
166 /// Returns the segments of [path] beneath [root]. 175 /// A virtual file system entity tracked by the [PathSet].
167 List<String> _split(String path) => p.split(p.relative(path, from: root)); 176 ///
177 /// It may have child entries in [contents], which implies it's a directory.
178 class _Entry {
179 /// The child entries contained in this directory.
180 final Map<String, _Entry> contents = {};
181
182 /// If this entry was explicitly added as a leaf file system entity, this
183 /// will be true.
184 ///
185 /// Otherwise, it represents a parent directory that was implicitly added
186 /// when added some child of it.
187 bool isExplicit = false;
168 } 188 }
OLDNEW
« no previous file with comments | « packages/watcher/lib/src/file_watcher/polling.dart ('k') | packages/watcher/lib/src/resubscribable.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698