Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: pkg/path/lib/path.dart

Issue 59483008: Add isWithin to pkg/path. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: code review Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | pkg/path/test/posix_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « no previous file | pkg/path/test/posix_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698