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:io'; | 5 import 'dart:io'; |
6 import 'dart:async'; | 6 import 'dart:async'; |
7 | 7 |
| 8 import 'package:async/async.dart'; |
8 import 'package:path/path.dart' as p; | 9 import 'package:path/path.dart' as p; |
9 | 10 |
10 import 'ast.dart'; | 11 import 'ast.dart'; |
11 import 'stream_pool.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 |
17 /// 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 |
18 /// directory. | 18 /// directory. |
19 const _ENOENT_WIN = 3; | 19 const _ENOENT_WIN = 3; |
20 | 20 |
21 /// 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 Loading... |
90 } | 90 } |
91 } | 91 } |
92 | 92 |
93 _addGlob(root, components); | 93 _addGlob(root, components); |
94 } | 94 } |
95 | 95 |
96 _canOverlap = _computeCanOverlap(); | 96 _canOverlap = _computeCanOverlap(); |
97 } | 97 } |
98 | 98 |
99 /// Add the glob represented by [components] to the tree under [root]. | 99 /// Add the glob represented by [components] to the tree under [root]. |
100 void _addGlob(String root, List<AstNode> components) { | 100 void _addGlob(String root, List<SequenceNode> components) { |
101 // 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 |
102 // 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, |
103 // we'll create it below. | 103 // we'll create it below. |
104 // | 104 // |
105 // As we iterate through [components], [parent] will be set to | 105 // As we iterate through [components], [parent] will be set to |
106 // progressively more nested nodes. | 106 // progressively more nested nodes. |
107 var parent = _trees[root]; | 107 var parent = _trees[root]; |
108 for (var i = 0; i < components.length; i++) { | 108 for (var i = 0; i < components.length; i++) { |
109 var component = components[i]; | 109 var component = components[i]; |
110 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 Loading... |
163 if (_trees.length > 1 && _trees.containsKey('.')) return true; | 163 if (_trees.length > 1 && _trees.containsKey('.')) return true; |
164 | 164 |
165 // 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 |
166 // overlap internally. | 166 // overlap internally. |
167 return _trees.values.any((node) => node.canOverlap); | 167 return _trees.values.any((node) => node.canOverlap); |
168 } | 168 } |
169 | 169 |
170 /// List all entities that match this glob beneath [root]. | 170 /// List all entities that match this glob beneath [root]. |
171 Stream<FileSystemEntity> list({String root, bool followLinks: true}) { | 171 Stream<FileSystemEntity> list({String root, bool followLinks: true}) { |
172 if (root == null) root = '.'; | 172 if (root == null) root = '.'; |
173 var pool = new StreamPool(); | 173 var group = new StreamGroup<FileSystemEntity>(); |
174 for (var rootDir in _trees.keys) { | 174 for (var rootDir in _trees.keys) { |
175 var dir = rootDir == '.' ? root : rootDir; | 175 var dir = rootDir == '.' ? root : rootDir; |
176 pool.add(_trees[rootDir].list(dir, followLinks: followLinks)); | 176 group.add(_trees[rootDir].list(dir, followLinks: followLinks)); |
177 } | 177 } |
178 pool.closeWhenEmpty(); | 178 group.close(); |
179 | 179 |
180 if (!_canOverlap) return pool.stream; | 180 if (!_canOverlap) return group.stream; |
181 | 181 |
182 // TODO(nweiz): Rather than filtering here, avoid double-listing directories | 182 // TODO(nweiz): Rather than filtering here, avoid double-listing directories |
183 // in the first place. | 183 // in the first place. |
184 var seen = new Set(); | 184 var seen = new Set(); |
185 return pool.stream.where((entity) { | 185 return group.stream.where((entity) { |
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 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) { |
197 var dir = rootDir == '.' ? root : rootDir; | 198 var dir = rootDir == '.' ? root : rootDir; |
198 return _trees[rootDir].listSync(dir, followLinks: followLinks); | 199 return _trees[rootDir].listSync(dir, followLinks: followLinks); |
199 }); | 200 }); |
200 | 201 |
201 if (!_canOverlap) return result.toList(); | 202 if (!_canOverlap) return result.toList(); |
202 | 203 |
203 // TODO(nweiz): Rather than filtering here, avoid double-listing directories | 204 // TODO(nweiz): Rather than filtering here, avoid double-listing directories |
204 // in the first place. | 205 // in the first place. |
205 var seen = new Set(); | 206 var seen = new Set<String>(); |
206 return result.where((entity) { | 207 return result.where((entity) { |
207 if (seen.contains(entity.path)) return false; | 208 if (seen.contains(entity.path)) return false; |
208 seen.add(entity.path); | 209 seen.add(entity.path); |
209 return true; | 210 return true; |
210 }).toList(); | 211 }).toList(); |
211 } | 212 } |
212 } | 213 } |
213 | 214 |
214 /// A single node in a [ListTree]. | 215 /// A single node in a [ListTree]. |
215 class _ListTreeNode { | 216 class _ListTreeNode { |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
310 /// Lists all entities within [dir] matching this node or its children. | 311 /// Lists all entities within [dir] matching this node or its children. |
311 /// | 312 /// |
312 /// This may return duplicate entities. These will be filtered out in | 313 /// This may return duplicate entities. These will be filtered out in |
313 /// [ListTree.list]. | 314 /// [ListTree.list]. |
314 Stream<FileSystemEntity> list(String dir, {bool followLinks: true}) { | 315 Stream<FileSystemEntity> list(String dir, {bool followLinks: true}) { |
315 if (isRecursive) { | 316 if (isRecursive) { |
316 return new Directory(dir).list(recursive: true, followLinks: followLinks) | 317 return new Directory(dir).list(recursive: true, followLinks: followLinks) |
317 .where((entity) => _matches(p.relative(entity.path, from: dir))); | 318 .where((entity) => _matches(p.relative(entity.path, from: dir))); |
318 } | 319 } |
319 | 320 |
320 var resultPool = new StreamPool(); | 321 var resultGroup = new StreamGroup<FileSystemEntity>(); |
321 | 322 |
322 // 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 |
323 // which subdirectories we're interested in. | 324 // which subdirectories we're interested in. |
324 if (_isIntermediate) { | 325 if (_isIntermediate) { |
325 children.forEach((sequence, child) { | 326 children.forEach((sequence, child) { |
326 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), |
327 followLinks: followLinks)); | 329 followLinks: followLinks)); |
328 }); | 330 }); |
329 resultPool.closeWhenEmpty(); | 331 resultGroup.close(); |
330 return resultPool.stream; | 332 return resultGroup.stream; |
331 } | 333 } |
332 | 334 |
333 var resultController = new StreamController(sync: true); | 335 var resultController = new StreamController<FileSystemEntity>(sync: true); |
334 resultPool.add(resultController.stream); | 336 resultGroup.add(resultController.stream); |
335 new Directory(dir).list(followLinks: followLinks).listen((entity) { | 337 new Directory(dir).list(followLinks: followLinks).listen((entity) { |
336 var basename = p.relative(entity.path, from: dir); | 338 var basename = p.relative(entity.path, from: dir); |
337 if (_matches(basename)) resultController.add(entity); | 339 if (_matches(basename)) resultController.add(entity); |
338 | 340 |
339 children.forEach((sequence, child) { | 341 children.forEach((sequence, child) { |
340 if (entity is! Directory) return; | 342 if (entity is! Directory) return; |
341 if (!sequence.matches(basename)) return; | 343 if (!sequence.matches(basename)) return; |
342 var stream = child.list(p.join(dir, basename), followLinks: followLinks) | 344 var stream = child.list(p.join(dir, basename), followLinks: followLinks) |
343 .handleError((_) {}, test: (error) { | 345 .handleError((_) {}, test: (error) { |
344 // Ignore errors from directories not existing. We do this here so | 346 // Ignore errors from directories not existing. We do this here so |
345 // that we only ignore warnings below wild cards. For example, the | 347 // that we only ignore warnings below wild cards. For example, the |
346 // 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 |
347 // succeed if "foo/bar/qux/baz" doesn't exist. | 349 // succeed if "foo/bar/qux/baz" doesn't exist. |
348 return error is FileSystemException && | 350 return error is FileSystemException && |
349 (error.osError.errorCode == _ENOENT || | 351 (error.osError.errorCode == _ENOENT || |
350 error.osError.errorCode == _ENOENT_WIN); | 352 error.osError.errorCode == _ENOENT_WIN); |
351 }); | 353 }); |
352 resultPool.add(stream); | 354 resultGroup.add(stream); |
353 }); | 355 }); |
354 }, | 356 }, |
355 onError: resultController.addError, | 357 onError: resultController.addError, |
356 onDone: resultController.close); | 358 onDone: () { |
| 359 resultController.close(); |
| 360 resultGroup.close(); |
| 361 }); |
357 | 362 |
358 resultPool.closeWhenEmpty(); | 363 return resultGroup.stream; |
359 return resultPool.stream; | |
360 } | 364 } |
361 | 365 |
362 /// Synchronously lists all entities within [dir] matching this node or its | 366 /// Synchronously lists all entities within [dir] matching this node or its |
363 /// children. | 367 /// children. |
364 /// | 368 /// |
365 /// This may return duplicate entities. These will be filtered out in | 369 /// This may return duplicate entities. These will be filtered out in |
366 /// [ListTree.listSync]. | 370 /// [ListTree.listSync]. |
367 Iterable<FileSystemEntity> listSync(String dir, {bool followLinks: true}) { | 371 Iterable<FileSystemEntity> listSync(String dir, {bool followLinks: true}) { |
368 if (isRecursive) { | 372 if (isRecursive) { |
369 return new Directory(dir) | 373 return new Directory(dir) |
370 .listSync(recursive: true, followLinks: followLinks) | 374 .listSync(recursive: true, followLinks: followLinks) |
371 .where((entity) => _matches(p.relative(entity.path, from: dir))); | 375 .where((entity) => _matches(p.relative(entity.path, from: dir))); |
372 } | 376 } |
373 | 377 |
374 // 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 |
375 // which subdirectories we're interested in. | 379 // which subdirectories we're interested in. |
376 if (_isIntermediate) { | 380 if (_isIntermediate) { |
377 return children.keys.expand((sequence) { | 381 return children.keys.expand((sequence) { |
378 return children[sequence].listSync( | 382 return children[sequence].listSync( |
379 p.join(dir, sequence.nodes.single.text), followLinks: followLinks); | 383 p.join(dir, (sequence.nodes.single as LiteralNode).text), |
| 384 followLinks: followLinks); |
380 }); | 385 }); |
381 } | 386 } |
382 | 387 |
383 return new Directory(dir).listSync(followLinks: followLinks) | 388 return new Directory(dir).listSync(followLinks: followLinks) |
384 .expand((entity) { | 389 .expand((entity) { |
385 var entities = []; | 390 var entities = <FileSystemEntity>[]; |
386 var basename = p.relative(entity.path, from: dir); | 391 var basename = p.relative(entity.path, from: dir); |
387 if (_matches(basename)) entities.add(entity); | 392 if (_matches(basename)) entities.add(entity); |
388 if (entity is! Directory) return entities; | 393 if (entity is! Directory) return entities; |
389 | 394 |
390 entities.addAll(children.keys | 395 entities.addAll(children.keys |
391 .where((sequence) => sequence.matches(basename)) | 396 .where((sequence) => sequence.matches(basename)) |
392 .expand((sequence) { | 397 .expand((sequence) { |
393 try { | 398 try { |
394 return children[sequence].listSync( | 399 return children[sequence].listSync( |
395 p.join(dir, basename), followLinks: followLinks).toList(); | 400 p.join(dir, basename), followLinks: followLinks).toList(); |
(...skipping 29 matching lines...) Expand all Loading... |
425 SequenceNode _join(Iterable<AstNode> components) { | 430 SequenceNode _join(Iterable<AstNode> components) { |
426 var componentsList = components.toList(); | 431 var componentsList = components.toList(); |
427 var first = componentsList.removeAt(0); | 432 var first = componentsList.removeAt(0); |
428 var nodes = [first]; | 433 var nodes = [first]; |
429 for (var component in componentsList) { | 434 for (var component in componentsList) { |
430 nodes.add(new LiteralNode('/', caseSensitive: first.caseSensitive)); | 435 nodes.add(new LiteralNode('/', caseSensitive: first.caseSensitive)); |
431 nodes.add(component); | 436 nodes.add(component); |
432 } | 437 } |
433 return new SequenceNode(nodes, caseSensitive: first.caseSensitive); | 438 return new SequenceNode(nodes, caseSensitive: first.caseSensitive); |
434 } | 439 } |
OLD | NEW |