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

Side by Side Diff: packages/glob/lib/src/list_tree.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
« no previous file with comments | « packages/glob/lib/src/ast.dart ('k') | packages/glob/lib/src/parser.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2014, 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 glob.list_tree;
6
7 import 'dart:io'; 5 import 'dart:io';
8 import 'dart:async'; 6 import 'dart:async';
9 7
8 import 'package:async/async.dart';
10 import 'package:path/path.dart' as p; 9 import 'package:path/path.dart' as p;
11 10
12 import 'ast.dart'; 11 import 'ast.dart';
13 import 'stream_pool.dart';
14 import 'utils.dart'; 12 import 'utils.dart';
15 13
16 /// The errno for a file or directory not existing on Mac and Linux. 14 /// The errno for a file or directory not existing on Mac and Linux.
17 const _ENOENT = 2; 15 const _ENOENT = 2;
18 16
19 /// Another errno we see on Windows when trying to list a non-existent 17 /// Another errno we see on Windows when trying to list a non-existent
20 /// directory. 18 /// directory.
21 const _ENOENT_WIN = 3; 19 const _ENOENT_WIN = 3;
22 20
23 /// A structure built from a glob that efficiently lists filesystem entities 21 /// A structure built from a glob that efficiently lists filesystem entities
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
92 } 90 }
93 } 91 }
94 92
95 _addGlob(root, components); 93 _addGlob(root, components);
96 } 94 }
97 95
98 _canOverlap = _computeCanOverlap(); 96 _canOverlap = _computeCanOverlap();
99 } 97 }
100 98
101 /// Add the glob represented by [components] to the tree under [root]. 99 /// Add the glob represented by [components] to the tree under [root].
102 void _addGlob(String root, List<AstNode> components) { 100 void _addGlob(String root, List<SequenceNode> components) {
103 // The first [parent] represents the root directory itself. It may be null 101 // The first [parent] represents the root directory itself. It may be null
104 // here if this is the first option with this particular [root]. If so, 102 // here if this is the first option with this particular [root]. If so,
105 // we'll create it below. 103 // we'll create it below.
106 // 104 //
107 // As we iterate through [components], [parent] will be set to 105 // As we iterate through [components], [parent] will be set to
108 // progressively more nested nodes. 106 // progressively more nested nodes.
109 var parent = _trees[root]; 107 var parent = _trees[root];
110 for (var i = 0; i < components.length; i++) { 108 for (var i = 0; i < components.length; i++) {
111 var component = components[i]; 109 var component = components[i];
112 var recursive = component.nodes.any((node) => node is DoubleStarNode); 110 var recursive = component.nodes.any((node) => node is DoubleStarNode);
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
165 if (_trees.length > 1 && _trees.containsKey('.')) return true; 163 if (_trees.length > 1 && _trees.containsKey('.')) return true;
166 164
167 // Otherwise, this can only overlap if the tree beneath any given root could 165 // Otherwise, this can only overlap if the tree beneath any given root could
168 // overlap internally. 166 // overlap internally.
169 return _trees.values.any((node) => node.canOverlap); 167 return _trees.values.any((node) => node.canOverlap);
170 } 168 }
171 169
172 /// List all entities that match this glob beneath [root]. 170 /// List all entities that match this glob beneath [root].
173 Stream<FileSystemEntity> list({String root, bool followLinks: true}) { 171 Stream<FileSystemEntity> list({String root, bool followLinks: true}) {
174 if (root == null) root = '.'; 172 if (root == null) root = '.';
175 var pool = new StreamPool(); 173 var group = new StreamGroup<FileSystemEntity>();
176 for (var rootDir in _trees.keys) { 174 for (var rootDir in _trees.keys) {
177 var dir = rootDir == '.' ? root : rootDir; 175 var dir = rootDir == '.' ? root : rootDir;
178 pool.add(_trees[rootDir].list(dir, followLinks: followLinks)); 176 group.add(_trees[rootDir].list(dir, followLinks: followLinks));
179 } 177 }
180 pool.closeWhenEmpty(); 178 group.close();
181 179
182 if (!_canOverlap) return pool.stream; 180 if (!_canOverlap) return group.stream;
183 181
184 // TODO(nweiz): Rather than filtering here, avoid double-listing directories 182 // TODO(nweiz): Rather than filtering here, avoid double-listing directories
185 // in the first place. 183 // in the first place.
186 var seen = new Set(); 184 var seen = new Set();
187 return pool.stream.where((entity) { 185 return group.stream.where((entity) {
188 if (seen.contains(entity.path)) return false; 186 if (seen.contains(entity.path)) return false;
189 seen.add(entity.path); 187 seen.add(entity.path);
190 return true; 188 return true;
191 }); 189 });
192 } 190 }
193 191
194 /// Synchronosuly list all entities that match this glob beneath [root]. 192 /// Synchronosuly list all entities that match this glob beneath [root].
195 List<FileSystemEntity> listSync({String root, bool followLinks: true}) { 193 List<FileSystemEntity> listSync({String root, bool followLinks: true}) {
196 if (root == null) root = '.'; 194 if (root == null) root = '.';
197 195
198 var result = _trees.keys.expand((rootDir) { 196 // TODO(nweiz): Remove the explicit annotation when sdk#26139 is fixed.
197 var result = _trees.keys.expand/*<FileSystemEntity>*/((rootDir) {
199 var dir = rootDir == '.' ? root : rootDir; 198 var dir = rootDir == '.' ? root : rootDir;
200 return _trees[rootDir].listSync(dir, followLinks: followLinks); 199 return _trees[rootDir].listSync(dir, followLinks: followLinks);
201 }); 200 });
202 201
203 if (!_canOverlap) return result.toList(); 202 if (!_canOverlap) return result.toList();
204 203
205 // TODO(nweiz): Rather than filtering here, avoid double-listing directories 204 // TODO(nweiz): Rather than filtering here, avoid double-listing directories
206 // in the first place. 205 // in the first place.
207 var seen = new Set(); 206 var seen = new Set<String>();
208 return result.where((entity) { 207 return result.where((entity) {
209 if (seen.contains(entity.path)) return false; 208 if (seen.contains(entity.path)) return false;
210 seen.add(entity.path); 209 seen.add(entity.path);
211 return true; 210 return true;
212 }).toList(); 211 }).toList();
213 } 212 }
214 } 213 }
215 214
216 /// A single node in a [ListTree]. 215 /// A single node in a [ListTree].
217 class _ListTreeNode { 216 class _ListTreeNode {
218 /// This node's child nodes, by their corresponding globs. 217 /// This node's child nodes, by their corresponding globs.
219 /// 218 ///
220 /// Each child node will only be listed on directories that match its glob. 219 /// Each child node will only be listed on directories that match its glob.
221 /// 220 ///
222 /// This may be `null`, indicating that this node should be listed 221 /// This may be `null`, indicating that this node should be listed
223 /// recursively. 222 /// recursively.
224 Map<SequenceNode, _ListTreeNode> children; 223 Map<SequenceNode, _ListTreeNode> children;
225 224
226 /// This node's validator. 225 /// This node's validator.
227 /// 226 ///
228 /// This determines which entities will ultimately be emitted when [list] is 227 /// This determines which entities will ultimately be emitted when [list] is
229 /// called. 228 /// called.
230 OptionsNode _validator; 229 OptionsNode _validator;
231 230
232 /// Whether this node is recursive. 231 /// Whether this node is recursive.
233 /// 232 ///
234 /// A recursive node has no children and is listed recursively. 233 /// A recursive node has no children and is listed recursively.
235 bool get isRecursive => children == null; 234 bool get isRecursive => children == null;
236 235
236 bool get _caseSensitive {
237 if (_validator != null) return _validator.caseSensitive;
238 if (children == null) return true;
239 if (children.isEmpty) return true;
240 return children.keys.first.caseSensitive;
241 }
242
237 /// Whether this node doesn't itself need to be listed. 243 /// Whether this node doesn't itself need to be listed.
238 /// 244 ///
239 /// If a node has no validator and all of its children are literal filenames, 245 /// If a node has no validator and all of its children are literal filenames,
240 /// there's no need to list its contents. We can just directly traverse into 246 /// there's no need to list its contents. We can just directly traverse into
241 /// its children. 247 /// its children.
242 bool get _isIntermediate { 248 bool get _isIntermediate {
243 if (_validator != null) return false; 249 if (_validator != null) return false;
250 if (!_caseSensitive) return false;
244 return children.keys.every((sequence) => 251 return children.keys.every((sequence) =>
245 sequence.nodes.length == 1 && sequence.nodes.first is LiteralNode); 252 sequence.nodes.length == 1 && sequence.nodes.first is LiteralNode);
246 } 253 }
247 254
248 /// Returns whether listing this node might return overlapping results. 255 /// Returns whether listing this node might return overlapping results.
249 bool get canOverlap { 256 bool get canOverlap {
250 // A recusive node can never overlap with itself, because it will only ever 257 // A recusive node can never overlap with itself, because it will only ever
251 // involve a single call to [Directory.list] that's then filtered with 258 // involve a single call to [Directory.list] that's then filtered with
252 // [_validator]. 259 // [_validator].
253 if (isRecursive) return false; 260 if (isRecursive) return false;
254 261
255 // If there's more than one child node and at least one of the children is 262 // If there's more than one child node and at least one of the children is
256 // dynamic (that is, matches more than just a literal string), there may be 263 // dynamic (that is, matches more than just a literal string), there may be
257 // overlap. 264 // overlap.
258 if (children.length > 1 && children.keys.any((sequence) => 265 if (children.length > 1) {
266 // Case-insensitivity means that even literals may match multiple entries.
267 if (!_caseSensitive) return true;
268
269 if (children.keys.any((sequence) =>
259 sequence.nodes.length > 1 || sequence.nodes.single is! LiteralNode)) { 270 sequence.nodes.length > 1 || sequence.nodes.single is! LiteralNode)) {
260 return true; 271 return true;
272 }
261 } 273 }
262 274
263 return children.values.any((node) => node.canOverlap); 275 return children.values.any((node) => node.canOverlap);
264 } 276 }
265 277
266 /// Creates a node with no children and no validator. 278 /// Creates a node with no children and no validator.
267 _ListTreeNode() 279 _ListTreeNode()
268 : children = new Map<SequenceNode, _ListTreeNode>(), 280 : children = new Map<SequenceNode, _ListTreeNode>(),
269 _validator = null; 281 _validator = null;
270 282
271 /// Creates a recursive node the given [validator]. 283 /// Creates a recursive node the given [validator].
272 _ListTreeNode.recursive(SequenceNode validator) 284 _ListTreeNode.recursive(SequenceNode validator)
273 : children = null, 285 : children = null,
274 _validator = new OptionsNode([validator]); 286 _validator = new OptionsNode([validator],
287 caseSensitive: validator.caseSensitive);
275 288
276 /// Transforms this into recursive node, folding all its children into its 289 /// Transforms this into recursive node, folding all its children into its
277 /// validator. 290 /// validator.
278 void makeRecursive() { 291 void makeRecursive() {
279 if (isRecursive) return; 292 if (isRecursive) return;
280 _validator = new OptionsNode(children.keys.map((sequence) { 293 _validator = new OptionsNode(children.keys.map((sequence) {
281 var child = children[sequence]; 294 var child = children[sequence];
282 child.makeRecursive(); 295 child.makeRecursive();
283 return _join([sequence, child._validator]); 296 return _join([sequence, child._validator]);
284 })); 297 }), caseSensitive: _caseSensitive);
285 children = null; 298 children = null;
286 } 299 }
287 300
288 /// Adds [validator] to this node's existing validator. 301 /// Adds [validator] to this node's existing validator.
289 void addOption(SequenceNode validator) { 302 void addOption(SequenceNode validator) {
290 if (_validator == null) { 303 if (_validator == null) {
291 _validator = new OptionsNode([validator]); 304 _validator = new OptionsNode([validator],
305 caseSensitive: validator.caseSensitive);
292 } else { 306 } else {
293 _validator.options.add(validator); 307 _validator.options.add(validator);
294 } 308 }
295 } 309 }
296 310
297 /// Lists all entities within [dir] matching this node or its children. 311 /// Lists all entities within [dir] matching this node or its children.
298 /// 312 ///
299 /// This may return duplicate entities. These will be filtered out in 313 /// This may return duplicate entities. These will be filtered out in
300 /// [ListTree.list]. 314 /// [ListTree.list].
301 Stream<FileSystemEntity> list(String dir, {bool followLinks: true}) { 315 Stream<FileSystemEntity> list(String dir, {bool followLinks: true}) {
302 if (isRecursive) { 316 if (isRecursive) {
303 return new Directory(dir).list(recursive: true, followLinks: followLinks) 317 return new Directory(dir).list(recursive: true, followLinks: followLinks)
304 .where((entity) => _matches(entity.path.substring(dir.length + 1))); 318 .where((entity) => _matches(p.relative(entity.path, from: dir)));
305 } 319 }
306 320
307 var resultPool = new StreamPool(); 321 var resultGroup = new StreamGroup<FileSystemEntity>();
308 322
309 // Don't spawn extra [Directory.list] calls when we already know exactly 323 // Don't spawn extra [Directory.list] calls when we already know exactly
310 // which subdirectories we're interested in. 324 // which subdirectories we're interested in.
311 if (_isIntermediate) { 325 if (_isIntermediate) {
312 children.forEach((sequence, child) { 326 children.forEach((sequence, child) {
313 resultPool.add(child.list(p.join(dir, sequence.nodes.single.text), 327 resultGroup.add(child.list(
328 p.join(dir, (sequence.nodes.single as LiteralNode).text),
314 followLinks: followLinks)); 329 followLinks: followLinks));
315 }); 330 });
316 resultPool.closeWhenEmpty(); 331 resultGroup.close();
317 return resultPool.stream; 332 return resultGroup.stream;
318 } 333 }
319 334
320 var resultController = new StreamController(sync: true); 335 var resultController = new StreamController<FileSystemEntity>(sync: true);
321 resultPool.add(resultController.stream); 336 resultGroup.add(resultController.stream);
322 new Directory(dir).list(followLinks: followLinks).listen((entity) { 337 new Directory(dir).list(followLinks: followLinks).listen((entity) {
323 var basename = entity.path.substring(dir.length + 1); 338 var basename = p.relative(entity.path, from: dir);
324 if (_matches(basename)) resultController.add(entity); 339 if (_matches(basename)) resultController.add(entity);
325 340
326 children.forEach((sequence, child) { 341 children.forEach((sequence, child) {
327 if (entity is! Directory) return; 342 if (entity is! Directory) return;
328 if (!sequence.matches(basename)) return; 343 if (!sequence.matches(basename)) return;
329 var stream = child.list(p.join(dir, basename), followLinks: followLinks) 344 var stream = child.list(p.join(dir, basename), followLinks: followLinks)
330 .handleError((_) {}, test: (error) { 345 .handleError((_) {}, test: (error) {
331 // Ignore errors from directories not existing. We do this here so 346 // Ignore errors from directories not existing. We do this here so
332 // that we only ignore warnings below wild cards. For example, the 347 // that we only ignore warnings below wild cards. For example, the
333 // glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but 348 // glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but
334 // succeed if "foo/bar/qux/baz" doesn't exist. 349 // succeed if "foo/bar/qux/baz" doesn't exist.
335 return error is FileSystemException && 350 return error is FileSystemException &&
336 (error.osError.errorCode == _ENOENT || 351 (error.osError.errorCode == _ENOENT ||
337 error.osError.errorCode == _ENOENT_WIN); 352 error.osError.errorCode == _ENOENT_WIN);
338 }); 353 });
339 resultPool.add(stream); 354 resultGroup.add(stream);
340 }); 355 });
341 }, 356 },
342 onError: resultController.addError, 357 onError: resultController.addError,
343 onDone: resultController.close); 358 onDone: () {
359 resultController.close();
360 resultGroup.close();
361 });
344 362
345 resultPool.closeWhenEmpty(); 363 return resultGroup.stream;
346 return resultPool.stream;
347 } 364 }
348 365
349 /// Synchronously lists all entities within [dir] matching this node or its 366 /// Synchronously lists all entities within [dir] matching this node or its
350 /// children. 367 /// children.
351 /// 368 ///
352 /// This may return duplicate entities. These will be filtered out in 369 /// This may return duplicate entities. These will be filtered out in
353 /// [ListTree.listSync]. 370 /// [ListTree.listSync].
354 Iterable<FileSystemEntity> listSync(String dir, {bool followLinks: true}) { 371 Iterable<FileSystemEntity> listSync(String dir, {bool followLinks: true}) {
355 if (isRecursive) { 372 if (isRecursive) {
356 return new Directory(dir) 373 return new Directory(dir)
357 .listSync(recursive: true, followLinks: followLinks) 374 .listSync(recursive: true, followLinks: followLinks)
358 .where((entity) => _matches(entity.path.substring(dir.length + 1))); 375 .where((entity) => _matches(p.relative(entity.path, from: dir)));
359 } 376 }
360 377
361 // Don't spawn extra [Directory.listSync] calls when we already know exactly 378 // Don't spawn extra [Directory.listSync] calls when we already know exactly
362 // which subdirectories we're interested in. 379 // which subdirectories we're interested in.
363 if (_isIntermediate) { 380 if (_isIntermediate) {
364 return children.keys.expand((sequence) { 381 return children.keys.expand((sequence) {
365 return children[sequence].listSync( 382 return children[sequence].listSync(
366 p.join(dir, sequence.nodes.single.text), followLinks: followLinks); 383 p.join(dir, (sequence.nodes.single as LiteralNode).text),
384 followLinks: followLinks);
367 }); 385 });
368 } 386 }
369 387
370 return new Directory(dir).listSync(followLinks: followLinks) 388 return new Directory(dir).listSync(followLinks: followLinks)
371 .expand((entity) { 389 .expand((entity) {
372 var entities = []; 390 var entities = <FileSystemEntity>[];
373 var basename = entity.path.substring(dir.length + 1); 391 var basename = p.relative(entity.path, from: dir);
374 if (_matches(basename)) entities.add(entity); 392 if (_matches(basename)) entities.add(entity);
375 if (entity is! Directory) return entities; 393 if (entity is! Directory) return entities;
376 394
377 entities.addAll(children.keys 395 entities.addAll(children.keys
378 .where((sequence) => sequence.matches(basename)) 396 .where((sequence) => sequence.matches(basename))
379 .expand((sequence) { 397 .expand((sequence) {
380 try { 398 try {
381 return children[sequence].listSync( 399 return children[sequence].listSync(
382 p.join(dir, basename), followLinks: followLinks).toList(); 400 p.join(dir, basename), followLinks: followLinks).toList();
383 } on FileSystemException catch (error) { 401 } on FileSystemException catch (error) {
(...skipping 20 matching lines...) Expand all
404 return _validator.matches(toPosixPath(p.context, path)); 422 return _validator.matches(toPosixPath(p.context, path));
405 } 423 }
406 424
407 String toString() => "($_validator) $children"; 425 String toString() => "($_validator) $children";
408 } 426 }
409 427
410 /// Joins each [components] into a new glob where each component is separated by 428 /// Joins each [components] into a new glob where each component is separated by
411 /// a path separator. 429 /// a path separator.
412 SequenceNode _join(Iterable<AstNode> components) { 430 SequenceNode _join(Iterable<AstNode> components) {
413 var componentsList = components.toList(); 431 var componentsList = components.toList();
414 var nodes = [componentsList.removeAt(0)]; 432 var first = componentsList.removeAt(0);
433 var nodes = [first];
415 for (var component in componentsList) { 434 for (var component in componentsList) {
416 nodes.add(new LiteralNode('/')); 435 nodes.add(new LiteralNode('/', caseSensitive: first.caseSensitive));
417 nodes.add(component); 436 nodes.add(component);
418 } 437 }
419 return new SequenceNode(nodes); 438 return new SequenceNode(nodes, caseSensitive: first.caseSensitive);
420 } 439 }
OLDNEW
« no previous file with comments | « packages/glob/lib/src/ast.dart ('k') | packages/glob/lib/src/parser.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698