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