OLD | NEW |
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 import 'dart:async'; |
5 import 'dart:io'; | 6 import 'dart:io'; |
6 import 'dart:async'; | |
7 | 7 |
8 import 'package:async/async.dart'; | 8 import 'package:async/async.dart'; |
9 import 'package:path/path.dart' as p; | 9 import 'package:path/path.dart' as p; |
10 | 10 |
11 import 'ast.dart'; | 11 import 'ast.dart'; |
12 import 'utils.dart'; | 12 import 'utils.dart'; |
13 | 13 |
14 /// 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. |
15 const _ENOENT = 2; | 15 const _ENOENT = 2; |
16 | 16 |
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
136 // On the other hand if there are more components, add [component] | 136 // On the other hand if there are more components, add [component] |
137 // to [parent]'s children and not its validator. Since we process | 137 // to [parent]'s children and not its validator. Since we process |
138 // each option's components separately, the same component is never | 138 // each option's components separately, the same component is never |
139 // both a validator and a child. | 139 // both a validator and a child. |
140 if (!parent.children.containsKey(component)) { | 140 if (!parent.children.containsKey(component)) { |
141 parent.children[component] = new _ListTreeNode(); | 141 parent.children[component] = new _ListTreeNode(); |
142 } | 142 } |
143 parent = parent.children[component]; | 143 parent = parent.children[component]; |
144 } | 144 } |
145 } else if (recursive) { | 145 } else if (recursive) { |
146 _trees[root] = new _ListTreeNode.recursive( | 146 _trees[root] = |
147 _join(components.sublist(i))); | 147 new _ListTreeNode.recursive(_join(components.sublist(i))); |
148 return; | 148 return; |
149 } else if (complete) { | 149 } else if (complete) { |
150 _trees[root] = new _ListTreeNode()..addOption(component); | 150 _trees[root] = new _ListTreeNode()..addOption(component); |
151 } else { | 151 } else { |
152 _trees[root] = new _ListTreeNode(); | 152 _trees[root] = new _ListTreeNode(); |
153 _trees[root].children[component] = new _ListTreeNode(); | 153 _trees[root].children[component] = new _ListTreeNode(); |
154 parent = _trees[root].children[component]; | 154 parent = _trees[root].children[component]; |
155 } | 155 } |
156 } | 156 } |
157 } | 157 } |
(...skipping 28 matching lines...) Expand all Loading... |
186 if (seen.contains(entity.path)) return false; | 186 if (seen.contains(entity.path)) return false; |
187 seen.add(entity.path); | 187 seen.add(entity.path); |
188 return true; | 188 return true; |
189 }); | 189 }); |
190 } | 190 } |
191 | 191 |
192 /// Synchronosuly list all entities that match this glob beneath [root]. | 192 /// Synchronosuly list all entities that match this glob beneath [root]. |
193 List<FileSystemEntity> listSync({String root, bool followLinks: true}) { | 193 List<FileSystemEntity> listSync({String root, bool followLinks: true}) { |
194 if (root == null) root = '.'; | 194 if (root == null) root = '.'; |
195 | 195 |
196 // TODO(nweiz): Remove the explicit annotation when sdk#26139 is fixed. | 196 var result = _trees.keys.expand((rootDir) { |
197 var result = _trees.keys.expand/*<FileSystemEntity>*/((rootDir) { | |
198 var dir = rootDir == '.' ? root : rootDir; | 197 var dir = rootDir == '.' ? root : rootDir; |
199 return _trees[rootDir].listSync(dir, followLinks: followLinks); | 198 return _trees[rootDir].listSync(dir, followLinks: followLinks); |
200 }); | 199 }); |
201 | 200 |
202 if (!_canOverlap) return result.toList(); | 201 if (!_canOverlap) return result.toList(); |
203 | 202 |
204 // TODO(nweiz): Rather than filtering here, avoid double-listing directories | 203 // TODO(nweiz): Rather than filtering here, avoid double-listing directories |
205 // in the first place. | 204 // in the first place. |
206 var seen = new Set<String>(); | 205 var seen = new Set<String>(); |
207 return result.where((entity) { | 206 return result.where((entity) { |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
240 return children.keys.first.caseSensitive; | 239 return children.keys.first.caseSensitive; |
241 } | 240 } |
242 | 241 |
243 /// Whether this node doesn't itself need to be listed. | 242 /// Whether this node doesn't itself need to be listed. |
244 /// | 243 /// |
245 /// If a node has no validator and all of its children are literal filenames, | 244 /// If a node has no validator and all of its children are literal filenames, |
246 /// there's no need to list its contents. We can just directly traverse into | 245 /// there's no need to list its contents. We can just directly traverse into |
247 /// its children. | 246 /// its children. |
248 bool get _isIntermediate { | 247 bool get _isIntermediate { |
249 if (_validator != null) return false; | 248 if (_validator != null) return false; |
250 if (!_caseSensitive) return false; | |
251 return children.keys.every((sequence) => | 249 return children.keys.every((sequence) => |
252 sequence.nodes.length == 1 && sequence.nodes.first is LiteralNode); | 250 sequence.nodes.length == 1 && sequence.nodes.first is LiteralNode); |
253 } | 251 } |
254 | 252 |
255 /// Returns whether listing this node might return overlapping results. | 253 /// Returns whether listing this node might return overlapping results. |
256 bool get canOverlap { | 254 bool get canOverlap { |
257 // A recusive node can never overlap with itself, because it will only ever | 255 // A recusive node can never overlap with itself, because it will only ever |
258 // involve a single call to [Directory.list] that's then filtered with | 256 // involve a single call to [Directory.list] that's then filtered with |
259 // [_validator]. | 257 // [_validator]. |
260 if (isRecursive) return false; | 258 if (isRecursive) return false; |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
294 var child = children[sequence]; | 292 var child = children[sequence]; |
295 child.makeRecursive(); | 293 child.makeRecursive(); |
296 return _join([sequence, child._validator]); | 294 return _join([sequence, child._validator]); |
297 }), caseSensitive: _caseSensitive); | 295 }), caseSensitive: _caseSensitive); |
298 children = null; | 296 children = null; |
299 } | 297 } |
300 | 298 |
301 /// Adds [validator] to this node's existing validator. | 299 /// Adds [validator] to this node's existing validator. |
302 void addOption(SequenceNode validator) { | 300 void addOption(SequenceNode validator) { |
303 if (_validator == null) { | 301 if (_validator == null) { |
304 _validator = new OptionsNode([validator], | 302 _validator = |
305 caseSensitive: validator.caseSensitive); | 303 new OptionsNode([validator], caseSensitive: validator.caseSensitive); |
306 } else { | 304 } else { |
307 _validator.options.add(validator); | 305 _validator.options.add(validator); |
308 } | 306 } |
309 } | 307 } |
310 | 308 |
311 /// Lists all entities within [dir] matching this node or its children. | 309 /// Lists all entities within [dir] matching this node or its children. |
312 /// | 310 /// |
313 /// This may return duplicate entities. These will be filtered out in | 311 /// This may return duplicate entities. These will be filtered out in |
314 /// [ListTree.list]. | 312 /// [ListTree.list]. |
315 Stream<FileSystemEntity> list(String dir, {bool followLinks: true}) { | 313 Stream<FileSystemEntity> list(String dir, {bool followLinks: true}) { |
316 if (isRecursive) { | 314 if (isRecursive) { |
317 return new Directory(dir).list(recursive: true, followLinks: followLinks) | 315 return new Directory(dir) |
| 316 .list(recursive: true, followLinks: followLinks) |
318 .where((entity) => _matches(p.relative(entity.path, from: dir))); | 317 .where((entity) => _matches(p.relative(entity.path, from: dir))); |
319 } | 318 } |
320 | 319 |
321 var resultGroup = new StreamGroup<FileSystemEntity>(); | |
322 | |
323 // Don't spawn extra [Directory.list] calls when we already know exactly | 320 // Don't spawn extra [Directory.list] calls when we already know exactly |
324 // which subdirectories we're interested in. | 321 // which subdirectories we're interested in. |
325 if (_isIntermediate) { | 322 if (_isIntermediate && _caseSensitive) { |
| 323 var resultGroup = new StreamGroup<FileSystemEntity>(); |
326 children.forEach((sequence, child) { | 324 children.forEach((sequence, child) { |
327 resultGroup.add(child.list( | 325 resultGroup.add(child.list( |
328 p.join(dir, (sequence.nodes.single as LiteralNode).text), | 326 p.join(dir, (sequence.nodes.single as LiteralNode).text), |
329 followLinks: followLinks)); | 327 followLinks: followLinks)); |
330 }); | 328 }); |
331 resultGroup.close(); | 329 resultGroup.close(); |
332 return resultGroup.stream; | 330 return resultGroup.stream; |
333 } | 331 } |
334 | 332 |
335 var resultController = new StreamController<FileSystemEntity>(sync: true); | 333 return StreamCompleter.fromFuture(() async { |
336 resultGroup.add(resultController.stream); | 334 var entities = |
337 new Directory(dir).list(followLinks: followLinks).listen((entity) { | 335 await new Directory(dir).list(followLinks: followLinks).toList(); |
338 var basename = p.relative(entity.path, from: dir); | 336 await _validateIntermediateChildrenAsync(dir, entities); |
339 if (_matches(basename)) resultController.add(entity); | |
340 | 337 |
341 children.forEach((sequence, child) { | 338 var resultGroup = new StreamGroup<FileSystemEntity>(); |
342 if (entity is! Directory) return; | 339 var resultController = new StreamController<FileSystemEntity>(sync: true); |
343 if (!sequence.matches(basename)) return; | 340 resultGroup.add(resultController.stream); |
344 var stream = child.list(p.join(dir, basename), followLinks: followLinks) | 341 for (var entity in entities) { |
345 .handleError((_) {}, test: (error) { | 342 var basename = p.relative(entity.path, from: dir); |
346 // Ignore errors from directories not existing. We do this here so | 343 if (_matches(basename)) resultController.add(entity); |
347 // that we only ignore warnings below wild cards. For example, the | 344 |
348 // glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but | 345 children.forEach((sequence, child) { |
349 // succeed if "foo/bar/qux/baz" doesn't exist. | 346 if (entity is! Directory) return; |
350 return error is FileSystemException && | 347 if (!sequence.matches(basename)) return; |
351 (error.osError.errorCode == _ENOENT || | 348 var stream = child |
352 error.osError.errorCode == _ENOENT_WIN); | 349 .list(p.join(dir, basename), followLinks: followLinks) |
| 350 .handleError((_) {}, test: (error) { |
| 351 // Ignore errors from directories not existing. We do this here so |
| 352 // that we only ignore warnings below wild cards. For example, the |
| 353 // glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but |
| 354 // succeed if "foo/bar/qux/baz" doesn't exist. |
| 355 return error is FileSystemException && |
| 356 (error.osError.errorCode == _ENOENT || |
| 357 error.osError.errorCode == _ENOENT_WIN); |
| 358 }); |
| 359 resultGroup.add(stream); |
353 }); | 360 }); |
354 resultGroup.add(stream); | 361 } |
355 }); | 362 resultController.close(); |
356 }, | 363 resultGroup.close(); |
357 onError: resultController.addError, | 364 return resultGroup.stream; |
358 onDone: () { | 365 }()); |
359 resultController.close(); | 366 } |
360 resultGroup.close(); | |
361 }); | |
362 | 367 |
363 return resultGroup.stream; | 368 /// If this is a case-insensitive list, validates that all intermediate |
| 369 /// children (according to [_isIntermediate]) match at least one entity in |
| 370 /// [entities]. |
| 371 /// |
| 372 /// This ensures that listing "foo/bar/*" fails on case-sensitive systems if |
| 373 /// "foo/bar" doesn't exist. |
| 374 Future _validateIntermediateChildrenAsync( |
| 375 String dir, List<FileSystemEntity> entities) async { |
| 376 if (_caseSensitive) return; |
| 377 |
| 378 for (var sequence in children.keys) { |
| 379 var child = children[sequence]; |
| 380 if (!child._isIntermediate) continue; |
| 381 if (entities.any( |
| 382 (entity) => sequence.matches(p.relative(entity.path, from: dir)))) { |
| 383 continue; |
| 384 } |
| 385 |
| 386 // We know this will fail, we're just doing it to force dart:io to emit |
| 387 // the exception it would if we were listing case-sensitively. |
| 388 await child |
| 389 .list(p.join(dir, (sequence.nodes.single as LiteralNode).text)) |
| 390 .toList(); |
| 391 } |
364 } | 392 } |
365 | 393 |
366 /// Synchronously lists all entities within [dir] matching this node or its | 394 /// Synchronously lists all entities within [dir] matching this node or its |
367 /// children. | 395 /// children. |
368 /// | 396 /// |
369 /// This may return duplicate entities. These will be filtered out in | 397 /// This may return duplicate entities. These will be filtered out in |
370 /// [ListTree.listSync]. | 398 /// [ListTree.listSync]. |
371 Iterable<FileSystemEntity> listSync(String dir, {bool followLinks: true}) { | 399 Iterable<FileSystemEntity> listSync(String dir, {bool followLinks: true}) { |
372 if (isRecursive) { | 400 if (isRecursive) { |
373 return new Directory(dir) | 401 return new Directory(dir) |
374 .listSync(recursive: true, followLinks: followLinks) | 402 .listSync(recursive: true, followLinks: followLinks) |
375 .where((entity) => _matches(p.relative(entity.path, from: dir))); | 403 .where((entity) => _matches(p.relative(entity.path, from: dir))); |
376 } | 404 } |
377 | 405 |
378 // Don't spawn extra [Directory.listSync] calls when we already know exactly | 406 // Don't spawn extra [Directory.listSync] calls when we already know exactly |
379 // which subdirectories we're interested in. | 407 // which subdirectories we're interested in. |
380 if (_isIntermediate) { | 408 if (_isIntermediate && _caseSensitive) { |
381 return children.keys.expand((sequence) { | 409 return children.keys.expand((sequence) { |
382 return children[sequence].listSync( | 410 return children[sequence].listSync( |
383 p.join(dir, (sequence.nodes.single as LiteralNode).text), | 411 p.join(dir, (sequence.nodes.single as LiteralNode).text), |
384 followLinks: followLinks); | 412 followLinks: followLinks); |
385 }); | 413 }); |
386 } | 414 } |
387 | 415 |
388 return new Directory(dir).listSync(followLinks: followLinks) | 416 var entities = new Directory(dir).listSync(followLinks: followLinks); |
389 .expand((entity) { | 417 _validateIntermediateChildrenSync(dir, entities); |
| 418 |
| 419 return entities.expand((entity) { |
390 var entities = <FileSystemEntity>[]; | 420 var entities = <FileSystemEntity>[]; |
391 var basename = p.relative(entity.path, from: dir); | 421 var basename = p.relative(entity.path, from: dir); |
392 if (_matches(basename)) entities.add(entity); | 422 if (_matches(basename)) entities.add(entity); |
393 if (entity is! Directory) return entities; | 423 if (entity is! Directory) return entities; |
394 | 424 |
395 entities.addAll(children.keys | 425 entities.addAll(children.keys |
396 .where((sequence) => sequence.matches(basename)) | 426 .where((sequence) => sequence.matches(basename)) |
397 .expand((sequence) { | 427 .expand((sequence) { |
398 try { | 428 try { |
399 return children[sequence].listSync( | 429 return children[sequence] |
400 p.join(dir, basename), followLinks: followLinks).toList(); | 430 .listSync(p.join(dir, basename), followLinks: followLinks) |
| 431 .toList(); |
401 } on FileSystemException catch (error) { | 432 } on FileSystemException catch (error) { |
402 // Ignore errors from directories not existing. We do this here so | 433 // Ignore errors from directories not existing. We do this here so |
403 // that we only ignore warnings below wild cards. For example, the | 434 // that we only ignore warnings below wild cards. For example, the |
404 // glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but | 435 // glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but |
405 // succeed if "foo/bar/qux/baz" doesn't exist. | 436 // succeed if "foo/bar/qux/baz" doesn't exist. |
406 if (error.osError.errorCode == _ENOENT || | 437 if (error.osError.errorCode == _ENOENT || |
407 error.osError.errorCode == _ENOENT_WIN) { | 438 error.osError.errorCode == _ENOENT_WIN) { |
408 return const []; | 439 return const []; |
409 } else { | 440 } else { |
410 rethrow; | 441 rethrow; |
411 } | 442 } |
412 } | 443 } |
413 })); | 444 })); |
414 | 445 |
415 return entities; | 446 return entities; |
416 }); | 447 }); |
417 } | 448 } |
418 | 449 |
| 450 /// If this is a case-insensitive list, validates that all intermediate |
| 451 /// children (according to [_isIntermediate]) match at least one entity in |
| 452 /// [entities]. |
| 453 /// |
| 454 /// This ensures that listing "foo/bar/*" fails on case-sensitive systems if |
| 455 /// "foo/bar" doesn't exist. |
| 456 void _validateIntermediateChildrenSync( |
| 457 String dir, List<FileSystemEntity> entities) { |
| 458 if (_caseSensitive) return; |
| 459 |
| 460 children.forEach((sequence, child) { |
| 461 if (!child._isIntermediate) return; |
| 462 if (entities.any( |
| 463 (entity) => sequence.matches(p.relative(entity.path, from: dir)))) { |
| 464 return; |
| 465 } |
| 466 |
| 467 // If there are no [entities] that match [sequence], manually list the |
| 468 // directory to force `dart:io` to throw an error. This allows us to |
| 469 // ensure that listing "foo/bar/*" fails on case-sensitive systems if |
| 470 // "foo/bar" doesn't exist. |
| 471 child.listSync(p.join(dir, (sequence.nodes.single as LiteralNode).text)); |
| 472 }); |
| 473 } |
| 474 |
419 /// Returns whether the native [path] matches [_validator]. | 475 /// Returns whether the native [path] matches [_validator]. |
420 bool _matches(String path) { | 476 bool _matches(String path) { |
421 if (_validator == null) return false; | 477 if (_validator == null) return false; |
422 return _validator.matches(toPosixPath(p.context, path)); | 478 return _validator.matches(toPosixPath(p.context, path)); |
423 } | 479 } |
424 | 480 |
425 String toString() => "($_validator) $children"; | 481 String toString() => "($_validator) $children"; |
426 } | 482 } |
427 | 483 |
428 /// Joins each [components] into a new glob where each component is separated by | 484 /// Joins each [components] into a new glob where each component is separated by |
429 /// a path separator. | 485 /// a path separator. |
430 SequenceNode _join(Iterable<AstNode> components) { | 486 SequenceNode _join(Iterable<AstNode> components) { |
431 var componentsList = components.toList(); | 487 var componentsList = components.toList(); |
432 var first = componentsList.removeAt(0); | 488 var first = componentsList.removeAt(0); |
433 var nodes = [first]; | 489 var nodes = [first]; |
434 for (var component in componentsList) { | 490 for (var component in componentsList) { |
435 nodes.add(new LiteralNode('/', caseSensitive: first.caseSensitive)); | 491 nodes.add(new LiteralNode('/', caseSensitive: first.caseSensitive)); |
436 nodes.add(component); | 492 nodes.add(component); |
437 } | 493 } |
438 return new SequenceNode(nodes, caseSensitive: first.caseSensitive); | 494 return new SequenceNode(nodes, caseSensitive: first.caseSensitive); |
439 } | 495 } |
OLD | NEW |