OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 /// A comprehensive, cross-platform path manipulation library. | 5 /// A comprehensive, cross-platform path manipulation library. |
6 /// | 6 /// |
7 /// ## Installing ## | 7 /// ## Installing ## |
8 /// | 8 /// |
9 /// Use [pub][] to install this package. Add the following to your | 9 /// Use [pub][] to install this package. Add the following to your |
10 /// `pubspec.yaml` file. | 10 /// `pubspec.yaml` file. |
(...skipping 283 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
294 /// | 294 /// |
295 /// // Windows | 295 /// // Windows |
296 /// path.relative(r'D:\other', from: r'C:\home'); // -> 'D:\other' | 296 /// path.relative(r'D:\other', from: r'C:\home'); // -> 'D:\other' |
297 /// | 297 /// |
298 /// // URL | 298 /// // URL |
299 /// path.relative('http://dartlang.org', from: 'http://pub.dartlang.org'); | 299 /// path.relative('http://dartlang.org', from: 'http://pub.dartlang.org'); |
300 /// // -> 'http://dartlang.org' | 300 /// // -> 'http://dartlang.org' |
301 String relative(String path, {String from}) => | 301 String relative(String path, {String from}) => |
302 _builder.relative(path, from: from); | 302 _builder.relative(path, from: from); |
303 | 303 |
| 304 /// Returns `true` if [child] is a path beneath `parent`, and `false` otherwise. |
| 305 /// |
| 306 /// path.isWithin('/root/path', '/root/path/a'); // -> true |
| 307 /// path.isWithin('/root/path', '/root/other'); // -> false |
| 308 /// path.isWithin('/root/path', '/root/path') // -> false |
| 309 bool isWithin(String parent, String child) => _builder.isWithin(parent, child); |
| 310 |
304 /// Removes a trailing extension from the last part of [path]. | 311 /// Removes a trailing extension from the last part of [path]. |
305 /// | 312 /// |
306 /// withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' | 313 /// withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' |
307 String withoutExtension(String path) => _builder.withoutExtension(path); | 314 String withoutExtension(String path) => _builder.withoutExtension(path); |
308 | 315 |
309 /// Returns the path represented by [uri]. | 316 /// Returns the path represented by [uri]. |
310 /// | 317 /// |
311 /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL | 318 /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL |
312 /// style, this will just convert [uri] to a string. | 319 /// style, this will just convert [uri] to a string. |
313 /// | 320 /// |
(...skipping 239 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
553 /// For a fixed number of parts, [join] is usually terser. | 560 /// For a fixed number of parts, [join] is usually terser. |
554 String joinAll(Iterable<String> parts) { | 561 String joinAll(Iterable<String> parts) { |
555 var buffer = new StringBuffer(); | 562 var buffer = new StringBuffer(); |
556 var needsSeparator = false; | 563 var needsSeparator = false; |
557 var isAbsoluteAndNotRootRelative = false; | 564 var isAbsoluteAndNotRootRelative = false; |
558 | 565 |
559 for (var part in parts.where((part) => part != '')) { | 566 for (var part in parts.where((part) => part != '')) { |
560 if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) { | 567 if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) { |
561 // If the new part is root-relative, it preserves the previous root but | 568 // If the new part is root-relative, it preserves the previous root but |
562 // replaces the path after it. | 569 // replaces the path after it. |
563 var oldRoot = this.rootPrefix(buffer.toString()); | 570 var parsed = _parse(part); |
| 571 parsed.root = this.rootPrefix(buffer.toString()); |
| 572 if (parsed.root.contains(style.needsSeparatorPattern)) { |
| 573 parsed.separators[0] = style.separator; |
| 574 } |
564 buffer.clear(); | 575 buffer.clear(); |
565 buffer.write(oldRoot); | 576 buffer.write(parsed); |
566 buffer.write(part); | |
567 } else if (this.isAbsolute(part)) { | 577 } else if (this.isAbsolute(part)) { |
568 isAbsoluteAndNotRootRelative = !this.isRootRelative(part); | 578 isAbsoluteAndNotRootRelative = !this.isRootRelative(part); |
569 // An absolute path discards everything before it. | 579 // An absolute path discards everything before it. |
570 buffer.clear(); | 580 buffer.clear(); |
571 buffer.write(part); | 581 buffer.write(part); |
572 } else { | 582 } else { |
573 if (part.length > 0 && part[0].contains(style.separatorPattern)) { | 583 if (part.length > 0 && part[0].contains(style.separatorPattern)) { |
574 // The part starts with a separator, so we don't need to add one. | 584 // The part starts with a separator, so we don't need to add one. |
575 } else if (needsSeparator) { | 585 } else if (needsSeparator) { |
576 buffer.write(separator); | 586 buffer.write(separator); |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
654 /// Since there is no relative path from one drive letter to another on | 664 /// Since there is no relative path from one drive letter to another on |
655 /// Windows, this will return an absolute path in that case. | 665 /// Windows, this will return an absolute path in that case. |
656 /// | 666 /// |
657 /// builder.relative(r'D:\other', from: r'C:\other'); // -> 'D:\other' | 667 /// builder.relative(r'D:\other', from: r'C:\other'); // -> 'D:\other' |
658 /// | 668 /// |
659 /// This will also return an absolute path if an absolute [path] is passed to | 669 /// This will also return an absolute path if an absolute [path] is passed to |
660 /// a builder with a relative [root]. | 670 /// a builder with a relative [root]. |
661 /// | 671 /// |
662 /// var builder = new Builder(r'some/relative/path'); | 672 /// var builder = new Builder(r'some/relative/path'); |
663 /// builder.relative(r'/absolute/path'); // -> '/absolute/path' | 673 /// builder.relative(r'/absolute/path'); // -> '/absolute/path' |
| 674 /// |
| 675 /// If [root] is relative, it may be impossible to determine a path from |
| 676 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is |
| 677 /// "/", no path can be determined. In this case, a [PathException] will be |
| 678 /// thrown. |
664 String relative(String path, {String from}) { | 679 String relative(String path, {String from}) { |
665 from = from == null ? root : this.join(root, from); | 680 from = from == null ? root : this.join(root, from); |
666 | 681 |
667 // We can't determine the path from a relative path to an absolute path. | 682 // We can't determine the path from a relative path to an absolute path. |
668 if (this.isRelative(from) && this.isAbsolute(path)) { | 683 if (this.isRelative(from) && this.isAbsolute(path)) { |
669 return this.normalize(path); | 684 return this.normalize(path); |
670 } | 685 } |
671 | 686 |
672 // If the given path is relative, resolve it relative to the root of the | 687 // If the given path is relative, resolve it relative to the root of the |
673 // builder. | 688 // builder. |
674 if (this.isRelative(path) || this.isRootRelative(path)) { | 689 if (this.isRelative(path) || this.isRootRelative(path)) { |
675 path = this.resolve(path); | 690 path = this.resolve(path); |
676 } | 691 } |
677 | 692 |
678 // If the path is still relative and `from` is absolute, we're unable to | 693 // If the path is still relative and `from` is absolute, we're unable to |
679 // find a path from `from` to `path`. | 694 // find a path from `from` to `path`. |
680 if (this.isRelative(path) && this.isAbsolute(from)) { | 695 if (this.isRelative(path) && this.isAbsolute(from)) { |
681 throw new ArgumentError('Unable to find a path to "$path" from "$from".'); | 696 throw new PathException('Unable to find a path to "$path" from "$from".'); |
682 } | 697 } |
683 | 698 |
684 var fromParsed = _parse(from)..normalize(); | 699 var fromParsed = _parse(from)..normalize(); |
685 var pathParsed = _parse(path)..normalize(); | 700 var pathParsed = _parse(path)..normalize(); |
686 | 701 |
687 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') { | 702 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') { |
688 return pathParsed.toString(); | 703 return pathParsed.toString(); |
689 } | 704 } |
690 | 705 |
691 // If the root prefixes don't match (for example, different drive letters | 706 // If the root prefixes don't match (for example, different drive letters |
(...skipping 13 matching lines...) Expand all Loading... |
705 fromParsed.parts.removeAt(0); | 720 fromParsed.parts.removeAt(0); |
706 fromParsed.separators.removeAt(1); | 721 fromParsed.separators.removeAt(1); |
707 pathParsed.parts.removeAt(0); | 722 pathParsed.parts.removeAt(0); |
708 pathParsed.separators.removeAt(1); | 723 pathParsed.separators.removeAt(1); |
709 } | 724 } |
710 | 725 |
711 // If there are any directories left in the from path, we need to walk up | 726 // If there are any directories left in the from path, we need to walk up |
712 // out of them. If a directory left in the from path is '..', it cannot | 727 // out of them. If a directory left in the from path is '..', it cannot |
713 // be cancelled by adding a '..'. | 728 // be cancelled by adding a '..'. |
714 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') { | 729 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') { |
715 throw new ArgumentError('Unable to find a path to "$path" from "$from".'); | 730 throw new PathException('Unable to find a path to "$path" from "$from".'); |
716 } | 731 } |
717 _growListFront(pathParsed.parts, fromParsed.parts.length, '..'); | 732 _growListFront(pathParsed.parts, fromParsed.parts.length, '..'); |
718 pathParsed.separators[0] = ''; | 733 pathParsed.separators[0] = ''; |
719 pathParsed.separators.insertAll(1, | 734 pathParsed.separators.insertAll(1, |
720 new List.filled(fromParsed.parts.length, style.separator)); | 735 new List.filled(fromParsed.parts.length, style.separator)); |
721 | 736 |
722 // Corner case: the paths completely collapsed. | 737 // Corner case: the paths completely collapsed. |
723 if (pathParsed.parts.length == 0) return '.'; | 738 if (pathParsed.parts.length == 0) return '.'; |
724 | 739 |
725 // Corner case: path was '.' and some '..' directories were added in front. | 740 // Corner case: path was '.' and some '..' directories were added in front. |
726 // Don't add a final '/.' in that case. | 741 // Don't add a final '/.' in that case. |
727 if (pathParsed.parts.length > 1 && pathParsed.parts.last == '.') { | 742 if (pathParsed.parts.length > 1 && pathParsed.parts.last == '.') { |
728 pathParsed.parts.removeLast(); | 743 pathParsed.parts.removeLast(); |
729 pathParsed.separators..removeLast()..removeLast()..add(''); | 744 pathParsed.separators..removeLast()..removeLast()..add(''); |
730 } | 745 } |
731 | 746 |
732 // Make it relative. | 747 // Make it relative. |
733 pathParsed.root = ''; | 748 pathParsed.root = ''; |
734 pathParsed.removeTrailingSeparators(); | 749 pathParsed.removeTrailingSeparators(); |
735 | 750 |
736 return pathParsed.toString(); | 751 return pathParsed.toString(); |
737 } | 752 } |
738 | 753 |
| 754 /// Returns `true` if [child] is a path beneath `parent`, and `false` |
| 755 /// otherwise. |
| 756 /// |
| 757 /// path.isWithin('/root/path', '/root/path/a'); // -> true |
| 758 /// path.isWithin('/root/path', '/root/other'); // -> false |
| 759 /// path.isWithin('/root/path', '/root/path') // -> false |
| 760 bool isWithin(String parent, String child) { |
| 761 var relative; |
| 762 try { |
| 763 relative = this.relative(child, from: parent); |
| 764 } on PathException catch (_) { |
| 765 // If no relative path from [parent] to [child] is found, [child] |
| 766 // definitely isn't a child of [parent]. |
| 767 return false; |
| 768 } |
| 769 |
| 770 var parts = this.split(relative); |
| 771 return this.isRelative(relative) && parts.first != '..' && |
| 772 parts.first != '.'; |
| 773 } |
| 774 |
739 /// Removes a trailing extension from the last part of [path]. | 775 /// Removes a trailing extension from the last part of [path]. |
740 /// | 776 /// |
741 /// builder.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' | 777 /// builder.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' |
742 String withoutExtension(String path) { | 778 String withoutExtension(String path) { |
743 var parsed = _parse(path); | 779 var parsed = _parse(path); |
744 | 780 |
745 for (var i = parsed.parts.length - 1; i >= 0; i--) { | 781 for (var i = parsed.parts.length - 1; i >= 0; i--) { |
746 if (!parsed.parts[i].isEmpty) { | 782 if (!parsed.parts[i].isEmpty) { |
747 parsed.parts[i] = parsed.basenameWithoutExtension; | 783 parsed.parts[i] = parsed.basenameWithoutExtension; |
748 break; | 784 break; |
(...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
976 /// The style for Windows paths. | 1012 /// The style for Windows paths. |
977 class _WindowsStyle extends Style { | 1013 class _WindowsStyle extends Style { |
978 _WindowsStyle(); | 1014 _WindowsStyle(); |
979 | 1015 |
980 final name = 'windows'; | 1016 final name = 'windows'; |
981 final separator = '\\'; | 1017 final separator = '\\'; |
982 final separatorPattern = new RegExp(r'[/\\]'); | 1018 final separatorPattern = new RegExp(r'[/\\]'); |
983 final needsSeparatorPattern = new RegExp(r'[^/\\]$'); | 1019 final needsSeparatorPattern = new RegExp(r'[^/\\]$'); |
984 final rootPattern = new RegExp(r'^(\\\\[^\\]+\\[^\\/]+|[a-zA-Z]:[/\\])'); | 1020 final rootPattern = new RegExp(r'^(\\\\[^\\]+\\[^\\/]+|[a-zA-Z]:[/\\])'); |
985 | 1021 |
| 1022 // Matches a back or forward slash that's not followed by another back or |
| 1023 // forward slash. |
| 1024 final relativeRootPattern = new RegExp(r"^[/\\](?![/\\])"); |
| 1025 |
986 String pathFromUri(Uri uri) { | 1026 String pathFromUri(Uri uri) { |
987 if (uri.scheme != '' && uri.scheme != 'file') { | 1027 if (uri.scheme != '' && uri.scheme != 'file') { |
988 throw new ArgumentError("Uri $uri must have scheme 'file:'."); | 1028 throw new ArgumentError("Uri $uri must have scheme 'file:'."); |
989 } | 1029 } |
990 | 1030 |
991 var path = uri.path; | 1031 var path = uri.path; |
992 if (uri.host == '') { | 1032 if (uri.host == '') { |
993 // Drive-letter paths look like "file:///C:/path/to/file". The | 1033 // Drive-letter paths look like "file:///C:/path/to/file". The |
994 // replaceFirst removes the extra initial slash. | 1034 // replaceFirst removes the extra initial slash. |
995 if (path.startsWith('/')) path = path.replaceFirst("/", ""); | 1035 if (path.startsWith('/')) path = path.replaceFirst("/", ""); |
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1190 // doesn't count. | 1230 // doesn't count. |
1191 if (lastDot <= 0) return [file, '']; | 1231 if (lastDot <= 0) return [file, '']; |
1192 | 1232 |
1193 return [file.substring(0, lastDot), file.substring(lastDot)]; | 1233 return [file.substring(0, lastDot), file.substring(lastDot)]; |
1194 } | 1234 } |
1195 | 1235 |
1196 _ParsedPath clone() => new _ParsedPath( | 1236 _ParsedPath clone() => new _ParsedPath( |
1197 style, root, isRootRelative, | 1237 style, root, isRootRelative, |
1198 new List.from(parts), new List.from(separators)); | 1238 new List.from(parts), new List.from(separators)); |
1199 } | 1239 } |
| 1240 |
| 1241 /// An exception class that's thrown when a path operation is unable to be |
| 1242 /// computed accurately. |
| 1243 class PathException implements Exception { |
| 1244 String message; |
| 1245 |
| 1246 PathException(this.message); |
| 1247 |
| 1248 String toString() => "PathException: $message"; |
| 1249 } |
OLD | NEW |