Optimize absolute() and normalize().
This short-circuits absolute() if the path is already absolute.
Detecting absolute paths is very fast, whereas join() is relatively
slow.
For normalize(), this runs a single-pass check over a path to see
whether it needs to be normalized at all. This check isn't free, but
it's a lot less costly than parsing and reserializing the path, and it
covers the common case.
See dart-lang/glob#24962
R=rnystrom@google.com
Review URL: https://codereview.chromium.org//1455003002 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 457be80..1dfc09a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.3.7
+
+* Improve the performance of `absolute()` and `normalize()`.
+
## 1.3.6
* Ensure that `path.toUri` preserves trailing slashes for relative paths.
diff --git a/benchmark/benchmark.dart b/benchmark/benchmark.dart
index 419eee0..03871d0 100644
--- a/benchmark/benchmark.dart
+++ b/benchmark/benchmark.dart
@@ -33,6 +33,7 @@
}
}
+ benchmark('absolute', context.absolute);
benchmark('basename', context.basename);
benchmark('basenameWithoutExtension', context.basenameWithoutExtension);
benchmark('dirname', context.dirname);
diff --git a/lib/src/context.dart b/lib/src/context.dart
index db055a1..44bdde9 100644
--- a/lib/src/context.dart
+++ b/lib/src/context.dart
@@ -4,6 +4,7 @@
library path.context;
+import 'characters.dart' as chars;
import 'internal_style.dart';
import 'style.dart';
import 'parsed_path.dart';
@@ -73,6 +74,15 @@
/// If [current] isn't absolute, this won't return an absolute path.
String absolute(String part1, [String part2, String part3, String part4,
String part5, String part6, String part7]) {
+ _validateArgList(
+ "absolute", [part1, part2, part3, part4, part5, part6, part7]);
+
+ // If there's a single absolute path, just return it. This is a lot faster
+ // for the common case of `p.absolute(path)`.
+ if (part2 == null && isAbsolute(part1) && !isRootRelative(part1)) {
+ return part1;
+ }
+
return join(current, part1, part2, part3, part4, part5, part6, part7);
}
@@ -295,11 +305,79 @@
///
/// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
String normalize(String path) {
+ if (!_needsNormalization(path)) return path;
+
var parsed = _parse(path);
parsed.normalize();
return parsed.toString();
}
+ /// Returns whether [path] needs to be normalized.
+ bool _needsNormalization(String path) {
+ var start = 0;
+ var codeUnits = path.codeUnits;
+ var previousPrevious;
+ var previous;
+
+ // Skip past the root before we start looking for snippets that need
+ // normalization. We want to normalize "//", but not when it's part of
+ // "http://".
+ var root = style.rootLength(path);
+ if (root != 0) {
+ start = root;
+ previous = chars.SLASH;
+
+ // On Windows, the root still needs to be normalized if it contains a
+ // forward slash.
+ if (style == Style.windows) {
+ for (var i = 0; i < root; i++) {
+ if (codeUnits[i] == chars.SLASH) return true;
+ }
+ }
+ }
+
+ for (var i = start; i < codeUnits.length; i++) {
+ var codeUnit = codeUnits[i];
+ if (style.isSeparator(codeUnit)) {
+ // Forward slashes in Windows paths are normalized to backslashes.
+ if (style == Style.windows && codeUnit == chars.SLASH) return true;
+
+ // Multiple separators are normalized to single separators.
+ if (previous != null && style.isSeparator(previous)) return true;
+
+ // Single dots and double dots are normalized to directory traversals.
+ //
+ // This can return false positives for ".../", but that's unlikely
+ // enough that it's probably not going to cause performance issues.
+ if (previous == chars.PERIOD &&
+ (previousPrevious == null ||
+ previousPrevious == chars.PERIOD ||
+ style.isSeparator(previousPrevious))) {
+ return true;
+ }
+ }
+
+ previousPrevious = previous;
+ previous = codeUnit;
+ }
+
+ // Empty paths are normalized to ".".
+ if (previous == null) return true;
+
+ // Trailing separators are removed.
+ if (style.isSeparator(previous)) return true;
+
+ // Single dots and double dots are normalized to directory traversals.
+ if (previous == chars.PERIOD &&
+ (previousPrevious == null ||
+ previousPrevious == chars.SLASH ||
+ previousPrevious == chars.PERIOD)) {
+ return true;
+ }
+
+ return false;
+ }
+
/// Attempts to convert [path] to an equivalent relative path relative to
/// [root].
///
diff --git a/pubspec.yaml b/pubspec.yaml
index e669844..bc7ab98 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: path
-version: 1.3.7-dev
+version: 1.3.7
author: Dart Team <misc@dartlang.org>
description: >
A string-based path manipulation library. All of the path operations you know