Index: pkg/path/lib/path.dart |
diff --git a/pkg/path/lib/path.dart b/pkg/path/lib/path.dart |
index 3c5f51eb3ea967285dfa8aee4586088c21e8a588..5f469edd6a6e5b9e67cc4abd2d892a8e8405e89e 100644 |
--- a/pkg/path/lib/path.dart |
+++ b/pkg/path/lib/path.dart |
@@ -301,6 +301,13 @@ String normalize(String path) => _builder.normalize(path); |
String relative(String path, {String from}) => |
_builder.relative(path, from: from); |
+/// Returns `true` if [child] is a path beneath `parent`, and `false` otherwise. |
+/// |
+/// path.isWithin('/root/path', '/root/path/a'); // -> true |
+/// path.isWithin('/root/path', '/root/other'); // -> false |
+/// path.isWithin('/root/path', '/root/path') // -> false |
+bool isWithin(String parent, String child) => _builder.isWithin(parent, child); |
+ |
/// Removes a trailing extension from the last part of [path]. |
/// |
/// withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' |
@@ -560,10 +567,13 @@ class Builder { |
if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) { |
// If the new part is root-relative, it preserves the previous root but |
// replaces the path after it. |
- var oldRoot = this.rootPrefix(buffer.toString()); |
+ var parsed = _parse(part); |
+ parsed.root = this.rootPrefix(buffer.toString()); |
+ if (parsed.root.contains(style.needsSeparatorPattern)) { |
+ parsed.separators[0] = style.separator; |
+ } |
buffer.clear(); |
- buffer.write(oldRoot); |
- buffer.write(part); |
+ buffer.write(parsed); |
} else if (this.isAbsolute(part)) { |
isAbsoluteAndNotRootRelative = !this.isRootRelative(part); |
// An absolute path discards everything before it. |
@@ -661,6 +671,11 @@ class Builder { |
/// |
/// var builder = new Builder(r'some/relative/path'); |
/// builder.relative(r'/absolute/path'); // -> '/absolute/path' |
+ /// |
+ /// If [root] is relative, it may be impossible to determine a path from |
+ /// [from] to [path]. For example, if [root] and [path] are "." and [from] is |
+ /// "/", no path can be determined. In this case, a [PathException] will be |
+ /// thrown. |
String relative(String path, {String from}) { |
from = from == null ? root : this.join(root, from); |
@@ -678,7 +693,7 @@ class Builder { |
// If the path is still relative and `from` is absolute, we're unable to |
// find a path from `from` to `path`. |
if (this.isRelative(path) && this.isAbsolute(from)) { |
- throw new ArgumentError('Unable to find a path to "$path" from "$from".'); |
+ throw new PathException('Unable to find a path to "$path" from "$from".'); |
} |
var fromParsed = _parse(from)..normalize(); |
@@ -712,7 +727,7 @@ class Builder { |
// out of them. If a directory left in the from path is '..', it cannot |
// be cancelled by adding a '..'. |
if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') { |
- throw new ArgumentError('Unable to find a path to "$path" from "$from".'); |
+ throw new PathException('Unable to find a path to "$path" from "$from".'); |
} |
_growListFront(pathParsed.parts, fromParsed.parts.length, '..'); |
pathParsed.separators[0] = ''; |
@@ -736,6 +751,27 @@ class Builder { |
return pathParsed.toString(); |
} |
+ /// Returns `true` if [child] is a path beneath `parent`, and `false` |
+ /// otherwise. |
+ /// |
+ /// path.isWithin('/root/path', '/root/path/a'); // -> true |
+ /// path.isWithin('/root/path', '/root/other'); // -> false |
+ /// path.isWithin('/root/path', '/root/path') // -> false |
+ bool isWithin(String parent, String child) { |
+ var relative; |
+ try { |
+ relative = this.relative(child, from: parent); |
+ } on PathException catch (_) { |
+ // If no relative path from [parent] to [child] is found, [child] |
+ // definitely isn't a child of [parent]. |
+ return false; |
+ } |
+ |
+ var parts = this.split(relative); |
+ return this.isRelative(relative) && parts.first != '..' && |
+ parts.first != '.'; |
+ } |
+ |
/// Removes a trailing extension from the last part of [path]. |
/// |
/// builder.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' |
@@ -983,6 +1019,10 @@ class _WindowsStyle extends Style { |
final needsSeparatorPattern = new RegExp(r'[^/\\]$'); |
final rootPattern = new RegExp(r'^(\\\\[^\\]+\\[^\\/]+|[a-zA-Z]:[/\\])'); |
+ // Matches a back or forward slash that's not followed by another back or |
+ // forward slash. |
+ final relativeRootPattern = new RegExp(r"^[/\\](?![/\\])"); |
+ |
String pathFromUri(Uri uri) { |
if (uri.scheme != '' && uri.scheme != 'file') { |
throw new ArgumentError("Uri $uri must have scheme 'file:'."); |
@@ -1197,3 +1237,13 @@ class _ParsedPath { |
style, root, isRootRelative, |
new List.from(parts), new List.from(separators)); |
} |
+ |
+/// An exception class that's thrown when a path operation is unable to be |
+/// computed accurately. |
+class PathException implements Exception { |
+ String message; |
+ |
+ PathException(this.message); |
+ |
+ String toString() => "PathException: $message"; |
+} |