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 i = 0; i < parts.length; i++) { | |
Bob Nystrom
2013/11/09 00:42:57
Use a for-in loop here?
nweiz
2013/11/09 02:19:26
Done. This used to be structured differently.
| |
48 dir = dir.putIfAbsent(parts[i], () => {}); | |
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 | |
Bob Nystrom
2013/11/09 00:42:57
This could definitely use some more docs.
nweiz
2013/11/09 02:19:26
Done.
| |
64 recurse(dir, partialPath) { | |
65 if (parts.length > 1) { | |
66 var part = parts.removeFirst(); | |
67 var entry = dir[part]; | |
68 if (entry.isEmpty) return new Set(); | |
69 | |
70 partialPath = p.join(partialPath, part); | |
71 var paths = recurse(entry, partialPath); | |
72 if (entry.isEmpty && !_paths.contains(partialPath)) dir.remove(part); | |
Bob Nystrom
2013/11/09 00:42:57
// If after removing the children, there is nothin
nweiz
2013/11/09 02:19:26
Done.
| |
73 return paths; | |
74 } | |
75 | |
76 var entry = dir.remove(parts.first); | |
77 if (entry == null) return new Set(); | |
78 | |
79 if (entry.isEmpty) { | |
80 _paths.remove(path); | |
81 return new Set.from([path]); | |
82 } | |
83 | |
84 var set = _removePathsIn(entry, path); | |
85 if (_paths.contains(path)) { | |
86 _paths.remove(path); | |
87 set.add(path); | |
88 } | |
89 return set; | |
90 } | |
91 | |
92 return recurse(_entries, root); | |
93 } | |
94 | |
95 /// Recursively removes and returns all paths in [dir]. | |
96 /// | |
97 /// [root] should be the path to [dir]. | |
98 Set<String> _removePathsIn(Map dir, String root) { | |
99 var removedPaths = new Set(); | |
100 recurse(dir, path) { | |
101 dir.forEach((name, entry) { | |
102 var entryPath = p.join(path, name); | |
103 if (_paths.remove(entryPath)) removedPaths.add(entryPath); | |
104 recurse(entry, entryPath); | |
105 }); | |
106 } | |
107 | |
108 recurse(dir, root); | |
109 return removedPaths; | |
110 } | |
111 | |
112 /// Returns whether [this] contains [path]. | |
113 /// | |
114 /// This only returns true for paths explicitly added to [this]. | |
115 /// Implicitly-added directories can be inspected using [containsDir]. | |
116 bool contains(String path) => _paths.contains(_normalize(path)); | |
117 | |
118 /// Returns whether [this] contains paths beneath [path]. | |
119 bool containsDir(String path) { | |
120 path = _normalize(path); | |
121 var dir = _entries; | |
122 | |
123 for (var part in _split(path)) { | |
124 dir = dir[part]; | |
125 if (dir == null) return false; | |
126 } | |
127 | |
128 return !dir.isEmpty; | |
129 } | |
130 | |
131 /// Returns a [Set] of all paths in [this]. | |
132 Set<String> toSet() => _paths.toSet(); | |
133 | |
134 /// Removes all paths from [this]. | |
135 void clear() { | |
136 _paths.clear(); | |
137 _entries.clear(); | |
138 } | |
139 | |
140 String toString() => _paths.toString(); | |
141 | |
142 /// Returns a normalized version of [path]. | |
143 /// | |
144 /// This removes any extra ".." or "."s and ensure that the returned path | |
145 /// begins with [root]. It's an error if [path] isn't within [root]. | |
146 String _normalize(String path) { | |
147 var relative = p.relative(p.normalize(path), from: root); | |
148 var parts = p.split(relative); | |
149 if (!p.isRelative(relative) || parts.first == '..' || parts.first == '.') { | |
Bob Nystrom
2013/11/09 00:42:57
We should really add isWithin() to pkg/path.
nweiz
2013/11/09 02:19:26
Filed a bug and referenced it here.
| |
150 throw new ArgumentError('Path "$path" is not inside "$root".'); | |
151 } | |
152 return p.join(root, relative); | |
153 } | |
154 | |
155 /// Returns the segments of [path] beneath [root]. | |
156 List<String> _split(String path) => p.split(p.relative(path, from: root)); | |
157 } | |
OLD | NEW |