| 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 | 
|---|