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

Unified Diff: runtime/bin/path_impl.dart

Issue 10417053: Add Path class to dart:io, and add unit tests for it. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Address comments, finish implementation (mostly). Created 8 years, 7 months 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 side-by-side diff with in-line comments
Download patch
Index: runtime/bin/path_impl.dart
diff --git a/runtime/bin/path_impl.dart b/runtime/bin/path_impl.dart
new file mode 100644
index 0000000000000000000000000000000000000000..14622c64682ee086c4f78545d25cbaf7b67d8c59
--- /dev/null
+++ b/runtime/bin/path_impl.dart
@@ -0,0 +1,212 @@
+// 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.
+
+class _PathImpl implements Path {
+ final String path;
+
+ const _PathImpl(String source) : path = source;
+ _PathImpl.fromNative(String source) : path = _clean(source);
+
+ static String _clean(String source) {
+ switch (Platform.operatingSystem) {
+ case 'windows':
+ return _cleanWindows(source);
+ default:
+ return source;
+ }
+ }
+
+ static String _cleanWindows(source) {
+ // Change \ to /.
+ var clean = source.replaceAll('\\', '/');
+ // Add / before intial [Drive letter]:
+ if (clean.length >= 2 && clean[1] == ':') {
+ clean = '/$clean';
+ }
+ return clean;
+ }
+
+ bool get isEmpty() => path == '';
Mads Ager (google) 2012/06/01 08:11:31 bool get isEmpty() => path.isEmpty();
+ bool get isAbsolute() => path.startsWith('/');
+ bool get hasTrailingSlash() => path.endsWith('/');
+
+ String toString() => path;
+
+ Path relativeTo(Path base) {
Mads Ager (google) 2012/06/01 08:11:31 This is not in the interface. Remove for now and a
+ // Throws exception if not doable.
+ // Unimplemented
+ if (base.isAbsolute && path.startsWith(base.path)) {
+ if (path == base.path) return new Path('.');
+ if (path[base.path.length] == '/') {
+ return new Path(path.substring(base.path.length + 1));
+ }
+ }
+ throw new UnimplementedException(
+ "Unimplemented case of Path.relativeTo(base):\n"
+ " Only absolute paths with strict containment are handled at present.\n"
+ " Arguments: $path.relativeTo($base)");
+ }
+
+ Path join(Path further) {
+ if (further.isAbsolute) {
+ throw new IllegalArgumentException(
Mads Ager (google) 2012/06/01 08:11:31 This is not documented in the interface.
+ "Path.join called with absolute Path as argument.");
+ }
+ if (isEmpty) {
+ return further.canonicalize();
+ }
+ // We do not drop everything after the last / in the base (this).
+ if (hasTrailingSlash) {
+ return new Path('$path${further.path}').canonicalize();
+ }
+ return new Path('$path/$further.path').canonicalize();
+ }
+
+ Path safeJoin(Path further) {
+ further = further.canonicalize();
+ if (further.toString() == '..' || further.toString().startsWith('../')) {
+ throw new IllegalArgumentException(
+ 'Path.safeJoin called with argument $further, which starts with ..');
+ } else {
+ return join(further);
+ }
+ }
+
+ // Note: The URI RFC names for these operations are normalize, resolve, and
+ // relativize.
+ Path canonicalize() {
+ if (isCanonical) return this;
+ return makeCanonical();
+ }
+
+ bool get isCanonical() {
+ // Contains no consecutive /s.
Mads Ager (google) 2012/06/01 08:11:31 /s -> path separators.
Bill Hesse 2012/06/15 14:23:08 Done.
+ // Contains no . components.
+ // Absolute paths have no .. components.
+ // All .. components of a relative path are initial.
+ List components = path.split('/');
Mads Ager (google) 2012/06/01 08:11:31 segments and isAbsolute instead of split and check
Bill Hesse 2012/06/15 14:23:08 Yes, I was just optimizing. This avoids a removeR
+ if (components[0] == '') { // Absolute path
+ components[0] = 'Okay';
Mads Ager (google) 2012/06/01 08:11:31 WAT?
Bill Hesse 2012/06/15 14:23:08 Done.
+ } else { // Relative path starting with .. components.
+ for (int pos = 0;
+ pos < components.length && components[pos] == '..';
+ ++pos) {
+ components[pos] = 'Okay';
Mads Ager (google) 2012/06/01 08:11:31 Ditto?
Bill Hesse 2012/06/15 14:23:08 Done.
+ }
+ }
+ if (components.isEmpty()) return true;
Mads Ager (google) 2012/06/01 08:11:31 ? You have just accessed components[0]. We need mo
Bill Hesse 2012/06/15 14:23:08 Done.
+ if (components.last() == '') components.removeLast(); // Path ends with /.
+ // No remaining components can be ., .., or empty.
+ return !components.some((c) => c == '..' || c == '.' || c == '');
+ }
+
+ Path makeCanonical() {
+ bool isAbs = isAbsolute;
Mads Ager (google) 2012/06/01 08:11:31 Why? Isn't 'isAbsolute' a fine name?
Bill Hesse 2012/06/15 14:23:08 I was just optimizing. It is used about 5 times.
+ List components = path.split('/');
Mads Ager (google) 2012/06/01 08:11:31 path.split -> segments?
Bill Hesse 2012/06/15 14:23:08 Done.
+ Expect.isNotNull(components);
Mads Ager (google) 2012/06/01 08:11:31 If this is needed here it probably is elsewhere to
Bill Hesse 2012/06/15 14:23:08 Done.
+ String drive;
+ if (isAbs) {
+ components.removeRange(0, 1);
+ }
+ if (isAbs &&
Mads Ager (google) 2012/06/01 08:11:31 Move this if inside the if above instead of repeat
Bill Hesse 2012/06/15 14:23:08 This is only hit in the exact case it needs to be
+ !components.isEmpty() &&
+ components[0].length == 2 &&
+ components[0][1] == ':') {
+ drive = components[0];
+ components.removeRange(0, 1);
+ }
+ List newComponents = [];
+ for (String segment in components) {
+ switch (segment) {
+ case '..':
+ // Absolute paths drop leading .. markers, including after a drive.
+ if (newComponents.isEmpty()) {
+ if (isAbs) {
+ // Do nothing: drop the segment.
+ } else {
+ newComponents.add('..');
+ }
+ } else if (newComponents.last() == '..') {
+ newComponents.add('..');
+ } else {
+ newComponents.removeLast();
+ }
+ break;
+ case '.':
+ case '':
+ // Do nothing - drop the segment.
+ break;
+ default:
+ newComponents.add(segment);
+ break;
+ }
+ }
+
+ List segmentsToJoin = [];
+ if (isAbs) {
+ segmentsToJoin.add('');
+ if (drive != null) {
+ segmentsToJoin.add(drive);
+ }
+ }
+ if (newComponents.isEmpty()) {
+ if (isAbs) {
+ segmentsToJoin.add('');
+ } else {
+ segmentsToJoin.add('.');
+ }
+ } else {
+ segmentsToJoin.addAll(newComponents);
+ if (hasTrailingSlash) {
+ segmentsToJoin.add('');
+ }
+ }
+ return new Path(Strings.join(segmentsToJoin, '/'));
+ }
+
+
+ String toNativePath() {
+ if (Platform.operatingSystem == 'windows') {
+ String nativePath = path;
+ // Drop '/' before a drive letter.
+ if (nativePath.startsWith('/') && nativePath[2] == ':') {
+ nativePath = nativePath.substring(1);
+ }
+ nativePath = nativePath.replace('/', '\\');
+ return nativePath;
+ }
+ return path;
+ }
+
+ List<String> segments() {
+ List result = path.split('/');
+ if (isAbsolute) result.removeRange(0, 1);
+ if (hasTrailingSlash) result.removeLast();
+ return result;
+ }
+
+ String get filenameWithoutExtension() {
+ var name = filename;
+ int pos = name.lastIndexOf('.');
+ return (pos < 0) ? name : name.substring(0, pos);
+ }
+
+ String get extension() {
+ var name = filename;
+ int pos = name.lastIndexOf('.');
+ return (pos < 0) ? '' : name.substring(pos + 1);
+ }
+
+ Path get directoryPath() {
+ int pos = path.lastIndexOf('/');
+ if (pos < 0) return new Path('');
+ while (pos > 0 && path[pos - 1] == '/') --pos;
+ return new Path((pos > 0) ? path.substring(0, pos) : '/');
+ }
+
+ String get filename() {
+ int pos = path.lastIndexOf('/');
+ return path.substring(pos + 1);
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698