Rename "pathos" package to "path".
R=ajohnsen@google.com
Review URL: https://codereview.chromium.org//18356011
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/path@24964 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/README.md b/README.md
index 66f40b1..dacacaa 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
A comprehensive, cross-platform path manipulation library for Dart.
-The pathos library provides common operations for manipulating file paths:
+The path package provides common operations for manipulating file paths:
joining, splitting, normalizing, etc.
We've tried very hard to make this library do the "right" thing on whatever
-platform you run it on. When you use the top-level functions, it will assume
-the host OS's path style and work with that. If you want to specifically work
-with paths of a specific style, you can construct a `path.Builder` for that
+platform you run it on. When you use the top-level functions, it will assume the
+current platform's path style and work with that. If you want to specifically
+work with paths of a specific style, you can construct a `path.Builder` for that
style.
## Using
@@ -14,22 +14,24 @@
The path library was designed to be imported with a prefix, though you don't
have to if you don't want to:
- import 'package:pathos/path.dart' as path;
+ import 'package:path/path.dart' as path; // TODO(bob): ???
## Top-level functions
The most common way to use the library is through the top-level functions.
These manipulate path strings based on your current working directory and the
-path style (POSIX or Windows) of the host operating system.
+path style (POSIX, Windows, or URLs) of the host platform.
### String get current
-Gets the path to the current working directory.
+Gets the path to the current working directory. In the browser, this means the
+current URL. When using dart2js, this currently returns `.` due to technical
+constraints. In the future, it will return the current URL.
### String get separator
-Gets the path separator for the current platform. On Mac and Linux, this
-is `/`. On Windows, it's `\`.
+Gets the path separator for the current platform. On Mac, Linux, and the
+browser, this is `/`. On Windows, it's `\`.
### String absolute(String path)
@@ -100,12 +102,23 @@
path.rootPrefix(r'path\to\foo'); // -> ''
path.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
+ // URL
+ path.rootPrefix('path/to/foo'); // -> ''
+ path.rootPrefix('http://dartlang.org/path/to/foo');
+ // -> 'http://dartlang.org'
+
### bool isAbsolute(String path)
-Returns `true` if [path] is an absolute path and `false` if it is a
-relative path. On POSIX systems, absolute paths start with a `/` (forward
-slash). On Windows, an absolute path starts with `\\`, or a drive letter
-followed by `:/` or `:\`.
+Returns `true` if [path] is an absolute path and `false` if it is a relative
+path. On POSIX systems, absolute paths start with a `/` (forward slash). On
+Windows, an absolute path starts with `\\`, or a drive letter followed by `:/`
+or `:\`. For URLs, absolute paths either start with a protocol and optional
+hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`.
+
+URLs that start with `/` are known as "root-relative", since they're relative to
+the root of the current URL. Since root-relative paths are still absolute in
+every other sense, [isAbsolute] will return true for them. They can be detected
+using [isRootRelative].
### bool isRelative(String path)
@@ -114,6 +127,16 @@
Windows, an absolute path starts with `\\`, or a drive letter followed by
`:/` or `:\`.
+### bool isRootRelative(String path)
+
+Returns `true` if [path] is a root-relative path and `false` if it's not. URLs
+that start with `/` are known as "root-relative", since they're relative to the
+root of the current URL. Since root-relative paths are still absolute in every
+other sense, [isAbsolute] will return true for them. They can be detected using
+[isRootRelative].
+
+No POSIX and Windows paths are root-relative.
+
### String join(String part1, [String part2, String part3, ...])
Joins the given path parts into a single path using the current platform's
@@ -149,6 +172,10 @@
// Windows
path.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
+ // Browser
+ path.split('http://dartlang.org/path/to/foo');
+ // -> ['http://dartlang.org', 'path', 'to', 'foo']
+
### String normalize(String path)
Normalizes [path], simplifying it by handling `..`, and `.`, and
@@ -176,14 +203,57 @@
Since there is no relative path from one drive letter to another on Windows,
this will return an absolute path in that case.
+ // Windows
path.relative(r'D:\other', from: r'C:\home'); // -> 'D:\other'
+ // URL
+ path.relative('http://dartlang.org', from: 'http://pub.dartlang.org');
+ // -> 'http://dartlang.org'
+
### String withoutExtension(String path)
Removes a trailing extension from the last part of [path].
withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
+### String fromUri(Uri uri)
+
+Returns the path represented by [uri]. For POSIX and Windows styles, [uri] must
+be a `file:` URI. For the URL style, this will just convert [uri] to a string.
+
+ // POSIX
+ path.fromUri(Uri.parse('file:///path/to/foo'))
+ // -> '/path/to/foo'
+
+ // Windows
+ path.fromUri(Uri.parse('file:///C:/path/to/foo'))
+ // -> r'C:\path\to\foo'
+
+ // URL
+ path.fromUri(Uri.parse('http://dartlang.org/path/to/foo'))
+ // -> 'http://dartlang.org/path/to/foo'
+
+### Uri toUri(String path)
+
+Returns the URI that represents [path]. For POSIX and Windows styles, this will
+return a `file:` URI. For the URL style, this will just convert [path] to a
+[Uri].
+
+This will always convert relative paths to absolute ones before converting
+to a URI.
+
+ // POSIX
+ path.toUri('/path/to/foo')
+ // -> Uri.parse('file:///path/to/foo')
+
+ // Windows
+ path.toUri(r'C:\path\to\foo')
+ // -> Uri.parse('file:///C:/path/to/foo')
+
+ // URL
+ path.toUri('http://dartlang.org/path/to/foo')
+ // -> Uri.parse('http://dartlang.org/path/to/foo')
+
## The path.Builder class
In addition to the functions, path exposes a `path.Builder` class. This lets
@@ -234,6 +304,11 @@
builder.rootPrefix(r'path\to\foo'); // -> ''
builder.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
+ // URL
+ builder.rootPrefix('path/to/foo'); // -> ''
+ builder.rootPrefix('http://dartlang.org/path/to/foo');
+ // -> 'http://dartlang.org'
+
### String resolve(String part1, [String part2, String part3, ...])
Creates a new path by appending the given path parts to the [root].
@@ -244,9 +319,9 @@
## The path.Style class
-The path library can work with two different "flavors" of path: POSIX and
-Windows. The differences between these are encapsulated by the `path.Style`
-enum class. There are two instances of it:
+The path library can work with three different "flavors" of path: POSIX,
+Windows, and URLs. The differences between these are encapsulated by the
+`path.Style` enum class. There are three instances of it:
### path.Style.posix
@@ -259,16 +334,22 @@
a drive letter followed by a colon (example, "C:") or two backslashes
("\\") for UNC paths.
+### path.Style.url
+
+URLs aren't filesystem paths, but they're supported by Pathos to make it easier
+to manipulate URL paths in the browser.
+
+URLs use "/" (forward slash) as separators. Absolute paths either start with a
+protocol and optional hostname (e.g. `http://dartlang.org`, `file://`) or with
+"/".
+
## FAQ
### Where can I use this?
-Currently, Dart has no way of encapsulating configuration-specific code.
-Ideally, this library would be able to import dart:io when that's available or
-dart:html when that is. That would let it seamlessly work on both.
-
-Until then, this only works on the standalone VM. It's API is not coupled to
-dart:io, but it uses it internally to determine the current working directory.
+Pathos runs on the Dart VM and in the browser under both dart2js and Dartium.
+Under dart2js, it currently returns "." as the current working directory, while
+under Dartium it returns the current URL.
### Why doesn't this make paths first-class objects?
diff --git a/lib/path.dart b/lib/path.dart
index 9e540b2..b425124 100644
--- a/lib/path.dart
+++ b/lib/path.dart
@@ -3,16 +3,77 @@
// BSD-style license that can be found in the LICENSE file.
/// A comprehensive, cross-platform path manipulation library.
+///
+/// ## Installing ##
+///
+/// Use [pub][] to install this package. Add the following to your
+/// `pubspec.yaml` file.
+///
+/// dependencies:
+/// path: any
+///
+/// Then run `pub install`.
+///
+/// For more information, see the [path package on pub.dartlang.org][pkg].
+///
+/// [pub]: http://pub.dartlang.org
+/// [pkg]: http://pub.dartlang.org/packages/path
library path;
-import 'dart:io' as io;
+import 'dart:mirrors';
/// An internal builder for the current OS so we can provide a straight
/// functional interface and not require users to create one.
final _builder = new Builder();
+/**
+ * Inserts [length] elements in front of the [list] and fills them with the
+ * [fillValue].
+ */
+void _growListFront(List list, int length, fillValue) =>
+ list.insertAll(0, new List.filled(length, fillValue));
+
+/// If we're running in the server-side Dart VM, this will return a
+/// [LibraryMirror] that gives access to the `dart:io` library.
+///
+/// If `dart:io` is not available, this returns null.
+LibraryMirror get _io {
+ try {
+ return currentMirrorSystem().libraries[Uri.parse('dart:io')];
+ } catch (_) {
+ return null;
+ }
+}
+
+// TODO(nweiz): when issue 6490 or 6943 are fixed, make this work under dart2js.
+/// If we're running in Dartium, this will return a [LibraryMirror] that gives
+/// access to the `dart:html` library.
+///
+/// If `dart:html` is not available, this returns null.
+LibraryMirror get _html {
+ try {
+ return currentMirrorSystem().libraries[Uri.parse('dart:html')];
+ } catch (_) {
+ return null;
+ }
+}
+
/// Gets the path to the current working directory.
-String get current => new io.Directory.current().path;
+///
+/// In the browser, this means the current URL. When using dart2js, this
+/// currently returns `.` due to technical constraints. In the future, it will
+/// return the current URL.
+String get current {
+ if (_io != null) {
+ return _io.classes[const Symbol('Directory')]
+ .getField(const Symbol('current')).reflectee.path;
+ } else if (_html != null) {
+ return _html.getField(const Symbol('window'))
+ .reflectee.location.href;
+ } else {
+ return '.';
+ }
+}
/// Gets the path separator for the current platform. On Mac and Linux, this
/// is `/`. On Windows, it's `\`.
@@ -81,12 +142,25 @@
/// // Windows
/// path.rootPrefix(r'path\to\foo'); // -> ''
/// path.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
+///
+/// // URL
+/// path.rootPrefix('path/to/foo'); // -> ''
+/// path.rootPrefix('http://dartlang.org/path/to/foo');
+/// // -> 'http://dartlang.org'
String rootPrefix(String path) => _builder.rootPrefix(path);
/// Returns `true` if [path] is an absolute path and `false` if it is a
-/// relative path. On POSIX systems, absolute paths start with a `/` (forward
-/// slash). On Windows, an absolute path starts with `\\`, or a drive letter
-/// followed by `:/` or `:\`.
+/// relative path.
+///
+/// On POSIX systems, absolute paths start with a `/` (forward slash). On
+/// Windows, an absolute path starts with `\\`, or a drive letter followed by
+/// `:/` or `:\`. For URLs, absolute paths either start with a protocol and
+/// optional hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`.
+///
+/// URLs that start with `/` are known as "root-relative", since they're
+/// relative to the root of the current URL. Since root-relative paths are still
+/// absolute in every other sense, [isAbsolute] will return true for them. They
+/// can be detected using [isRootRelative].
bool isAbsolute(String path) => _builder.isAbsolute(path);
/// Returns `true` if [path] is a relative path and `false` if it is absolute.
@@ -95,6 +169,16 @@
/// `:/` or `:\`.
bool isRelative(String path) => _builder.isRelative(path);
+/// Returns `true` if [path] is a root-relative path and `false` if it's not.
+///
+/// URLs that start with `/` are known as "root-relative", since they're
+/// relative to the root of the current URL. Since root-relative paths are still
+/// absolute in every other sense, [isAbsolute] will return true for them. They
+/// can be detected using [isRootRelative].
+///
+/// No POSIX and Windows paths are root-relative.
+bool isRootRelative(String path) => _builder.isRootRelative(path);
+
/// Joins the given path parts into a single path using the current platform's
/// [separator]. Example:
///
@@ -146,6 +230,10 @@
///
/// // Windows
/// path.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
+///
+/// // Browser
+/// path.split('http://dartlang.org/path/to/foo');
+/// // -> ['http://dartlang.org', 'path', 'to', 'foo']
List<String> split(String path) => _builder.split(path);
/// Normalizes [path], simplifying it by handling `..`, and `.`, and
@@ -169,9 +257,15 @@
/// from: '/root/path'); // -> '../other.dart'
///
/// Since there is no relative path from one drive letter to another on Windows,
-/// this will return an absolute path in that case.
+/// or from one hostname to another for URLs, this will return an absolute path
+/// in those cases.
///
+/// // Windows
/// path.relative(r'D:\other', from: r'C:\home'); // -> 'D:\other'
+///
+/// // URL
+/// path.relative('http://dartlang.org', from: 'http://pub.dartlang.org');
+/// // -> 'http://dartlang.org'
String relative(String path, {String from}) =>
_builder.relative(path, from: from);
@@ -180,6 +274,45 @@
/// withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
String withoutExtension(String path) => _builder.withoutExtension(path);
+/// Returns the path represented by [uri].
+///
+/// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL
+/// style, this will just convert [uri] to a string.
+///
+/// // POSIX
+/// path.fromUri(Uri.parse('file:///path/to/foo'))
+/// // -> '/path/to/foo'
+///
+/// // Windows
+/// path.fromUri(Uri.parse('file:///C:/path/to/foo'))
+/// // -> r'C:\path\to\foo'
+///
+/// // URL
+/// path.fromUri(Uri.parse('http://dartlang.org/path/to/foo'))
+/// // -> 'http://dartlang.org/path/to/foo'
+String fromUri(Uri uri) => _builder.fromUri(uri);
+
+/// Returns the URI that represents [path].
+///
+/// For POSIX and Windows styles, this will return a `file:` URI. For the URL
+/// style, this will just convert [path] to a [Uri].
+///
+/// This will always convert relative paths to absolute ones before converting
+/// to a URI.
+///
+/// // POSIX
+/// path.toUri('/path/to/foo')
+/// // -> Uri.parse('file:///path/to/foo')
+///
+/// // Windows
+/// path.toUri(r'C:\path\to\foo')
+/// // -> Uri.parse('file:///C:/path/to/foo')
+///
+/// // URL
+/// path.toUri('http://dartlang.org/path/to/foo')
+/// // -> Uri.parse('http://dartlang.org/path/to/foo')
+Uri toUri(String path) => _builder.toUri(path);
+
/// Validates that there are no non-null arguments following a null one and
/// throws an appropriate [ArgumentError] on failure.
_validateArgList(String method, List<String> args) {
@@ -194,11 +327,11 @@
// Show the arguments.
var message = new StringBuffer();
- message.add("$method(");
- message.add(args.take(numArgs)
+ message.write("$method(");
+ message.write(args.take(numArgs)
.map((arg) => arg == null ? "null" : '"$arg"')
.join(", "));
- message.add("): part ${i - 1} was null, but part $i was not.");
+ message.write("): part ${i - 1} was null, but part $i was not.");
throw new ArgumentError(message.toString());
}
}
@@ -211,9 +344,16 @@
/// If [style] is omitted, it uses the host operating system's path style. If
/// [root] is omitted, it defaults to the current working directory. If [root]
/// is relative, it is considered relative to the current working directory.
+ ///
+ /// On the browser, the path style is [Style.url]. In Dartium, [root] defaults
+ /// to the current URL. When using dart2js, it currently defaults to `.` due
+ /// to technical constraints.
factory Builder({Style style, String root}) {
if (style == null) {
- if (io.Platform.operatingSystem == 'windows') {
+ if (_io == null) {
+ style = Style.url;
+ } else if (_io.classes[const Symbol('Platform')]
+ .getField(const Symbol('operatingSystem')).reflectee == 'windows') {
style = Style.windows;
} else {
style = Style.posix;
@@ -245,7 +385,7 @@
///
/// Trailing separators are ignored.
///
- /// builder.dirname('path/to/'); // -> 'to'
+ /// builder.basename('path/to/'); // -> 'to'
String basename(String path) => _parse(path).basename;
/// Gets the part of [path] after the last separator on the builder's
@@ -255,7 +395,7 @@
///
/// Trailing separators are ignored.
///
- /// builder.dirname('path/to/foo.dart/'); // -> 'foo'
+ /// builder.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo'
String basenameWithoutExtension(String path) =>
_parse(path).basenameWithoutExtension;
@@ -306,22 +446,45 @@
/// // Windows
/// builder.rootPrefix(r'path\to\foo'); // -> ''
/// builder.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
+ ///
+ /// // URL
+ /// builder.rootPrefix('path/to/foo'); // -> ''
+ /// builder.rootPrefix('http://dartlang.org/path/to/foo');
+ /// // -> 'http://dartlang.org'
String rootPrefix(String path) {
var root = _parse(path).root;
return root == null ? '' : root;
}
/// Returns `true` if [path] is an absolute path and `false` if it is a
- /// relative path. On POSIX systems, absolute paths start with a `/` (forward
- /// slash). On Windows, an absolute path starts with `\\`, or a drive letter
- /// followed by `:/` or `:\`.
+ /// relative path.
+ ///
+ /// On POSIX systems, absolute paths start with a `/` (forward slash). On
+ /// Windows, an absolute path starts with `\\`, or a drive letter followed by
+ /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and
+ /// optional hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`.
+ ///
+ /// URLs that start with `/` are known as "root-relative", since they're
+ /// relative to the root of the current URL. Since root-relative paths are
+ /// still absolute in every other sense, [isAbsolute] will return true for
+ /// them. They can be detected using [isRootRelative].
bool isAbsolute(String path) => _parse(path).isAbsolute;
/// Returns `true` if [path] is a relative path and `false` if it is absolute.
/// On POSIX systems, absolute paths start with a `/` (forward slash). On
/// Windows, an absolute path starts with `\\`, or a drive letter followed by
/// `:/` or `:\`.
- bool isRelative(String path) => !isAbsolute(path);
+ bool isRelative(String path) => !this.isAbsolute(path);
+
+ /// Returns `true` if [path] is a root-relative path and `false` if it's not.
+ ///
+ /// URLs that start with `/` are known as "root-relative", since they're
+ /// relative to the root of the current URL. Since root-relative paths are
+ /// still absolute in every other sense, [isAbsolute] will return true for
+ /// them. They can be detected using [isRootRelative].
+ ///
+ /// No POSIX and Windows paths are root-relative.
+ bool isRootRelative(String path) => _parse(path).isRootRelative;
/// Joins the given path parts into a single path. Example:
///
@@ -360,26 +523,34 @@
String joinAll(Iterable<String> parts) {
var buffer = new StringBuffer();
var needsSeparator = false;
+ var isAbsoluteAndNotRootRelative = false;
for (var part in parts) {
- if (this.isAbsolute(part)) {
+ 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());
+ buffer.clear();
+ buffer.write(oldRoot);
+ buffer.write(part);
+ } else if (this.isAbsolute(part)) {
+ isAbsoluteAndNotRootRelative = !this.isRootRelative(part);
// An absolute path discards everything before it.
buffer.clear();
- buffer.add(part);
+ buffer.write(part);
} else {
if (part.length > 0 && part[0].contains(style.separatorPattern)) {
// The part starts with a separator, so we don't need to add one.
} else if (needsSeparator) {
- buffer.add(separator);
+ buffer.write(separator);
}
- buffer.add(part);
+ buffer.write(part);
}
// Unless this part ends with a separator, we'll need to add one before
// the next part.
- needsSeparator = part.length > 0 &&
- !part[part.length - 1].contains(style.separatorPattern);
+ needsSeparator = part.contains(style.needsSeparatorPattern);
}
return buffer.toString();
@@ -406,8 +577,9 @@
List<String> split(String path) {
var parsed = _parse(path);
// Filter out empty parts that exist due to multiple separators in a row.
- parsed.parts = parsed.parts.where((part) => !part.isEmpty).toList();
- if (parsed.root != null) parsed.parts.insertRange(0, 1, parsed.root);
+ parsed.parts = parsed.parts.where((part) => !part.isEmpty)
+ .toList();
+ if (parsed.root != null) parsed.parts.insert(0, parsed.root);
return parsed.parts;
}
@@ -469,7 +641,9 @@
// If the given path is relative, resolve it relative to the root of the
// builder.
- if (this.isRelative(path)) path = this.resolve(path);
+ if (this.isRelative(path) || this.isRootRelative(path)) {
+ path = this.resolve(path);
+ }
// If the path is still relative and `from` is absolute, we're unable to
// find a path from `from` to `path`.
@@ -480,6 +654,10 @@
var fromParsed = _parse(from)..normalize();
var pathParsed = _parse(path)..normalize();
+ if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') {
+ return pathParsed.toString();
+ }
+
// If the root prefixes don't match (for example, different drive letters
// on Windows), then there is no relative path, so just return the absolute
// one. In Windows, drive letters are case-insenstive and we allow
@@ -495,16 +673,17 @@
while (fromParsed.parts.length > 0 && pathParsed.parts.length > 0 &&
fromParsed.parts[0] == pathParsed.parts[0]) {
fromParsed.parts.removeAt(0);
- fromParsed.separators.removeAt(0);
+ fromParsed.separators.removeAt(1);
pathParsed.parts.removeAt(0);
- pathParsed.separators.removeAt(0);
+ pathParsed.separators.removeAt(1);
}
// If there are any directories left in the root path, we need to walk up
// out of them.
- pathParsed.parts.insertRange(0, fromParsed.parts.length, '..');
- pathParsed.separators.insertRange(0, fromParsed.parts.length,
- style.separator);
+ _growListFront(pathParsed.parts, fromParsed.parts.length, '..');
+ pathParsed.separators[0] = '';
+ pathParsed.separators.insertAll(1,
+ new List.filled(fromParsed.parts.length, style.separator));
// Corner case: the paths completely collapsed.
if (pathParsed.parts.length == 0) return '.';
@@ -532,16 +711,68 @@
return parsed.toString();
}
+ /// Returns the path represented by [uri].
+ ///
+ /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL
+ /// style, this will just convert [uri] to a string.
+ ///
+ /// // POSIX
+ /// builder.fromUri(Uri.parse('file:///path/to/foo'))
+ /// // -> '/path/to/foo'
+ ///
+ /// // Windows
+ /// builder.fromUri(Uri.parse('file:///C:/path/to/foo'))
+ /// // -> r'C:\path\to\foo'
+ ///
+ /// // URL
+ /// builder.fromUri(Uri.parse('http://dartlang.org/path/to/foo'))
+ /// // -> 'http://dartlang.org/path/to/foo'
+ String fromUri(Uri uri) => style.pathFromUri(uri);
+
+ /// Returns the URI that represents [path].
+ ///
+ /// For POSIX and Windows styles, this will return a `file:` URI. For the URL
+ /// style, this will just convert [path] to a [Uri].
+ ///
+ /// // POSIX
+ /// builder.toUri('/path/to/foo')
+ /// // -> Uri.parse('file:///path/to/foo')
+ ///
+ /// // Windows
+ /// builder.toUri(r'C:\path\to\foo')
+ /// // -> Uri.parse('file:///C:/path/to/foo')
+ ///
+ /// // URL
+ /// builder.toUri('http://dartlang.org/path/to/foo')
+ /// // -> Uri.parse('http://dartlang.org/path/to/foo')
+ Uri toUri(String path) {
+ if (isRelative(path)) {
+ return Uri.parse(path.replaceAll(style.separatorPattern, '/'));
+ } else {
+ return style.pathToUri(join(root, path));
+ }
+ }
+
_ParsedPath _parse(String path) {
var before = path;
// Remove the root prefix, if any.
var root = style.getRoot(path);
+ var isRootRelative = style.getRelativeRoot(path) != null;
if (root != null) path = path.substring(root.length);
// Split the parts on path separators.
var parts = [];
var separators = [];
+
+ var firstSeparator = style.separatorPattern.firstMatch(path);
+ if (firstSeparator != null && firstSeparator.start == 0) {
+ separators.add(firstSeparator[0]);
+ path = path.substring(firstSeparator[0].length);
+ } else {
+ separators.add('');
+ }
+
var start = 0;
for (var match in style.separatorPattern.allMatches(path)) {
parts.add(path.substring(start, match.start));
@@ -555,57 +786,216 @@
separators.add('');
}
- return new _ParsedPath(style, root, parts, separators);
+ return new _ParsedPath(style, root, isRootRelative, parts, separators);
}
}
/// An enum type describing a "flavor" of path.
-class Style {
+abstract class Style {
/// POSIX-style paths use "/" (forward slash) as separators. Absolute paths
/// start with "/". Used by UNIX, Linux, Mac OS X, and others.
- static final posix = new Style._('posix', '/', '/', '/');
+ static final posix = new _PosixStyle();
/// Windows paths use "\" (backslash) as separators. Absolute paths start with
/// a drive letter followed by a colon (example, "C:") or two backslashes
/// ("\\") for UNC paths.
// TODO(rnystrom): The UNC root prefix should include the drive name too, not
// just the "\\".
- static final windows = new Style._('windows', '\\', r'[/\\]',
- r'\\\\|[a-zA-Z]:[/\\]');
+ static final windows = new _WindowsStyle();
- Style._(this.name, this.separator, String separatorPattern,
- String rootPattern)
- : separatorPattern = new RegExp(separatorPattern),
- _rootPattern = new RegExp('^$rootPattern');
+ /// URLs aren't filesystem paths, but they're supported by Pathos to make it
+ /// easier to manipulate URL paths in the browser.
+ ///
+ /// URLs use "/" (forward slash) as separators. Absolute paths either start
+ /// with a protocol and optional hostname (e.g. `http://dartlang.org`,
+ /// `file://`) or with "/".
+ static final url = new _UrlStyle();
/// The name of this path style. Will be "posix" or "windows".
- final String name;
+ String get name;
/// The path separator for this style. On POSIX, this is `/`. On Windows,
/// it's `\`.
- final String separator;
+ String get separator;
/// The [Pattern] that can be used to match a separator for a path in this
- /// style. Windows allows both "/" and "\" as path separators even though
- /// "\" is the canonical one.
- final Pattern separatorPattern;
+ /// style. Windows allows both "/" and "\" as path separators even though "\"
+ /// is the canonical one.
+ Pattern get separatorPattern;
- // TODO(nweiz): make this a Pattern when issue 7080 is fixed.
- /// The [RegExp] that can be used to match the root prefix of an absolute
+ /// The [Pattern] that matches path components that need a separator after
+ /// them.
+ ///
+ /// Windows and POSIX styles just need separators when the previous component
+ /// doesn't already end in a separator, but the URL always needs to place a
+ /// separator between the root and the first component, even if the root
+ /// already ends in a separator character. For example, to join "file://" and
+ /// "usr", an additional "/" is needed (making "file:///usr").
+ Pattern get needsSeparatorPattern;
+
+ /// The [Pattern] that can be used to match the root prefix of an absolute
/// path in this style.
- final RegExp _rootPattern;
+ Pattern get rootPattern;
+
+ /// The [Pattern] that can be used to match the root prefix of a root-relative
+ /// path in this style.
+ ///
+ /// This can be null to indicate that this style doesn't support root-relative
+ /// paths.
+ final Pattern relativeRootPattern = null;
/// Gets the root prefix of [path] if path is absolute. If [path] is relative,
/// returns `null`.
String getRoot(String path) {
- var match = _rootPattern.firstMatch(path);
+ var match = rootPattern.firstMatch(path);
+ if (match != null) return match[0];
+ return getRelativeRoot(path);
+ }
+
+ /// Gets the root prefix of [path] if it's root-relative.
+ ///
+ /// If [path] is relative or absolute and not root-relative, returns `null`.
+ String getRelativeRoot(String path) {
+ if (relativeRootPattern == null) return null;
+ var match = relativeRootPattern.firstMatch(path);
if (match == null) return null;
return match[0];
}
+ /// Returns the path represented by [uri] in this style.
+ String pathFromUri(Uri uri);
+
+ /// Returns the URI that represents [path].
+ ///
+ /// Pathos will always path an absolute path for [path]. Relative paths are
+ /// handled automatically by [Builder].
+ Uri pathToUri(String path);
+
String toString() => name;
}
+/// The style for POSIX paths.
+class _PosixStyle extends Style {
+ _PosixStyle();
+
+ static final _builder = new Builder(style: Style.posix);
+
+ final name = 'posix';
+ final separator = '/';
+ final separatorPattern = new RegExp(r'/');
+ final needsSeparatorPattern = new RegExp(r'[^/]$');
+ final rootPattern = new RegExp(r'^/');
+
+ String pathFromUri(Uri uri) {
+ if (uri.scheme == '' || uri.scheme == 'file') {
+ return Uri.decodeComponent(uri.path);
+ }
+ throw new ArgumentError("Uri $uri must have scheme 'file:'.");
+ }
+
+ Uri pathToUri(String path) {
+ var parsed = _builder._parse(path);
+
+ if (parsed.parts.isEmpty) {
+ // If the path is a bare root (e.g. "/"), [components] will
+ // currently be empty. We add two empty components so the URL constructor
+ // produces "file:///", with a trailing slash.
+ parsed.parts.addAll(["", ""]);
+ } else if (parsed.hasTrailingSeparator) {
+ // If the path has a trailing slash, add a single empty component so the
+ // URI has a trailing slash as well.
+ parsed.parts.add("");
+ }
+
+ return new Uri(scheme: 'file', pathSegments: parsed.parts);
+ }
+}
+
+/// The style for Windows paths.
+class _WindowsStyle extends Style {
+ _WindowsStyle();
+
+ static final _builder = new Builder(style: Style.windows);
+
+ final name = 'windows';
+ final separator = '\\';
+ final separatorPattern = new RegExp(r'[/\\]');
+ final needsSeparatorPattern = new RegExp(r'[^/\\]$');
+ final rootPattern = new RegExp(r'^(\\\\|[a-zA-Z]:[/\\])');
+
+ String pathFromUri(Uri uri) {
+ if (uri.scheme != '' && uri.scheme != 'file') {
+ throw new ArgumentError("Uri $uri must have scheme 'file:'.");
+ }
+
+ var path = uri.path;
+ if (uri.host == '') {
+ // Drive-letter paths look like "file:///C:/path/to/file". The
+ // replaceFirst removes the extra initial slash.
+ if (path.startsWith('/')) path = path.replaceFirst("/", "");
+ } else {
+ // Network paths look like "file://hostname/path/to/file".
+ path = '\\\\${uri.host}$path';
+ }
+ return Uri.decodeComponent(path.replaceAll("/", "\\"));
+ }
+
+ Uri pathToUri(String path) {
+ var parsed = _builder._parse(path);
+ if (parsed.root == r'\\') {
+ // Network paths become "file://hostname/path/to/file".
+
+ var host = parsed.parts.removeAt(0);
+
+ if (parsed.parts.isEmpty) {
+ // If the path is a bare root (e.g. "\\hostname"), [parsed.parts] will
+ // currently be empty. We add two empty components so the URL
+ // constructor produces "file://hostname/", with a trailing slash.
+ parsed.parts.addAll(["", ""]);
+ } else if (parsed.hasTrailingSeparator) {
+ // If the path has a trailing slash, add a single empty component so the
+ // URI has a trailing slash as well.
+ parsed.parts.add("");
+ }
+
+ return new Uri(scheme: 'file', host: host, pathSegments: parsed.parts);
+ } else {
+ // Drive-letter paths become "file:///C:/path/to/file".
+
+ // If the path is a bare root (e.g. "C:\"), [parsed.parts] will currently
+ // be empty. We add an empty component so the URL constructor produces
+ // "file:///C:/", with a trailing slash. We also add an empty component if
+ // the URL otherwise has a trailing slash.
+ if (parsed.parts.length == 0 || parsed.hasTrailingSeparator) {
+ parsed.parts.add("");
+ }
+
+ // Get rid of the trailing "\" in "C:\" because the URI constructor will
+ // add a separator on its own.
+ parsed.parts.insert(0, parsed.root.replaceAll(separatorPattern, ""));
+
+ return new Uri(scheme: 'file', pathSegments: parsed.parts);
+ }
+ }
+}
+
+/// The style for URL paths.
+class _UrlStyle extends Style {
+ _UrlStyle();
+
+ final name = 'url';
+ final separator = '/';
+ final separatorPattern = new RegExp(r'/');
+ final needsSeparatorPattern = new RegExp(
+ r"(^[a-zA-Z][-+.a-zA-Z\d]*://|[^/])$");
+ final rootPattern = new RegExp(r"[a-zA-Z][-+.a-zA-Z\d]*://[^/]*");
+ final relativeRootPattern = new RegExp(r"^/");
+
+ String pathFromUri(Uri uri) => uri.toString();
+
+ Uri pathToUri(String path) => Uri.parse(path);
+}
+
// TODO(rnystrom): Make this public?
class _ParsedPath {
/// The [Style] that was used to parse this path.
@@ -617,12 +1007,20 @@
/// letters.
String root;
+ /// Whether this path is root-relative.
+ ///
+ /// See [Builder.isRootRelative].
+ bool isRootRelative;
+
/// The path-separated parts of the path. All but the last will be
/// directories.
List<String> parts;
- /// The path separators following each part. The last one will be an empty
- /// string unless the path ends with a trailing separator.
+ /// The path separators preceding each part.
+ ///
+ /// The first one will be an empty string unless the root requires a separator
+ /// between it and the path. The last one will be an empty string unless the
+ /// path ends with a trailing separator.
List<String> separators;
/// The file extension of the last part, or "" if it doesn't have one.
@@ -631,7 +1029,8 @@
/// `true` if this is an absolute path.
bool get isAbsolute => root != null;
- _ParsedPath(this.style, this.root, this.parts, this.separators);
+ _ParsedPath(this.style, this.root, this.isRootRelative, this.parts,
+ this.separators);
String get basename {
var copy = this.clone();
@@ -647,6 +1046,8 @@
return copy._splitExtension()[0];
}
+ bool get hasTrailingSeparator => !parts.isEmpty && (parts.last == '' || separators.last != '');
+
void removeTrailingSeparators() {
while (!parts.isEmpty && parts.last == '') {
parts.removeLast();
@@ -677,7 +1078,7 @@
// A relative path can back out from the start directory.
if (!isAbsolute) {
- newParts.insertRange(0, leadingDoubles, '..');
+ _growListFront(newParts, leadingDoubles, '..');
}
// If we collapsed down to nothing, do ".".
@@ -686,8 +1087,12 @@
}
// Canonicalize separators.
- var newSeparators = [];
- newSeparators.insertRange(0, newParts.length, style.separator);
+ var newSeparators = new List.generate(
+ newParts.length, (_) => style.separator, growable: true);
+ newSeparators.insert(0,
+ isAbsolute && newParts.length > 0 &&
+ root.contains(style.needsSeparatorPattern) ?
+ style.separator : '');
parts = newParts;
separators = newSeparators;
@@ -701,11 +1106,12 @@
String toString() {
var builder = new StringBuffer();
- if (root != null) builder.add(root);
+ if (root != null) builder.write(root);
for (var i = 0; i < parts.length; i++) {
- builder.add(parts[i]);
- builder.add(separators[i]);
+ builder.write(separators[i]);
+ builder.write(parts[i]);
}
+ builder.write(separators.last);
return builder.toString();
}
@@ -729,5 +1135,6 @@
}
_ParsedPath clone() => new _ParsedPath(
- style, root, new List.from(parts), new List.from(separators));
+ style, root, isRootRelative,
+ new List.from(parts), new List.from(separators));
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 3372302..60fcec2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,10 +1,7 @@
-name: pathos
+name: path
author: "Dart Team <misc@dartlang.org>"
homepage: http://www.dartlang.org
description: >
A string-based path manipulation library. All of the path operations you know
and love, with solid support on both Windows and POSIX (Linux and Mac OS X)
machines.
-
- Currently only runs on the standalone VM, but will run in a browser as soon as
- configuration-specific code is supported by Dart.
diff --git a/test/dart2js_test.dart b/test/dart2js_test.dart
new file mode 100644
index 0000000..997513d
--- /dev/null
+++ b/test/dart2js_test.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:unittest/unittest.dart';
+import 'package:path/path.dart' as path;
+
+// In the future, the default root will be window.location.href, but right now
+// that's not possible.
+
+main() {
+ group('new Builder()', () {
+ test('uses the current working directory if root is omitted', () {
+ var builder = new path.Builder();
+ expect(builder.root, '.');
+ });
+
+ test('uses URL if style is omitted', () {
+ var builder = new path.Builder();
+ expect(builder.style, path.Style.url);
+ });
+ });
+
+ test('current', () {
+ expect(path.current, '.');
+ });
+}
diff --git a/test/dartium_test.dart b/test/dartium_test.dart
new file mode 100644
index 0000000..2a830c7
--- /dev/null
+++ b/test/dartium_test.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:html';
+
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+import 'package:path/path.dart' as path;
+
+main() {
+ useHtmlConfiguration();
+
+ group('new Builder()', () {
+ test('uses the current working directory if root is omitted', () {
+ var builder = new path.Builder();
+ expect(builder.root, window.location.href);
+ });
+
+ test('uses URL if style is omitted', () {
+ var builder = new path.Builder();
+ expect(builder.style, path.Style.url);
+ });
+ });
+
+ test('current', () {
+ expect(path.current, window.location.href);
+ });
+}
diff --git a/test/io_test.dart b/test/io_test.dart
new file mode 100644
index 0000000..fc29ced
--- /dev/null
+++ b/test/io_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io' as io;
+
+import 'package:unittest/unittest.dart';
+import 'package:path/path.dart' as path;
+
+main() {
+ group('new Builder()', () {
+ test('uses the current working directory if root is omitted', () {
+ var builder = new path.Builder();
+ expect(builder.root, io.Directory.current.path);
+ });
+
+ test('uses the host OS if style is omitted', () {
+ var builder = new path.Builder();
+ if (io.Platform.operatingSystem == 'windows') {
+ expect(builder.style, path.Style.windows);
+ } else {
+ expect(builder.style, path.Style.posix);
+ }
+ });
+ });
+
+ test('current', () {
+ expect(path.current, io.Directory.current.path);
+ });
+}
diff --git a/test/path_test.dart b/test/path_test.dart
index eea906a..ef3cf21 100644
--- a/test/path_test.dart
+++ b/test/path_test.dart
@@ -2,10 +2,6 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-library all_test;
-
-import 'dart:io' as io;
-
import 'package:unittest/unittest.dart';
import 'package:path/path.dart' as path;
@@ -37,23 +33,5 @@
var builder = new path.Builder(style: path.Style.windows);
expect(builder.style, path.Style.windows);
});
-
- test('uses the current working directory if root is omitted', () {
- var builder = new path.Builder();
- expect(builder.root, new io.Directory.current().path);
- });
-
- test('uses the host OS if style is omitted', () {
- var builder = new path.Builder();
- if (io.Platform.operatingSystem == 'windows') {
- expect(builder.style, path.Style.windows);
- } else {
- expect(builder.style, path.Style.posix);
- }
- });
- });
-
- test('current', () {
- expect(path.current, new io.Directory.current().path);
});
}
diff --git a/test/path_posix_test.dart b/test/posix_test.dart
similarity index 91%
rename from test/path_posix_test.dart
rename to test/posix_test.dart
index 763035f..f3232bc 100644
--- a/test/path_posix_test.dart
+++ b/test/posix_test.dart
@@ -2,9 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-library path_test;
-
-import 'dart:io' as io;
+library path.test.posix_test;
import 'package:unittest/unittest.dart';
import 'package:path/path.dart' as path;
@@ -300,6 +298,12 @@
expect(builder.relative('../a/b.txt'), '../a/b.txt');
expect(builder.relative('a/./b/../c.txt'), 'a/c.txt');
});
+
+ // Regression
+ test('from root-only path', () {
+ expect(builder.relative('/', from: '/'), '.');
+ expect(builder.relative('/root/path', from: '/'), 'root/path');
+ });
});
group('from relative root', () {
@@ -344,6 +348,12 @@
equals('/foo/bar/baz'));
expect(r.relative('..', from: 'foo/bar'), equals('../../..'));
});
+
+ test('from a . root', () {
+ var r = new path.Builder(style: path.Style.posix, root: '.');
+ expect(r.relative('/foo/bar/baz'), equals('/foo/bar/baz'));
+ expect(r.relative('foo/bar/baz'), equals('foo/bar/baz'));
+ });
});
group('resolve', () {
@@ -389,4 +399,26 @@
expect(builder.withoutExtension('a/b.c/'), 'a/b/');
expect(builder.withoutExtension('a/b.c//'), 'a/b//');
});
+
+ test('fromUri', () {
+ expect(builder.fromUri(Uri.parse('file:///path/to/foo')), '/path/to/foo');
+ expect(builder.fromUri(Uri.parse('file:///path/to/foo/')), '/path/to/foo/');
+ expect(builder.fromUri(Uri.parse('file:///')), '/');
+ expect(builder.fromUri(Uri.parse('foo/bar')), 'foo/bar');
+ expect(builder.fromUri(Uri.parse('/path/to/foo')), '/path/to/foo');
+ expect(builder.fromUri(Uri.parse('///path/to/foo')), '/path/to/foo');
+ expect(builder.fromUri(Uri.parse('file:///path/to/foo%23bar')),
+ '/path/to/foo#bar');
+ expect(() => builder.fromUri(Uri.parse('http://dartlang.org')),
+ throwsArgumentError);
+ });
+
+ test('toUri', () {
+ expect(builder.toUri('/path/to/foo'), Uri.parse('file:///path/to/foo'));
+ expect(builder.toUri('/path/to/foo/'), Uri.parse('file:///path/to/foo/'));
+ expect(builder.toUri('/'), Uri.parse('file:///'));
+ expect(builder.toUri('foo/bar'), Uri.parse('foo/bar'));
+ expect(builder.toUri('/path/to/foo#bar'),
+ Uri.parse('file:///path/to/foo%23bar'));
+ });
}
diff --git a/test/url_test.dart b/test/url_test.dart
new file mode 100644
index 0000000..3e03d50
--- /dev/null
+++ b/test/url_test.dart
@@ -0,0 +1,653 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:unittest/unittest.dart';
+import 'package:path/path.dart' as path;
+
+main() {
+ var builder = new path.Builder(style: path.Style.url,
+ root: 'http://dartlang.org/root/path');
+
+ test('separator', () {
+ expect(builder.separator, '/');
+ });
+
+ test('extension', () {
+ expect(builder.extension(''), '');
+ expect(builder.extension('foo.dart'), '.dart');
+ expect(builder.extension('foo.dart.js'), '.js');
+ expect(builder.extension('a.b/c'), '');
+ expect(builder.extension('a.b/c.d'), '.d');
+ expect(builder.extension(r'a.b\c'), r'.b\c');
+ });
+
+ test('rootPrefix', () {
+ expect(builder.rootPrefix(''), '');
+ expect(builder.rootPrefix('a'), '');
+ expect(builder.rootPrefix('a/b'), '');
+ expect(builder.rootPrefix('http://dartlang.org/a/c'),
+ 'http://dartlang.org');
+ expect(builder.rootPrefix('file:///a/c'), 'file://');
+ expect(builder.rootPrefix('/a/c'), '/');
+ expect(builder.rootPrefix('http://dartlang.org/'), 'http://dartlang.org');
+ expect(builder.rootPrefix('file:///'), 'file://');
+ expect(builder.rootPrefix('http://dartlang.org'), 'http://dartlang.org');
+ expect(builder.rootPrefix('file://'), 'file://');
+ expect(builder.rootPrefix('/'), '/');
+ });
+
+ test('dirname', () {
+ expect(builder.dirname(''), '.');
+ expect(builder.dirname('a'), '.');
+ expect(builder.dirname('a/b'), 'a');
+ expect(builder.dirname('a/b/c'), 'a/b');
+ expect(builder.dirname('a/b.c'), 'a');
+ expect(builder.dirname('a/'), '.');
+ expect(builder.dirname('a/.'), 'a');
+ expect(builder.dirname(r'a\b/c'), r'a\b');
+ expect(builder.dirname('http://dartlang.org/a'), 'http://dartlang.org');
+ expect(builder.dirname('file:///a'), 'file://');
+ expect(builder.dirname('/a'), '/');
+ expect(builder.dirname('http://dartlang.org///a'), 'http://dartlang.org');
+ expect(builder.dirname('file://///a'), 'file://');
+ expect(builder.dirname('///a'), '/');
+ expect(builder.dirname('http://dartlang.org/'), 'http://dartlang.org');
+ expect(builder.dirname('http://dartlang.org'), 'http://dartlang.org');
+ expect(builder.dirname('file:///'), 'file://');
+ expect(builder.dirname('file://'), 'file://');
+ expect(builder.dirname('/'), '/');
+ expect(builder.dirname('http://dartlang.org///'), 'http://dartlang.org');
+ expect(builder.dirname('file://///'), 'file://');
+ expect(builder.dirname('///'), '/');
+ expect(builder.dirname('a/b/'), 'a');
+ expect(builder.dirname(r'a/b\c'), 'a');
+ expect(builder.dirname('a//'), '.');
+ expect(builder.dirname('a/b//'), 'a');
+ expect(builder.dirname('a//b'), 'a');
+ });
+
+ test('basename', () {
+ expect(builder.basename(''), '');
+ expect(builder.basename('a'), 'a');
+ expect(builder.basename('a/b'), 'b');
+ expect(builder.basename('a/b/c'), 'c');
+ expect(builder.basename('a/b.c'), 'b.c');
+ expect(builder.basename('a/'), 'a');
+ expect(builder.basename('a/.'), '.');
+ expect(builder.basename(r'a\b/c'), 'c');
+ expect(builder.basename('http://dartlang.org/a'), 'a');
+ expect(builder.basename('file:///a'), 'a');
+ expect(builder.basename('/a'), 'a');
+ expect(builder.basename('http://dartlang.org/'), 'http://dartlang.org');
+ expect(builder.basename('http://dartlang.org'), 'http://dartlang.org');
+ expect(builder.basename('file:///'), 'file://');
+ expect(builder.basename('file://'), 'file://');
+ expect(builder.basename('/'), '/');
+ expect(builder.basename('a/b/'), 'b');
+ expect(builder.basename(r'a/b\c'), r'b\c');
+ expect(builder.basename('a//'), 'a');
+ expect(builder.basename('a/b//'), 'b');
+ expect(builder.basename('a//b'), 'b');
+ });
+
+ test('basenameWithoutExtension', () {
+ expect(builder.basenameWithoutExtension(''), '');
+ expect(builder.basenameWithoutExtension('a'), 'a');
+ expect(builder.basenameWithoutExtension('a/b'), 'b');
+ expect(builder.basenameWithoutExtension('a/b/c'), 'c');
+ expect(builder.basenameWithoutExtension('a/b.c'), 'b');
+ expect(builder.basenameWithoutExtension('a/'), 'a');
+ expect(builder.basenameWithoutExtension('a/.'), '.');
+ expect(builder.basenameWithoutExtension(r'a/b\c'), r'b\c');
+ expect(builder.basenameWithoutExtension('a/.bashrc'), '.bashrc');
+ expect(builder.basenameWithoutExtension('a/b/c.d.e'), 'c.d');
+ expect(builder.basenameWithoutExtension('a//'), 'a');
+ expect(builder.basenameWithoutExtension('a/b//'), 'b');
+ expect(builder.basenameWithoutExtension('a//b'), 'b');
+ expect(builder.basenameWithoutExtension('a/b.c/'), 'b');
+ expect(builder.basenameWithoutExtension('a/b.c//'), 'b');
+ });
+
+ test('isAbsolute', () {
+ expect(builder.isAbsolute(''), false);
+ expect(builder.isAbsolute('a'), false);
+ expect(builder.isAbsolute('a/b'), false);
+ expect(builder.isAbsolute('http://dartlang.org/a'), true);
+ expect(builder.isAbsolute('file:///a'), true);
+ expect(builder.isAbsolute('/a'), true);
+ expect(builder.isAbsolute('http://dartlang.org/a/b'), true);
+ expect(builder.isAbsolute('file:///a/b'), true);
+ expect(builder.isAbsolute('/a/b'), true);
+ expect(builder.isAbsolute('http://dartlang.org/'), true);
+ expect(builder.isAbsolute('file:///'), true);
+ expect(builder.isAbsolute('http://dartlang.org'), true);
+ expect(builder.isAbsolute('file://'), true);
+ expect(builder.isAbsolute('/'), true);
+ expect(builder.isAbsolute('~'), false);
+ expect(builder.isAbsolute('.'), false);
+ expect(builder.isAbsolute('../a'), false);
+ expect(builder.isAbsolute('C:/a'), false);
+ expect(builder.isAbsolute(r'C:\a'), false);
+ expect(builder.isAbsolute(r'\\a'), false);
+ });
+
+ test('isRelative', () {
+ expect(builder.isRelative(''), true);
+ expect(builder.isRelative('a'), true);
+ expect(builder.isRelative('a/b'), true);
+ expect(builder.isRelative('http://dartlang.org/a'), false);
+ expect(builder.isRelative('file:///a'), false);
+ expect(builder.isRelative('/a'), false);
+ expect(builder.isRelative('http://dartlang.org/a/b'), false);
+ expect(builder.isRelative('file:///a/b'), false);
+ expect(builder.isRelative('/a/b'), false);
+ expect(builder.isRelative('http://dartlang.org/'), false);
+ expect(builder.isRelative('file:///'), false);
+ expect(builder.isRelative('http://dartlang.org'), false);
+ expect(builder.isRelative('file://'), false);
+ expect(builder.isRelative('/'), false);
+ expect(builder.isRelative('~'), true);
+ expect(builder.isRelative('.'), true);
+ expect(builder.isRelative('../a'), true);
+ expect(builder.isRelative('C:/a'), true);
+ expect(builder.isRelative(r'C:\a'), true);
+ expect(builder.isRelative(r'\\a'), true);
+ });
+
+ test('isRootRelative', () {
+ expect(builder.isRootRelative(''), false);
+ expect(builder.isRootRelative('a'), false);
+ expect(builder.isRootRelative('a/b'), false);
+ expect(builder.isRootRelative('http://dartlang.org/a'), false);
+ expect(builder.isRootRelative('file:///a'), false);
+ expect(builder.isRootRelative('/a'), true);
+ expect(builder.isRootRelative('http://dartlang.org/a/b'), false);
+ expect(builder.isRootRelative('file:///a/b'), false);
+ expect(builder.isRootRelative('/a/b'), true);
+ expect(builder.isRootRelative('http://dartlang.org/'), false);
+ expect(builder.isRootRelative('file:///'), false);
+ expect(builder.isRootRelative('http://dartlang.org'), false);
+ expect(builder.isRootRelative('file://'), false);
+ expect(builder.isRootRelative('/'), true);
+ expect(builder.isRootRelative('~'), false);
+ expect(builder.isRootRelative('.'), false);
+ expect(builder.isRootRelative('../a'), false);
+ expect(builder.isRootRelative('C:/a'), false);
+ expect(builder.isRootRelative(r'C:\a'), false);
+ expect(builder.isRootRelative(r'\\a'), false);
+ });
+
+ group('join', () {
+ test('allows up to eight parts', () {
+ expect(builder.join('a'), 'a');
+ expect(builder.join('a', 'b'), 'a/b');
+ expect(builder.join('a', 'b', 'c'), 'a/b/c');
+ expect(builder.join('a', 'b', 'c', 'd'), 'a/b/c/d');
+ expect(builder.join('a', 'b', 'c', 'd', 'e'), 'a/b/c/d/e');
+ expect(builder.join('a', 'b', 'c', 'd', 'e', 'f'), 'a/b/c/d/e/f');
+ expect(builder.join('a', 'b', 'c', 'd', 'e', 'f', 'g'), 'a/b/c/d/e/f/g');
+ expect(builder.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'),
+ 'a/b/c/d/e/f/g/h');
+ });
+
+ test('does not add separator if a part ends in one', () {
+ expect(builder.join('a/', 'b', 'c/', 'd'), 'a/b/c/d');
+ expect(builder.join('a\\', 'b'), r'a\/b');
+ });
+
+ test('ignores parts before an absolute path', () {
+ expect(builder.join('a', 'http://dartlang.org', 'b', 'c'),
+ 'http://dartlang.org/b/c');
+ expect(builder.join('a', 'file://', 'b', 'c'), 'file:///b/c');
+ expect(builder.join('a', '/', 'b', 'c'), '/b/c');
+ expect(builder.join('a', '/b', 'http://dartlang.org/c', 'd'),
+ 'http://dartlang.org/c/d');
+ expect(builder.join(
+ 'a', 'http://google.com/b', 'http://dartlang.org/c', 'd'),
+ 'http://dartlang.org/c/d');
+ expect(builder.join('a', '/b', '/c', 'd'), '/c/d');
+ expect(builder.join('a', r'c:\b', 'c', 'd'), r'a/c:\b/c/d');
+ expect(builder.join('a', r'\\b', 'c', 'd'), r'a/\\b/c/d');
+ });
+
+ test('preserves roots before a root-relative path', () {
+ expect(builder.join('http://dartlang.org', 'a', '/b', 'c'),
+ 'http://dartlang.org/b/c');
+ expect(builder.join('file://', 'a', '/b', 'c'), 'file:///b/c');
+ expect(builder.join('file://', 'a', '/b', 'c', '/d'), 'file:///d');
+ });
+
+ test('ignores trailing nulls', () {
+ expect(builder.join('a', null), equals('a'));
+ expect(builder.join('a', 'b', 'c', null, null), equals('a/b/c'));
+ });
+
+ test('disallows intermediate nulls', () {
+ expect(() => builder.join('a', null, 'b'), throwsArgumentError);
+ expect(() => builder.join(null, 'a'), throwsArgumentError);
+ });
+ });
+
+ group('joinAll', () {
+ test('allows more than eight parts', () {
+ expect(builder.joinAll(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']),
+ 'a/b/c/d/e/f/g/h/i');
+ });
+
+ test('ignores parts before an absolute path', () {
+ expect(builder.joinAll(['a', 'http://dartlang.org', 'b', 'c']),
+ 'http://dartlang.org/b/c');
+ expect(builder.joinAll(['a', 'file://', 'b', 'c']), 'file:///b/c');
+ expect(builder.joinAll(['a', '/', 'b', 'c']), '/b/c');
+ expect(builder.joinAll(['a', '/b', 'http://dartlang.org/c', 'd']),
+ 'http://dartlang.org/c/d');
+ expect(builder.joinAll(
+ ['a', 'http://google.com/b', 'http://dartlang.org/c', 'd']),
+ 'http://dartlang.org/c/d');
+ expect(builder.joinAll(['a', '/b', '/c', 'd']), '/c/d');
+ expect(builder.joinAll(['a', r'c:\b', 'c', 'd']), r'a/c:\b/c/d');
+ expect(builder.joinAll(['a', r'\\b', 'c', 'd']), r'a/\\b/c/d');
+ });
+
+ test('preserves roots before a root-relative path', () {
+ expect(builder.joinAll(['http://dartlang.org', 'a', '/b', 'c']),
+ 'http://dartlang.org/b/c');
+ expect(builder.joinAll(['file://', 'a', '/b', 'c']), 'file:///b/c');
+ expect(builder.joinAll(['file://', 'a', '/b', 'c', '/d']), 'file:///d');
+ });
+ });
+
+ group('split', () {
+ test('simple cases', () {
+ expect(builder.split(''), []);
+ expect(builder.split('.'), ['.']);
+ expect(builder.split('..'), ['..']);
+ expect(builder.split('foo'), equals(['foo']));
+ expect(builder.split('foo/bar.txt'), equals(['foo', 'bar.txt']));
+ expect(builder.split('foo/bar/baz'), equals(['foo', 'bar', 'baz']));
+ expect(builder.split('foo/../bar/./baz'),
+ equals(['foo', '..', 'bar', '.', 'baz']));
+ expect(builder.split('foo//bar///baz'), equals(['foo', 'bar', 'baz']));
+ expect(builder.split('foo/\\/baz'), equals(['foo', '\\', 'baz']));
+ expect(builder.split('.'), equals(['.']));
+ expect(builder.split(''), equals([]));
+ expect(builder.split('foo/'), equals(['foo']));
+ expect(builder.split('http://dartlang.org//'),
+ equals(['http://dartlang.org']));
+ expect(builder.split('file:////'), equals(['file://']));
+ expect(builder.split('//'), equals(['/']));
+ });
+
+ test('includes the root for absolute paths', () {
+ expect(builder.split('http://dartlang.org/foo/bar/baz'),
+ equals(['http://dartlang.org', 'foo', 'bar', 'baz']));
+ expect(builder.split('file:///foo/bar/baz'),
+ equals(['file://', 'foo', 'bar', 'baz']));
+ expect(builder.split('/foo/bar/baz'), equals(['/', 'foo', 'bar', 'baz']));
+ expect(builder.split('http://dartlang.org/'),
+ equals(['http://dartlang.org']));
+ expect(builder.split('http://dartlang.org'),
+ equals(['http://dartlang.org']));
+ expect(builder.split('file:///'), equals(['file://']));
+ expect(builder.split('file://'), equals(['file://']));
+ expect(builder.split('/'), equals(['/']));
+ });
+ });
+
+ group('normalize', () {
+ test('simple cases', () {
+ expect(builder.normalize(''), '');
+ expect(builder.normalize('.'), '.');
+ expect(builder.normalize('..'), '..');
+ expect(builder.normalize('a'), 'a');
+ expect(builder.normalize('http://dartlang.org/'), 'http://dartlang.org');
+ expect(builder.normalize('http://dartlang.org'), 'http://dartlang.org');
+ expect(builder.normalize('file://'), 'file://');
+ expect(builder.normalize('file:///'), 'file://');
+ expect(builder.normalize('/'), '/');
+ expect(builder.normalize(r'\'), r'\');
+ });
+
+ test('collapses redundant separators', () {
+ expect(builder.normalize(r'a/b/c'), r'a/b/c');
+ expect(builder.normalize(r'a//b///c////d'), r'a/b/c/d');
+ });
+
+ test('does not collapse separators for other platform', () {
+ expect(builder.normalize(r'a\\b\\\c'), r'a\\b\\\c');
+ });
+
+ test('eliminates "." parts', () {
+ expect(builder.normalize('./'), '.');
+ expect(builder.normalize('http://dartlang.org/.'), 'http://dartlang.org');
+ expect(builder.normalize('file:///.'), 'file://');
+ expect(builder.normalize('/.'), '/');
+ expect(builder.normalize('http://dartlang.org/./'),
+ 'http://dartlang.org');
+ expect(builder.normalize('file:///./'), 'file://');
+ expect(builder.normalize('/./'), '/');
+ expect(builder.normalize('./.'), '.');
+ expect(builder.normalize('a/./b'), 'a/b');
+ expect(builder.normalize('a/.b/c'), 'a/.b/c');
+ expect(builder.normalize('a/././b/./c'), 'a/b/c');
+ expect(builder.normalize('././a'), 'a');
+ expect(builder.normalize('a/./.'), 'a');
+ });
+
+ test('eliminates ".." parts', () {
+ expect(builder.normalize('..'), '..');
+ expect(builder.normalize('../'), '..');
+ expect(builder.normalize('../../..'), '../../..');
+ expect(builder.normalize('../../../'), '../../..');
+ expect(builder.normalize('http://dartlang.org/..'),
+ 'http://dartlang.org');
+ expect(builder.normalize('file:///..'), 'file://');
+ expect(builder.normalize('/..'), '/');
+ expect(builder.normalize('http://dartlang.org/../../..'),
+ 'http://dartlang.org');
+ expect(builder.normalize('file:///../../..'), 'file://');
+ expect(builder.normalize('/../../..'), '/');
+ expect(builder.normalize('http://dartlang.org/../../../a'),
+ 'http://dartlang.org/a');
+ expect(builder.normalize('file:///../../../a'), 'file:///a');
+ expect(builder.normalize('/../../../a'), '/a');
+ expect(builder.normalize('a/..'), '.');
+ expect(builder.normalize('a/b/..'), 'a');
+ expect(builder.normalize('a/../b'), 'b');
+ expect(builder.normalize('a/./../b'), 'b');
+ expect(builder.normalize('a/b/c/../../d/e/..'), 'a/d');
+ expect(builder.normalize('a/b/../../../../c'), '../../c');
+ });
+
+ test('does not walk before root on absolute paths', () {
+ expect(builder.normalize('..'), '..');
+ expect(builder.normalize('../'), '..');
+ expect(builder.normalize('http://dartlang.org/..'),
+ 'http://dartlang.org');
+ expect(builder.normalize('file:///..'), 'file://');
+ expect(builder.normalize('/..'), '/');
+ expect(builder.normalize('a/..'), '.');
+ expect(builder.normalize('a/b/..'), 'a');
+ expect(builder.normalize('a/../b'), 'b');
+ expect(builder.normalize('a/./../b'), 'b');
+ expect(builder.normalize('a/b/c/../../d/e/..'), 'a/d');
+ expect(builder.normalize('a/b/../../../../c'), '../../c');
+ });
+
+ test('removes trailing separators', () {
+ expect(builder.normalize('./'), '.');
+ expect(builder.normalize('.//'), '.');
+ expect(builder.normalize('a/'), 'a');
+ expect(builder.normalize('a/b/'), 'a/b');
+ expect(builder.normalize('a/b///'), 'a/b');
+ });
+ });
+
+ group('relative', () {
+ group('from absolute root', () {
+ test('given absolute path in root', () {
+ expect(builder.relative('http://dartlang.org'), '../..');
+ expect(builder.relative('http://dartlang.org/'), '../..');
+ expect(builder.relative('/'), '../..');
+ expect(builder.relative('http://dartlang.org/root'), '..');
+ expect(builder.relative('/root'), '..');
+ expect(builder.relative('http://dartlang.org/root/path'), '.');
+ expect(builder.relative('/root/path'), '.');
+ expect(builder.relative('http://dartlang.org/root/path/a'), 'a');
+ expect(builder.relative('/root/path/a'), 'a');
+ expect(builder.relative('http://dartlang.org/root/path/a/b.txt'),
+ 'a/b.txt');
+ expect(builder.relative('/root/path/a/b.txt'), 'a/b.txt');
+ expect(builder.relative('http://dartlang.org/root/a/b.txt'),
+ '../a/b.txt');
+ expect(builder.relative('/root/a/b.txt'), '../a/b.txt');
+ });
+
+ test('given absolute path outside of root', () {
+ expect(builder.relative('http://dartlang.org/a/b'), '../../a/b');
+ expect(builder.relative('/a/b'), '../../a/b');
+ expect(builder.relative('http://dartlang.org/root/path/a'), 'a');
+ expect(builder.relative('/root/path/a'), 'a');
+ expect(builder.relative('http://dartlang.org/root/path/a/b.txt'),
+ 'a/b.txt');
+ expect(builder.relative('http://dartlang.org/root/path/a/b.txt'),
+ 'a/b.txt');
+ expect(builder.relative('http://dartlang.org/root/a/b.txt'),
+ '../a/b.txt');
+ });
+
+ test('given absolute path with different hostname/protocol', () {
+ expect(builder.relative(r'http://google.com/a/b'),
+ r'http://google.com/a/b');
+ expect(builder.relative(r'file:///a/b'),
+ r'file:///a/b');
+ });
+
+ test('given relative path', () {
+ // The path is considered relative to the root, so it basically just
+ // normalizes.
+ expect(builder.relative(''), '.');
+ expect(builder.relative('.'), '.');
+ expect(builder.relative('a'), 'a');
+ expect(builder.relative('a/b.txt'), 'a/b.txt');
+ expect(builder.relative('../a/b.txt'), '../a/b.txt');
+ expect(builder.relative('a/./b/../c.txt'), 'a/c.txt');
+ });
+
+ // Regression
+ test('from root-only path', () {
+ expect(builder.relative('http://dartlang.org',
+ from: 'http://dartlang.org'),
+ '.');
+ expect(builder.relative('http://dartlang.org/root/path',
+ from: 'http://dartlang.org'),
+ 'root/path');
+ });
+ });
+
+ group('from relative root', () {
+ var r = new path.Builder(style: path.Style.url, root: 'foo/bar');
+
+ test('given absolute path', () {
+ expect(r.relative('http://google.com/'), equals('http://google.com'));
+ expect(r.relative('http://google.com'), equals('http://google.com'));
+ expect(r.relative('file:///'), equals('file://'));
+ expect(r.relative('file://'), equals('file://'));
+ expect(r.relative('/'), equals('/'));
+ expect(r.relative('/a/b'), equals('/a/b'));
+ });
+
+ test('given relative path', () {
+ // The path is considered relative to the root, so it basically just
+ // normalizes.
+ expect(r.relative(''), '.');
+ expect(r.relative('.'), '.');
+ expect(r.relative('..'), '..');
+ expect(r.relative('a'), 'a');
+ expect(r.relative('a/b.txt'), 'a/b.txt');
+ expect(r.relative('../a/b.txt'), '../a/b.txt');
+ expect(r.relative('a/./b/../c.txt'), 'a/c.txt');
+ });
+ });
+
+ group('from root-relative root', () {
+ var r = new path.Builder(style: path.Style.url, root: '/foo/bar');
+
+ test('given absolute path', () {
+ expect(r.relative('http://google.com/'), equals('http://google.com'));
+ expect(r.relative('http://google.com'), equals('http://google.com'));
+ expect(r.relative('file:///'), equals('file://'));
+ expect(r.relative('file://'), equals('file://'));
+ expect(r.relative('/'), equals('../..'));
+ expect(r.relative('/a/b'), equals('../../a/b'));
+ });
+
+ test('given relative path', () {
+ // The path is considered relative to the root, so it basically just
+ // normalizes.
+ expect(r.relative(''), '.');
+ expect(r.relative('.'), '.');
+ expect(r.relative('..'), '..');
+ expect(r.relative('a'), 'a');
+ expect(r.relative('a/b.txt'), 'a/b.txt');
+ expect(r.relative('../a/b.txt'), '../a/b.txt');
+ expect(r.relative('a/./b/../c.txt'), 'a/c.txt');
+ });
+ });
+
+ test('from a root with extension', () {
+ var r = new path.Builder(style: path.Style.url, root: '/dir.ext');
+ expect(r.relative('/dir.ext/file'), 'file');
+ });
+
+ test('with a root parameter', () {
+ expect(builder.relative('/foo/bar/baz', from: '/foo/bar'), equals('baz'));
+ expect(
+ builder.relative('/foo/bar/baz', from: 'http://dartlang.org/foo/bar'),
+ equals('baz'));
+ expect(
+ builder.relative('http://dartlang.org/foo/bar/baz', from: '/foo/bar'),
+ equals('baz'));
+ expect(builder.relative('http://dartlang.org/foo/bar/baz',
+ from: 'file:///foo/bar'),
+ equals('http://dartlang.org/foo/bar/baz'));
+ expect(builder.relative('http://dartlang.org/foo/bar/baz',
+ from: 'http://dartlang.org/foo/bar'), equals('baz'));
+ expect(
+ builder.relative('/foo/bar/baz', from: 'file:///foo/bar'),
+ equals('http://dartlang.org/foo/bar/baz'));
+ expect(
+ builder.relative('file:///foo/bar/baz', from: '/foo/bar'),
+ equals('file:///foo/bar/baz'));
+
+ expect(builder.relative('..', from: '/foo/bar'), equals('../../root'));
+ expect(builder.relative('..', from: 'http://dartlang.org/foo/bar'),
+ equals('../../root'));
+ expect(builder.relative('..', from: 'file:///foo/bar'),
+ equals('http://dartlang.org/root'));
+ expect(builder.relative('..', from: '/foo/bar'), equals('../../root'));
+
+ expect(builder.relative('http://dartlang.org/foo/bar/baz',
+ from: 'foo/bar'),
+ equals('../../../../foo/bar/baz'));
+ expect(builder.relative('file:///foo/bar/baz', from: 'foo/bar'),
+ equals('file:///foo/bar/baz'));
+ expect(builder.relative('/foo/bar/baz', from: 'foo/bar'),
+ equals('../../../../foo/bar/baz'));
+
+ expect(builder.relative('..', from: 'foo/bar'), equals('../../..'));
+ });
+
+ test('with a root parameter and a relative root', () {
+ var r = new path.Builder(style: path.Style.url, root: 'relative/root');
+ expect(r.relative('/foo/bar/baz', from: '/foo/bar'), equals('baz'));
+ expect(
+ r.relative('/foo/bar/baz', from: 'http://dartlang.org/foo/bar'),
+ equals('/foo/bar/baz'));
+ expect(
+ r.relative('http://dartlang.org/foo/bar/baz', from: '/foo/bar'),
+ equals('http://dartlang.org/foo/bar/baz'));
+ expect(r.relative('http://dartlang.org/foo/bar/baz',
+ from: 'file:///foo/bar'),
+ equals('http://dartlang.org/foo/bar/baz'));
+ expect(r.relative('http://dartlang.org/foo/bar/baz',
+ from: 'http://dartlang.org/foo/bar'), equals('baz'));
+
+ expect(r.relative('http://dartlang.org/foo/bar/baz', from: 'foo/bar'),
+ equals('http://dartlang.org/foo/bar/baz'));
+ expect(r.relative('file:///foo/bar/baz', from: 'foo/bar'),
+ equals('file:///foo/bar/baz'));
+ expect(r.relative('/foo/bar/baz', from: 'foo/bar'),
+ equals('/foo/bar/baz'));
+
+ expect(r.relative('..', from: 'foo/bar'), equals('../../..'));
+ });
+
+ test('from a . root', () {
+ var r = new path.Builder(style: path.Style.url, root: '.');
+ expect(r.relative('http://dartlang.org/foo/bar/baz'),
+ equals('http://dartlang.org/foo/bar/baz'));
+ expect(r.relative('file:///foo/bar/baz'), equals('file:///foo/bar/baz'));
+ expect(r.relative('/foo/bar/baz'), equals('/foo/bar/baz'));
+ expect(r.relative('foo/bar/baz'), equals('foo/bar/baz'));
+ });
+ });
+
+ group('resolve', () {
+ test('allows up to seven parts', () {
+ expect(builder.resolve('a'), 'http://dartlang.org/root/path/a');
+ expect(builder.resolve('a', 'b'), 'http://dartlang.org/root/path/a/b');
+ expect(builder.resolve('a', 'b', 'c'),
+ 'http://dartlang.org/root/path/a/b/c');
+ expect(builder.resolve('a', 'b', 'c', 'd'),
+ 'http://dartlang.org/root/path/a/b/c/d');
+ expect(builder.resolve('a', 'b', 'c', 'd', 'e'),
+ 'http://dartlang.org/root/path/a/b/c/d/e');
+ expect(builder.resolve('a', 'b', 'c', 'd', 'e', 'f'),
+ 'http://dartlang.org/root/path/a/b/c/d/e/f');
+ expect(builder.resolve('a', 'b', 'c', 'd', 'e', 'f', 'g'),
+ 'http://dartlang.org/root/path/a/b/c/d/e/f/g');
+ });
+
+ test('does not add separator if a part ends in one', () {
+ expect(builder.resolve('a/', 'b', 'c/', 'd'),
+ 'http://dartlang.org/root/path/a/b/c/d');
+ expect(builder.resolve(r'a\', 'b'),
+ r'http://dartlang.org/root/path/a\/b');
+ });
+
+ test('ignores parts before an absolute path', () {
+ expect(builder.resolve('a', '/b', '/c', 'd'), 'http://dartlang.org/c/d');
+ expect(builder.resolve('a', '/b', 'file:///c', 'd'), 'file:///c/d');
+ expect(builder.resolve('a', r'c:\b', 'c', 'd'),
+ r'http://dartlang.org/root/path/a/c:\b/c/d');
+ expect(builder.resolve('a', r'\\b', 'c', 'd'),
+ r'http://dartlang.org/root/path/a/\\b/c/d');
+ });
+ });
+
+ test('withoutExtension', () {
+ expect(builder.withoutExtension(''), '');
+ expect(builder.withoutExtension('a'), 'a');
+ expect(builder.withoutExtension('.a'), '.a');
+ expect(builder.withoutExtension('a.b'), 'a');
+ expect(builder.withoutExtension('a/b.c'), 'a/b');
+ expect(builder.withoutExtension('a/b.c.d'), 'a/b.c');
+ expect(builder.withoutExtension('a/'), 'a/');
+ expect(builder.withoutExtension('a/b/'), 'a/b/');
+ expect(builder.withoutExtension('a/.'), 'a/.');
+ expect(builder.withoutExtension('a/.b'), 'a/.b');
+ expect(builder.withoutExtension('a.b/c'), 'a.b/c');
+ expect(builder.withoutExtension(r'a.b\c'), r'a');
+ expect(builder.withoutExtension(r'a/b\c'), r'a/b\c');
+ expect(builder.withoutExtension(r'a/b\c.d'), r'a/b\c');
+ expect(builder.withoutExtension('a/b.c/'), 'a/b/');
+ expect(builder.withoutExtension('a/b.c//'), 'a/b//');
+ });
+
+
+ test('fromUri', () {
+ expect(builder.fromUri(Uri.parse('http://dartlang.org/path/to/foo')),
+ 'http://dartlang.org/path/to/foo');
+ expect(builder.fromUri(Uri.parse('http://dartlang.org/path/to/foo/')),
+ 'http://dartlang.org/path/to/foo/');
+ expect(builder.fromUri(Uri.parse('file:///path/to/foo')),
+ 'file:///path/to/foo');
+ expect(builder.fromUri(Uri.parse('foo/bar')), 'foo/bar');
+ expect(builder.fromUri(Uri.parse('http://dartlang.org/path/to/foo%23bar')),
+ 'http://dartlang.org/path/to/foo%23bar');
+ });
+
+ test('toUri', () {
+ expect(builder.toUri('http://dartlang.org/path/to/foo'),
+ Uri.parse('http://dartlang.org/path/to/foo'));
+ expect(builder.toUri('http://dartlang.org/path/to/foo/'),
+ Uri.parse('http://dartlang.org/path/to/foo/'));
+ expect(builder.toUri('file:///path/to/foo'),
+ Uri.parse('file:///path/to/foo'));
+ expect(builder.toUri('foo/bar'), Uri.parse('foo/bar'));
+ expect(builder.toUri('http://dartlang.org/path/to/foo%23bar'),
+ Uri.parse('http://dartlang.org/path/to/foo%23bar'));
+ });
+}
diff --git a/test/path_windows_test.dart b/test/windows_test.dart
similarity index 89%
rename from test/path_windows_test.dart
rename to test/windows_test.dart
index 8164224..7b3cf60 100644
--- a/test/path_windows_test.dart
+++ b/test/windows_test.dart
@@ -2,9 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-library path_test;
-
-import 'dart:io' as io;
+library path.test.windows_test;
import 'package:unittest/unittest.dart';
import 'package:path/path.dart' as path;
@@ -333,6 +331,12 @@
expect(builder.relative(r'..\a\b.txt'), r'..\a\b.txt');
expect(builder.relative(r'a\.\b\..\c.txt'), r'a\c.txt');
});
+
+ // Regression
+ test('from root-only path', () {
+ expect(builder.relative(r'C:\', from: r'C:\'), '.');
+ expect(builder.relative(r'C:\root\path', from: r'C:\'), r'root\path');
+ });
});
group('from relative root', () {
@@ -385,6 +389,12 @@
expect(builder.relative(r'D:\a\b'), r'D:\a\b');
expect(builder.relative(r'\\a\b'), r'\\a\b');
});
+
+ test('from a . root', () {
+ var r = new path.Builder(style: path.Style.windows, root: '.');
+ expect(r.relative(r'C:\foo\bar\baz'), equals(r'C:\foo\bar\baz'));
+ expect(r.relative(r'foo\bar\baz'), equals(r'foo\bar\baz'));
+ });
});
group('resolve', () {
@@ -431,4 +441,38 @@
expect(builder.withoutExtension(r'a.b/c'), r'a.b/c');
expect(builder.withoutExtension(r'a\b.c\'), r'a\b\');
});
+
+ test('fromUri', () {
+ expect(builder.fromUri(Uri.parse('file:///C:/path/to/foo')),
+ r'C:\path\to\foo');
+ expect(builder.fromUri(Uri.parse('file://hostname/path/to/foo')),
+ r'\\hostname\path\to\foo');
+ expect(builder.fromUri(Uri.parse('file:///C:/')), r'C:\');
+ expect(builder.fromUri(Uri.parse('file://hostname/')), r'\\hostname\');
+ expect(builder.fromUri(Uri.parse('foo/bar')), r'foo\bar');
+ expect(builder.fromUri(Uri.parse('/C:/path/to/foo')), r'C:\path\to\foo');
+ expect(builder.fromUri(Uri.parse('///C:/path/to/foo')), r'C:\path\to\foo');
+ expect(builder.fromUri(Uri.parse('//hostname/path/to/foo')),
+ r'\\hostname\path\to\foo');
+ expect(builder.fromUri(Uri.parse('file:///C:/path/to/foo%23bar')),
+ r'C:\path\to\foo#bar');
+ expect(builder.fromUri(Uri.parse('file://hostname/path/to/foo%23bar')),
+ r'\\hostname\path\to\foo#bar');
+ expect(() => builder.fromUri(Uri.parse('http://dartlang.org')),
+ throwsArgumentError);
+ });
+
+ test('toUri', () {
+ expect(builder.toUri(r'C:\path\to\foo'),
+ Uri.parse('file:///C:/path/to/foo'));
+ expect(builder.toUri(r'C:\path\to\foo\'),
+ Uri.parse('file:///C:/path/to/foo/'));
+ expect(builder.toUri(r'C:\'), Uri.parse('file:///C:/'));
+ expect(builder.toUri(r'\\hostname\'), Uri.parse('file://hostname/'));
+ expect(builder.toUri(r'foo\bar'), Uri.parse('foo/bar'));
+ expect(builder.toUri(r'C:\path\to\foo#bar'),
+ Uri.parse('file:///C:/path/to/foo%23bar'));
+ expect(builder.toUri(r'\\hostname\path\to\foo#bar'),
+ Uri.parse('file://hostname/path/to/foo%23bar'));
+ });
}