diff --git a/pkgs/path/lib/src/context.dart b/pkgs/path/lib/src/context.dart index 7a88909f..e9237a2b 100644 --- a/pkgs/path/lib/src/context.dart +++ b/pkgs/path/lib/src/context.dart @@ -148,10 +148,7 @@ class Context { /// context.rootPrefix('path/to/foo'); // -> '' /// context.rootPrefix('http://dartlang.org/path/to/foo'); /// // -> 'http://dartlang.org' - String rootPrefix(String path) { - var root = _parse(path).root; - return root == null ? '' : root; - } + String rootPrefix(String path) => path.substring(0, style.rootLength(path)); /// Returns `true` if [path] is an absolute path and `false` if it is a /// relative path. @@ -165,7 +162,7 @@ class Context { /// 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; + bool isAbsolute(String path) => style.rootLength(path) > 0; /// 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 @@ -181,7 +178,7 @@ class Context { /// them. They can be detected using [isRootRelative]. /// /// No POSIX and Windows paths are root-relative. - bool isRootRelative(String path) => _parse(path).isRootRelative; + bool isRootRelative(String path) => style.isRootRelative(path); /// Joins the given path parts into a single path. Example: /// diff --git a/pkgs/path/lib/src/internal_style.dart b/pkgs/path/lib/src/internal_style.dart index 67b5d343..db2d3462 100644 --- a/pkgs/path/lib/src/internal_style.dart +++ b/pkgs/path/lib/src/internal_style.dart @@ -33,14 +33,25 @@ abstract class InternalStyle extends Style { /// "usr", an additional "/" is needed (making "file:///usr"). bool needsSeparator(String path); + /// Returns the number of characters of the root part. + /// + /// Returns 0 if the path is relative. + /// + /// If the path is root-relative, the root length is 1. + int rootLength(String path); + /// Gets the root prefix of [path] if path is absolute. If [path] is relative, /// returns `null`. - String getRoot(String path); + String getRoot(String path) { + var length = rootLength(path); + if (length > 0) return path.substring(0, length); + return isRootRelative(path) ? path[0] : null; + } - /// Gets the root prefix of [path] if it's root-relative. + /// Returns whether [path] is root-relative. /// - /// If [path] is relative or absolute and not root-relative, returns `null`. - String getRelativeRoot(String path); + /// If [path] is relative or absolute and not root-relative, returns `false`. + bool isRootRelative(String path); /// Returns the path represented by [uri] in this style. String pathFromUri(Uri uri); diff --git a/pkgs/path/lib/src/parsed_path.dart b/pkgs/path/lib/src/parsed_path.dart index 57773eee..a7b0afd3 100644 --- a/pkgs/path/lib/src/parsed_path.dart +++ b/pkgs/path/lib/src/parsed_path.dart @@ -45,7 +45,7 @@ class ParsedPath { // Remove the root prefix, if any. var root = style.getRoot(path); - var isRootRelative = style.getRelativeRoot(path) != null; + var isRootRelative = style.isRootRelative(path); if (root != null) path = path.substring(root.length); // Split the parts on path separators. diff --git a/pkgs/path/lib/src/style/posix.dart b/pkgs/path/lib/src/style/posix.dart index b8b82b40..74aeb4c5 100644 --- a/pkgs/path/lib/src/style/posix.dart +++ b/pkgs/path/lib/src/style/posix.dart @@ -30,11 +30,13 @@ class PosixStyle extends InternalStyle { bool needsSeparator(String path) => path.isNotEmpty && !isSeparator(path.codeUnitAt(path.length - 1)); - String getRoot(String path) { - if (path.isNotEmpty && isSeparator(path.codeUnitAt(0))) return '/'; - return null; + int rootLength(String path) { + if (path.isNotEmpty && isSeparator(path.codeUnitAt(0))) return 1; + return 0; } + bool isRootRelative(String path) => false; + String getRelativeRoot(String path) => null; String pathFromUri(Uri uri) { diff --git a/pkgs/path/lib/src/style/url.dart b/pkgs/path/lib/src/style/url.dart index f383923d..d5d0fdb8 100644 --- a/pkgs/path/lib/src/style/url.dart +++ b/pkgs/path/lib/src/style/url.dart @@ -36,53 +36,30 @@ class UrlStyle extends InternalStyle { // A URI that's just "scheme://" needs an extra separator, despite ending // with "/". - var root = _getRoot(path); - return root != null && root.endsWith('://'); + return path.endsWith("://") && rootLength(path) == path.length; } - String getRoot(String path) { - var root = _getRoot(path); - return root == null ? getRelativeRoot(path) : root; + int rootLength(String path) { + if (path.isEmpty) return 0; + if (isSeparator(path.codeUnitAt(0))) return 1; + var index = path.indexOf("/"); + if (index > 0 && path.startsWith('://', index - 1)) { + // The root part is up until the next '/', or the full path. Skip + // '://' and search for '/' after that. + index = path.indexOf('/', index + 2); + if (index > 0) return index; + return path.length; + } + return 0; } - String getRelativeRoot(String path) { - if (path.isEmpty) return null; - return isSeparator(path.codeUnitAt(0)) ? "/" : null; - } + bool isRootRelative(String path) => + path.isNotEmpty && isSeparator(path.codeUnitAt(0)); + + String getRelativeRoot(String path) => isRootRelative(path) ? '/' : null; String pathFromUri(Uri uri) => uri.toString(); Uri relativePathToUri(String path) => Uri.parse(path); Uri absolutePathToUri(String path) => Uri.parse(path); - - // A helper method for [getRoot] that doesn't handle relative roots. - String _getRoot(String path) { - if (path.isEmpty) return null; - - // We aren't using a RegExp for this because they're slow (issue 19090). If - // we could, we'd match against r"[a-zA-Z][-+.a-zA-Z\d]*://[^/]*". - - if (!isAlphabetic(path.codeUnitAt(0))) return null; - var start = 1; - for (; start < path.length; start++) { - var char = path.codeUnitAt(start); - if (isAlphabetic(char)) continue; - if (isNumeric(char)) continue; - if (char == chars.MINUS || char == chars.PLUS || char == chars.PERIOD) { - continue; - } - - break; - } - - if (start + 3 > path.length) return null; - if (path.substring(start, start + 3) != '://') return null; - start += 3; - - // A URL root can end with a non-"/" prefix. - while (start < path.length && !isSeparator(path.codeUnitAt(start))) { - start++; - } - return path.substring(0, start); - } } diff --git a/pkgs/path/lib/src/style/windows.dart b/pkgs/path/lib/src/style/windows.dart index 2965f1ee..16e14d59 100644 --- a/pkgs/path/lib/src/style/windows.dart +++ b/pkgs/path/lib/src/style/windows.dart @@ -34,16 +34,38 @@ class WindowsStyle extends InternalStyle { return !isSeparator(path.codeUnitAt(path.length - 1)); } - String getRoot(String path) { - var root = _getRoot(path); - return root == null ? getRelativeRoot(path) : root; + int rootLength(String path) { + if (path.isEmpty) return 0; + if (path.codeUnitAt(0) == chars.SLASH) return 1; + if (path.codeUnitAt(0) == chars.BACKSLASH) { + if (path.length < 2 || path.codeUnitAt(1) != chars.BACKSLASH) return 1; + // The path is a network share. Search for up to two '\'s, as they are + // the server and share - and part of the root part. + var index = path.indexOf('\\', 2); + if (index > 0) { + index = path.indexOf('\\', index + 1); + if (index > 0) return index; + } + return path.length; + } + // If the path is of the form 'C:/' or 'C:\', with C being any letter, it's + // a root part. + if (path.length < 3) return 0; + // Check for the letter. + if (!isAlphabetic(path.codeUnitAt(0))) return 0; + // Check for the ':'. + if (path.codeUnitAt(1) != chars.COLON) return 0; + // Check for either '/' or '\'. + if (!isSeparator(path.codeUnitAt(2))) return 0; + return 3; } + bool isRootRelative(String path) => rootLength(path) == 1; + String getRelativeRoot(String path) { - if (path.isEmpty) return null; - if (!isSeparator(path.codeUnitAt(0))) return null; - if (path.length > 1 && isSeparator(path.codeUnitAt(1))) return null; - return path[0]; + var length = rootLength(path); + if (length == 1) return path[0]; + return null; } String pathFromUri(Uri uri) { @@ -100,39 +122,4 @@ class WindowsStyle extends InternalStyle { return new Uri(scheme: 'file', pathSegments: parsed.parts); } } - - // A helper method for [getRoot] that doesn't handle relative roots. - String _getRoot(String path) { - if (path.length < 3) return null; - - // We aren't using a RegExp for this because they're slow (issue 19090). If - // we could, we'd match against r'^(\\\\[^\\]+\\[^\\/]+|[a-zA-Z]:[/\\])'. - - // Try roots like "C:\". - if (isAlphabetic(path.codeUnitAt(0))) { - if (path.codeUnitAt(1) != chars.COLON) return null; - if (!isSeparator(path.codeUnitAt(2))) return null; - return path.substring(0, 3); - } - - // Try roots like "\\server\share". - if (!path.startsWith('\\\\')) return null; - - var start = 2; - // The server is one or more non-"\" characters. - while (start < path.length && path.codeUnitAt(start) != chars.BACKSLASH) { - start++; - } - if (start == 2 || start == path.length) return null; - - // The share is one or more non-"\" characters. - start += 1; - if (path.codeUnitAt(start) == chars.BACKSLASH) return null; - start += 1; - while (start < path.length && path.codeUnitAt(start) != chars.BACKSLASH) { - start++; - } - - return path.substring(0, start); - } -} \ No newline at end of file +} diff --git a/pkgs/path/test/url_test.dart b/pkgs/path/test/url_test.dart index 27691d85..d75377e5 100644 --- a/pkgs/path/test/url_test.dart +++ b/pkgs/path/test/url_test.dart @@ -37,6 +37,7 @@ main() { expect(context.rootPrefix('http://dartlang.org'), 'http://dartlang.org'); expect(context.rootPrefix('file://'), 'file://'); expect(context.rootPrefix('/'), '/'); + expect(context.rootPrefix('foo/bar://'), ''); }); test('dirname', () { diff --git a/pkgs/path/test/windows_test.dart b/pkgs/path/test/windows_test.dart index 7c16e319..72eefc3f 100644 --- a/pkgs/path/test/windows_test.dart +++ b/pkgs/path/test/windows_test.dart @@ -41,6 +41,9 @@ main() { expect(context.rootPrefix('C:\\'), r'C:\'); expect(context.rootPrefix('C:/'), 'C:/'); expect(context.rootPrefix(r'\\server\share\a\b'), r'\\server\share'); + expect(context.rootPrefix(r'\\server\share'), r'\\server\share'); + expect(context.rootPrefix(r'\\server\'), r'\\server\'); + expect(context.rootPrefix(r'\\server'), r'\\server'); expect(context.rootPrefix(r'\a\b'), r'\'); expect(context.rootPrefix(r'/a/b'), r'/'); expect(context.rootPrefix(r'\'), r'\');