OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 path.context; | 5 library path.context; |
6 | 6 |
| 7 import 'characters.dart' as chars; |
7 import 'internal_style.dart'; | 8 import 'internal_style.dart'; |
8 import 'style.dart'; | 9 import 'style.dart'; |
9 import 'parsed_path.dart'; | 10 import 'parsed_path.dart'; |
10 import 'path_exception.dart'; | 11 import 'path_exception.dart'; |
11 import '../path.dart' as p; | 12 import '../path.dart' as p; |
12 | 13 |
13 Context createInternal() => new Context._internal(); | 14 Context createInternal() => new Context._internal(); |
14 | 15 |
15 /// An instantiable class for manipulating paths. Unlike the top-level | 16 /// An instantiable class for manipulating paths. Unlike the top-level |
16 /// functions, this lets you explicitly select what platform the paths will use. | 17 /// functions, this lets you explicitly select what platform the paths will use. |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
66 | 67 |
67 /// Creates a new path by appending the given path parts to [current]. | 68 /// Creates a new path by appending the given path parts to [current]. |
68 /// Equivalent to [join()] with [current] as the first argument. Example: | 69 /// Equivalent to [join()] with [current] as the first argument. Example: |
69 /// | 70 /// |
70 /// var context = new Context(current: '/root'); | 71 /// var context = new Context(current: '/root'); |
71 /// context.absolute('path', 'to', 'foo'); // -> '/root/path/to/foo' | 72 /// context.absolute('path', 'to', 'foo'); // -> '/root/path/to/foo' |
72 /// | 73 /// |
73 /// If [current] isn't absolute, this won't return an absolute path. | 74 /// If [current] isn't absolute, this won't return an absolute path. |
74 String absolute(String part1, [String part2, String part3, String part4, | 75 String absolute(String part1, [String part2, String part3, String part4, |
75 String part5, String part6, String part7]) { | 76 String part5, String part6, String part7]) { |
| 77 _validateArgList( |
| 78 "absolute", [part1, part2, part3, part4, part5, part6, part7]); |
| 79 |
| 80 // If there's a single absolute path, just return it. This is a lot faster |
| 81 // for the common case of `p.absolute(path)`. |
| 82 if (part2 == null && isAbsolute(part1) && !isRootRelative(part1)) { |
| 83 return part1; |
| 84 } |
| 85 |
76 return join(current, part1, part2, part3, part4, part5, part6, part7); | 86 return join(current, part1, part2, part3, part4, part5, part6, part7); |
77 } | 87 } |
78 | 88 |
79 /// Gets the part of [path] after the last separator on the context's | 89 /// Gets the part of [path] after the last separator on the context's |
80 /// platform. | 90 /// platform. |
81 /// | 91 /// |
82 /// context.basename('path/to/foo.dart'); // -> 'foo.dart' | 92 /// context.basename('path/to/foo.dart'); // -> 'foo.dart' |
83 /// context.basename('path/to'); // -> 'to' | 93 /// context.basename('path/to'); // -> 'to' |
84 /// | 94 /// |
85 /// Trailing separators are ignored. | 95 /// Trailing separators are ignored. |
(...skipping 202 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
288 parsed.parts = parsed.parts.where((part) => !part.isEmpty).toList(); | 298 parsed.parts = parsed.parts.where((part) => !part.isEmpty).toList(); |
289 if (parsed.root != null) parsed.parts.insert(0, parsed.root); | 299 if (parsed.root != null) parsed.parts.insert(0, parsed.root); |
290 return parsed.parts; | 300 return parsed.parts; |
291 } | 301 } |
292 | 302 |
293 /// Normalizes [path], simplifying it by handling `..`, and `.`, and | 303 /// Normalizes [path], simplifying it by handling `..`, and `.`, and |
294 /// removing redundant path separators whenever possible. | 304 /// removing redundant path separators whenever possible. |
295 /// | 305 /// |
296 /// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt' | 306 /// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt' |
297 String normalize(String path) { | 307 String normalize(String path) { |
| 308 if (!_needsNormalization(path)) return path; |
| 309 |
298 var parsed = _parse(path); | 310 var parsed = _parse(path); |
299 parsed.normalize(); | 311 parsed.normalize(); |
300 return parsed.toString(); | 312 return parsed.toString(); |
301 } | 313 } |
302 | 314 |
| 315 /// Returns whether [path] needs to be normalized. |
| 316 bool _needsNormalization(String path) { |
| 317 var start = 0; |
| 318 var codeUnits = path.codeUnits; |
| 319 var previousPrevious; |
| 320 var previous; |
| 321 |
| 322 // Skip past the root before we start looking for snippets that need |
| 323 // normalization. We want to normalize "//", but not when it's part of |
| 324 // "http://". |
| 325 var root = style.rootLength(path); |
| 326 if (root != 0) { |
| 327 start = root; |
| 328 previous = chars.SLASH; |
| 329 |
| 330 // On Windows, the root still needs to be normalized if it contains a |
| 331 // forward slash. |
| 332 if (style == Style.windows) { |
| 333 for (var i = 0; i < root; i++) { |
| 334 if (codeUnits[i] == chars.SLASH) return true; |
| 335 } |
| 336 } |
| 337 } |
| 338 |
| 339 for (var i = start; i < codeUnits.length; i++) { |
| 340 var codeUnit = codeUnits[i]; |
| 341 if (style.isSeparator(codeUnit)) { |
| 342 // Forward slashes in Windows paths are normalized to backslashes. |
| 343 if (style == Style.windows && codeUnit == chars.SLASH) return true; |
| 344 |
| 345 // Multiple separators are normalized to single separators. |
| 346 if (previous != null && style.isSeparator(previous)) return true; |
| 347 |
| 348 // Single dots and double dots are normalized to directory traversals. |
| 349 // |
| 350 // This can return false positives for ".../", but that's unlikely |
| 351 // enough that it's probably not going to cause performance issues. |
| 352 if (previous == chars.PERIOD && |
| 353 (previousPrevious == null || |
| 354 previousPrevious == chars.PERIOD || |
| 355 style.isSeparator(previousPrevious))) { |
| 356 return true; |
| 357 } |
| 358 } |
| 359 |
| 360 previousPrevious = previous; |
| 361 previous = codeUnit; |
| 362 } |
| 363 |
| 364 // Empty paths are normalized to ".". |
| 365 if (previous == null) return true; |
| 366 |
| 367 // Trailing separators are removed. |
| 368 if (style.isSeparator(previous)) return true; |
| 369 |
| 370 // Single dots and double dots are normalized to directory traversals. |
| 371 if (previous == chars.PERIOD && |
| 372 (previousPrevious == null || |
| 373 previousPrevious == chars.SLASH || |
| 374 previousPrevious == chars.PERIOD)) { |
| 375 return true; |
| 376 } |
| 377 |
| 378 return false; |
| 379 } |
| 380 |
303 /// Attempts to convert [path] to an equivalent relative path relative to | 381 /// Attempts to convert [path] to an equivalent relative path relative to |
304 /// [root]. | 382 /// [root]. |
305 /// | 383 /// |
306 /// var context = new Context(current: '/root/path'); | 384 /// var context = new Context(current: '/root/path'); |
307 /// context.relative('/root/path/a/b.dart'); // -> 'a/b.dart' | 385 /// context.relative('/root/path/a/b.dart'); // -> 'a/b.dart' |
308 /// context.relative('/root/other.dart'); // -> '../other.dart' | 386 /// context.relative('/root/other.dart'); // -> '../other.dart' |
309 /// | 387 /// |
310 /// If the [from] argument is passed, [path] is made relative to that instead. | 388 /// If the [from] argument is passed, [path] is made relative to that instead. |
311 /// | 389 /// |
312 /// context.relative('/root/path/a/b.dart', | 390 /// context.relative('/root/path/a/b.dart', |
(...skipping 13 matching lines...) Expand all Loading... |
326 /// a context with a relative path for [current]. | 404 /// a context with a relative path for [current]. |
327 /// | 405 /// |
328 /// var context = new Context(r'some/relative/path'); | 406 /// var context = new Context(r'some/relative/path'); |
329 /// context.relative(r'/absolute/path'); // -> '/absolute/path' | 407 /// context.relative(r'/absolute/path'); // -> '/absolute/path' |
330 /// | 408 /// |
331 /// If [root] is relative, it may be impossible to determine a path from | 409 /// If [root] is relative, it may be impossible to determine a path from |
332 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is | 410 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is |
333 /// "/", no path can be determined. In this case, a [PathException] will be | 411 /// "/", no path can be determined. In this case, a [PathException] will be |
334 /// thrown. | 412 /// thrown. |
335 String relative(String path, {String from}) { | 413 String relative(String path, {String from}) { |
336 // Avoid calling [current] since it is slow and calling join() when | 414 // Avoid expensive computation if the path is already relative. |
337 // [from] is absolute does nothing. | 415 if (from == null && this.isRelative(path)) return this.normalize(path); |
338 if (from == null) { | 416 |
339 from = current; | 417 from = from == null ? current : absolute(from); |
340 } else if (this.isRelative(from) || this.isRootRelative(from)) { | |
341 from = this.join(current, from); | |
342 } | |
343 | 418 |
344 // We can't determine the path from a relative path to an absolute path. | 419 // We can't determine the path from a relative path to an absolute path. |
345 if (this.isRelative(from) && this.isAbsolute(path)) { | 420 if (this.isRelative(from) && this.isAbsolute(path)) { |
346 return this.normalize(path); | 421 return this.normalize(path); |
347 } | 422 } |
348 | 423 |
349 // If the given path is relative, resolve it relative to the context's | 424 // If the given path is relative, resolve it relative to the context's |
350 // current directory. | 425 // current directory. |
351 if (this.isRelative(path) || this.isRootRelative(path)) { | 426 if (this.isRelative(path) || this.isRootRelative(path)) { |
352 path = this.absolute(path); | 427 path = this.absolute(path); |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
418 return pathParsed.toString(); | 493 return pathParsed.toString(); |
419 } | 494 } |
420 | 495 |
421 /// Returns `true` if [child] is a path beneath `parent`, and `false` | 496 /// Returns `true` if [child] is a path beneath `parent`, and `false` |
422 /// otherwise. | 497 /// otherwise. |
423 /// | 498 /// |
424 /// path.isWithin('/root/path', '/root/path/a'); // -> true | 499 /// path.isWithin('/root/path', '/root/path/a'); // -> true |
425 /// path.isWithin('/root/path', '/root/other'); // -> false | 500 /// path.isWithin('/root/path', '/root/other'); // -> false |
426 /// path.isWithin('/root/path', '/root/path'); // -> false | 501 /// path.isWithin('/root/path', '/root/path'); // -> false |
427 bool isWithin(String parent, String child) { | 502 bool isWithin(String parent, String child) { |
| 503 // Make both paths the same level of relative. We're only able to do the |
| 504 // quick comparison if both paths are in the same format, and making a path |
| 505 // absolute is faster than making it relative. |
| 506 var parentIsAbsolute = isAbsolute(parent); |
| 507 var childIsAbsolute = isAbsolute(child); |
| 508 if (parentIsAbsolute && !childIsAbsolute) { |
| 509 child = absolute(child); |
| 510 if (style.isRootRelative(parent)) parent = absolute(parent); |
| 511 } else if (childIsAbsolute && !parentIsAbsolute) { |
| 512 parent = absolute(parent); |
| 513 if (style.isRootRelative(child)) child = absolute(child); |
| 514 } else if (childIsAbsolute && parentIsAbsolute) { |
| 515 var childIsRootRelative = style.isRootRelative(child); |
| 516 var parentIsRootRelative = style.isRootRelative(parent); |
| 517 |
| 518 if (childIsRootRelative && !parentIsRootRelative) { |
| 519 child = absolute(child); |
| 520 } else if (parentIsRootRelative && !childIsRootRelative) { |
| 521 parent = absolute(parent); |
| 522 } |
| 523 } |
| 524 |
| 525 var fastResult = _isWithinFast(parent, child); |
| 526 if (fastResult != null) return fastResult; |
| 527 |
428 var relative; | 528 var relative; |
429 try { | 529 try { |
430 relative = this.relative(child, from: parent); | 530 relative = this.relative(child, from: parent); |
431 } on PathException catch (_) { | 531 } on PathException catch (_) { |
432 // If no relative path from [parent] to [child] is found, [child] | 532 // If no relative path from [parent] to [child] is found, [child] |
433 // definitely isn't a child of [parent]. | 533 // definitely isn't a child of [parent]. |
434 return false; | 534 return false; |
435 } | 535 } |
436 | 536 |
437 var parts = this.split(relative); | 537 var parts = this.split(relative); |
438 return this.isRelative(relative) && | 538 return this.isRelative(relative) && |
439 parts.first != '..' && | 539 parts.first != '..' && |
440 parts.first != '.'; | 540 parts.first != '.'; |
441 } | 541 } |
442 | 542 |
| 543 /// An optimized implementation of [isWithin] that doesn't handle a few |
| 544 /// complex cases. |
| 545 bool _isWithinFast(String parent, String child) { |
| 546 // Normally we just bail when we see "." path components, but we can handle |
| 547 // a single dot easily enough. |
| 548 if (parent == '.') parent = ''; |
| 549 |
| 550 var parentRootLength = style.rootLength(parent); |
| 551 var childRootLength = style.rootLength(child); |
| 552 |
| 553 // If the roots aren't the same length, we know both paths are absolute or |
| 554 // both are root-relative, and thus that the roots are meaningfully |
| 555 // different. |
| 556 // |
| 557 // isWithin("C:/bar", "//foo/bar/baz") //=> false |
| 558 // isWithin("http://example.com/", "http://google.com/bar") //=> false |
| 559 if (parentRootLength != childRootLength) return false; |
| 560 |
| 561 var parentCodeUnits = parent.codeUnits; |
| 562 var childCodeUnits = child.codeUnits; |
| 563 |
| 564 // Make sure that the roots are textually the same as well. |
| 565 // |
| 566 // isWithin("C:/bar", "D:/bar/baz") //=> false |
| 567 // isWithin("http://example.com/", "http://example.org/bar") //=> false |
| 568 for (var i = 0; i < parentRootLength; i++) { |
| 569 var parentCodeUnit = parentCodeUnits[i]; |
| 570 var childCodeUnit = childCodeUnits[i]; |
| 571 if (parentCodeUnit == childCodeUnit) continue; |
| 572 |
| 573 // If both code units are separators, that's fine too. |
| 574 // |
| 575 // isWithin("C:/", r"C:\foo") //=> true |
| 576 if (!style.isSeparator(parentCodeUnit) || |
| 577 !style.isSeparator(childCodeUnit)) { |
| 578 return false; |
| 579 } |
| 580 } |
| 581 |
| 582 // Start by considering the last code unit as a separator, since |
| 583 // semantically we're starting at a new path component even if we're |
| 584 // comparing relative paths. |
| 585 var lastCodeUnit = chars.SLASH; |
| 586 |
| 587 // Iterate through both paths as long as they're semantically identical. |
| 588 var parentIndex = parentRootLength; |
| 589 var childIndex = childRootLength; |
| 590 while (parentIndex < parent.length && childIndex < child.length) { |
| 591 var parentCodeUnit = parentCodeUnits[parentIndex]; |
| 592 var childCodeUnit = childCodeUnits[childIndex]; |
| 593 if (parentCodeUnit == childCodeUnit) { |
| 594 lastCodeUnit = parentCodeUnit; |
| 595 parentIndex++; |
| 596 childIndex++; |
| 597 continue; |
| 598 } |
| 599 |
| 600 // Different separators are considered identical. |
| 601 var parentIsSeparator = style.isSeparator(parentCodeUnit); |
| 602 var childIsSeparator = style.isSeparator(childCodeUnit); |
| 603 if (parentIsSeparator && childIsSeparator) { |
| 604 lastCodeUnit = parentCodeUnit; |
| 605 parentIndex++; |
| 606 childIndex++; |
| 607 continue; |
| 608 } |
| 609 |
| 610 // Ignore multiple separators in a row. |
| 611 if (parentIsSeparator && style.isSeparator(lastCodeUnit)) { |
| 612 parentIndex++; |
| 613 continue; |
| 614 } else if (childIsSeparator && style.isSeparator(lastCodeUnit)) { |
| 615 childIndex++; |
| 616 continue; |
| 617 } |
| 618 |
| 619 if (parentCodeUnit == chars.PERIOD) { |
| 620 // If a dot comes after a separator, it may be a directory traversal |
| 621 // operator. To check that, we need to know if it's followed by either |
| 622 // "/" or "./". Otherwise, it's just a normal non-matching character. |
| 623 // |
| 624 // isWithin("foo/./bar", "foo/bar/baz") //=> true |
| 625 // isWithin("foo/bar/../baz", "foo/bar/.foo") //=> false |
| 626 if (style.isSeparator(lastCodeUnit)) { |
| 627 parentIndex++; |
| 628 |
| 629 // We've hit "/." at the end of the parent path, which we can ignore, |
| 630 // since the paths were equivalent up to this point. |
| 631 if (parentIndex == parent.length) break; |
| 632 parentCodeUnit = parentCodeUnits[parentIndex]; |
| 633 |
| 634 // We've hit "/./", which we can ignore. |
| 635 if (style.isSeparator(parentCodeUnit)) { |
| 636 parentIndex++; |
| 637 continue; |
| 638 } |
| 639 |
| 640 // We've hit "/..", which may be a directory traversal operator that |
| 641 // we can't handle on the fast track. |
| 642 if (parentCodeUnit == chars.PERIOD) { |
| 643 parentIndex++; |
| 644 if (parentIndex == parent.length || |
| 645 style.isSeparator(parentCodeUnits[parentIndex])) { |
| 646 return null; |
| 647 } |
| 648 } |
| 649 } |
| 650 |
| 651 // If this isn't a directory traversal, fall through so we hit the |
| 652 // normal handling for mismatched paths. |
| 653 } |
| 654 |
| 655 // This is the same logic as above, but for the child path instead of the |
| 656 // parent. |
| 657 if (childCodeUnit == chars.PERIOD) { |
| 658 if (style.isSeparator(lastCodeUnit)) { |
| 659 childIndex++; |
| 660 if (childIndex == child.length) break; |
| 661 childCodeUnit = childCodeUnits[childIndex]; |
| 662 |
| 663 if (style.isSeparator(childCodeUnit)) { |
| 664 childIndex++; |
| 665 continue; |
| 666 } |
| 667 |
| 668 if (childCodeUnit == chars.PERIOD) { |
| 669 childIndex++; |
| 670 if (childIndex == child.length || |
| 671 style.isSeparator(childCodeUnits[childIndex])) { |
| 672 return null; |
| 673 } |
| 674 } |
| 675 } |
| 676 } |
| 677 |
| 678 // If we're here, we've hit two non-matching, non-significant characters. |
| 679 // As long as the remainders of the two paths don't have any unresolved |
| 680 // ".." components, we can be confident that [child] is not within |
| 681 // [parent]. |
| 682 var childDirection = _pathDirection(childCodeUnits, childIndex); |
| 683 if (childDirection != _PathDirection.belowRoot) return null; |
| 684 var parentDirection = _pathDirection(parentCodeUnits, parentIndex); |
| 685 if (parentDirection != _PathDirection.belowRoot) return null; |
| 686 |
| 687 return false; |
| 688 } |
| 689 |
| 690 // If the child is shorter than the parent, it's probably not within the |
| 691 // parent. The only exception is if the parent has some weird ".." stuff |
| 692 // going on, in which case we do the slow check. |
| 693 // |
| 694 // isWithin("foo/bar/baz", "foo/bar") //=> false |
| 695 // isWithin("foo/bar/baz/../..", "foo/bar") //=> true |
| 696 if (childIndex == child.length) { |
| 697 var direction = _pathDirection(parentCodeUnits, parentIndex); |
| 698 return direction == _PathDirection.aboveRoot ? null : false; |
| 699 } |
| 700 |
| 701 // We've reached the end of the parent path, which means it's time to make a |
| 702 // decision. Before we do, though, we'll check the rest of the child to see |
| 703 // what that tells us. |
| 704 var direction = _pathDirection(childCodeUnits, childIndex); |
| 705 |
| 706 // If there are no more components in the child, then it's the same as |
| 707 // the parent, not within it. |
| 708 // |
| 709 // isWithin("foo/bar", "foo/bar") //=> false |
| 710 // isWithin("foo/bar", "foo/bar//") //=> false |
| 711 if (direction == _PathDirection.atRoot) return false; |
| 712 |
| 713 // If there are unresolved ".." components in the child, no decision we make |
| 714 // will be valid. We'll abort and do the slow check instead. |
| 715 // |
| 716 // isWithin("foo/bar", "foo/bar/..") //=> false |
| 717 // isWithin("foo/bar", "foo/bar/baz/bang/../../..") //=> false |
| 718 // isWithin("foo/bar", "foo/bar/baz/bang/../../../bar/baz") //=> true |
| 719 if (direction == _PathDirection.aboveRoot) return null; |
| 720 |
| 721 // The child is within the parent if and only if we're on a separator |
| 722 // boundary. |
| 723 // |
| 724 // isWithin("foo/bar", "foo/bar/baz") //=> true |
| 725 // isWithin("foo/bar/", "foo/bar/baz") //=> true |
| 726 // isWithin("foo/bar", "foo/barbaz") //=> false |
| 727 return style.isSeparator(childCodeUnits[childIndex]) || |
| 728 style.isSeparator(lastCodeUnit); |
| 729 } |
| 730 |
| 731 // Returns a [_PathDirection] describing the path represented by [codeUnits] |
| 732 // after [index]. |
| 733 // |
| 734 // This ignores leading separators. |
| 735 // |
| 736 // pathDirection("foo") //=> below root |
| 737 // pathDirection("foo/bar/../baz") //=> below root |
| 738 // pathDirection("//foo/bar/baz") //=> below root |
| 739 // pathDirection("/") //=> at root |
| 740 // pathDirection("foo/..") //=> at root |
| 741 // pathDirection("foo/../baz") //=> reaches root |
| 742 // pathDirection("foo/../..") //=> above root |
| 743 // pathDirection("foo/../../foo/bar/baz") //=> above root |
| 744 _PathDirection _pathDirection(List<int> codeUnits, int index) { |
| 745 var depth = 0; |
| 746 var reachedRoot = false; |
| 747 var i = index; |
| 748 while (i < codeUnits.length) { |
| 749 // Ignore initial separators or doubled separators. |
| 750 while (i < codeUnits.length && style.isSeparator(codeUnits[i])) { |
| 751 i++; |
| 752 } |
| 753 |
| 754 // If we're at the end, stop. |
| 755 if (i == codeUnits.length) break; |
| 756 |
| 757 // Move through the path component to the next separator. |
| 758 var start = i; |
| 759 while (i < codeUnits.length && !style.isSeparator(codeUnits[i])) { |
| 760 i++; |
| 761 } |
| 762 |
| 763 // See if the path component is ".", "..", or a name. |
| 764 if (i - start == 1 && codeUnits[start] == chars.PERIOD) { |
| 765 // Don't change the depth. |
| 766 } else if (i - start == 2 && |
| 767 codeUnits[start] == chars.PERIOD && |
| 768 codeUnits[start + 1] == chars.PERIOD) { |
| 769 // ".." backs out a directory. |
| 770 depth--; |
| 771 |
| 772 // If we work back beyond the root, stop. |
| 773 if (depth < 0) break; |
| 774 |
| 775 // Record that we reached the root so we don't return |
| 776 // [_PathDirection.belowRoot]. |
| 777 if (depth == 0) reachedRoot = true; |
| 778 } else { |
| 779 // Step inside a directory. |
| 780 depth++; |
| 781 } |
| 782 |
| 783 // If we're at the end, stop. |
| 784 if (i == codeUnits.length) break; |
| 785 |
| 786 // Move past the separator. |
| 787 i++; |
| 788 } |
| 789 |
| 790 if (depth < 0) return _PathDirection.aboveRoot; |
| 791 if (depth == 0) return _PathDirection.atRoot; |
| 792 if (reachedRoot) return _PathDirection.reachesRoot; |
| 793 return _PathDirection.belowRoot; |
| 794 } |
| 795 |
443 /// Removes a trailing extension from the last part of [path]. | 796 /// Removes a trailing extension from the last part of [path]. |
444 /// | 797 /// |
445 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' | 798 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' |
446 String withoutExtension(String path) { | 799 String withoutExtension(String path) { |
447 var parsed = _parse(path); | 800 var parsed = _parse(path); |
448 | 801 |
449 for (var i = parsed.parts.length - 1; i >= 0; i--) { | 802 for (var i = parsed.parts.length - 1; i >= 0; i--) { |
450 if (!parsed.parts[i].isEmpty) { | 803 if (!parsed.parts[i].isEmpty) { |
451 parsed.parts[i] = parsed.basenameWithoutExtension; | 804 parsed.parts[i] = parsed.basenameWithoutExtension; |
452 break; | 805 break; |
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
565 var message = new StringBuffer(); | 918 var message = new StringBuffer(); |
566 message.write("$method("); | 919 message.write("$method("); |
567 message.write(args | 920 message.write(args |
568 .take(numArgs) | 921 .take(numArgs) |
569 .map((arg) => arg == null ? "null" : '"$arg"') | 922 .map((arg) => arg == null ? "null" : '"$arg"') |
570 .join(", ")); | 923 .join(", ")); |
571 message.write("): part ${i - 1} was null, but part $i was not."); | 924 message.write("): part ${i - 1} was null, but part $i was not."); |
572 throw new ArgumentError(message.toString()); | 925 throw new ArgumentError(message.toString()); |
573 } | 926 } |
574 } | 927 } |
| 928 |
| 929 /// An enum of possible return values for [Context._pathDirection]. |
| 930 class _PathDirection { |
| 931 /// The path contains enough ".." components that at some point it reaches |
| 932 /// above its original root. |
| 933 /// |
| 934 /// Note that this applies even if the path ends beneath its original root. It |
| 935 /// takes precendence over any other return values that may apple. |
| 936 static const aboveRoot = const _PathDirection("above root"); |
| 937 |
| 938 /// The path contains enough ".." components that it ends at its original |
| 939 /// root. |
| 940 static const atRoot = const _PathDirection("at root"); |
| 941 |
| 942 /// The path contains enough ".." components that at some point it reaches its |
| 943 /// original root, but it ends beneath that root. |
| 944 static const reachesRoot = const _PathDirection("reaches root"); |
| 945 |
| 946 /// The path never reaches to or above its original root. |
| 947 static const belowRoot = const _PathDirection("below root"); |
| 948 |
| 949 final String name; |
| 950 |
| 951 const _PathDirection(this.name); |
| 952 |
| 953 String toString() => name; |
| 954 } |
OLD | NEW |