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

Side by Side Diff: utils/pub/path.dart

Issue 11475046: Add Bob's path library to pub. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Code review changes Created 8 years 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « utils/pub/io.dart ('k') | utils/pub/pub.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /// A comprehensive, cross-platform path manipulation library.
6 library path;
7
8 import 'dart:io' as io;
9
10 /// An internal builder for the current OS so we can provide a straight
11 /// functional interface and not require users to create one.
12 final _builder = new Builder();
13
14 /// Gets the path to the current working directory.
15 String get current => new io.Directory.current().path;
16
17 /// Gets the path separator for the current platform. On Mac and Linux, this
18 /// is `/`. On Windows, it's `\`.
19 String get separator => _builder.separator;
20
21 /// Converts [path] to an absolute path by resolving it relative to the current
22 /// working directory. If [path] is already an absolute path, just returns it.
23 ///
24 /// path.absolute('foo/bar.txt'); // -> /your/current/dir/foo/bar.txt
25 String absolute(String path) => join(current, path);
26
27 /// Gets the file extension of [path]; the portion after the last `.` in the
28 /// [basename] of the path.
29 ///
30 /// path.extension('path/to/foo.dart'); // -> '.dart'
31 /// path.extension('path/to/foo'); // -> ''
32 /// path.extension('path.to/foo'); // -> ''
33 /// path.extension('path/to/foo.dart.js'); // -> '.js'
34 ///
35 /// If the file name starts with a `.`, then it is not considered an extension:
36 ///
37 /// path.extension('~/.bashrc'); // -> ''
38 String extension(String path) => _builder.extension(path);
39
40 /// Gets the part of [path] after the last separator on the current platform.
41 ///
42 /// path.filename('path/to/foo.dart'); // -> 'foo.dart'
43 /// path.filename('path/to'); // -> 'to'
44 String filename(String path) => _builder.filename(path);
45
46 /// Gets the part of [path] after the last separator on the current platform,
47 /// and without any trailing file extension.
48 ///
49 /// path.filenameWithoutExtension('path/to/foo.dart'); // -> 'foo'
50 String filenameWithoutExtension(String path) =>
51 _builder.filenameWithoutExtension(path);
52
53 /// Returns `true` if [path] is an absolute path and `false` if it is a
54 /// relative path. On Mac and Unix systems, relative paths start with a `/`
55 /// (forward slash). On Windows, an absolute path starts with `\\`, or a drive
56 /// letter followed by `:/` or `:\`.
57 bool isAbsolute(String path) => _builder.isAbsolute(path);
58
59 /// Returns `true` if [path] is a relative path and `false` if it is absolute.
60 /// On Mac and Unix systems, relative paths start with a `/` (forward slash).
61 /// On Windows, an absolute path starts with `\\`, or a drive letter followed
62 /// by `:/` or `:\`.
63 bool isRelative(String path) => _builder.isRelative(path);
64
65 /// Joins the given path parts into a single path using the current platform's
66 /// [separator]. Example:
67 ///
68 /// path.join('path', 'to', 'foo'); // -> 'path/to/foo'
69 ///
70 /// If any part ends in a path separator, then a redundant separator will not
71 /// be added:
72 ///
73 /// path.join('path/', 'to', 'foo'); // -> 'path/to/foo
74 ///
75 /// If a part is an absolute path, then anything before that will be ignored:
76 ///
77 /// path.join('path', '/to', 'foo'); // -> '/to/foo'
78 ///
79 String join(String part1, [String part2, String part3, String part4,
80 String part5, String part6, String part7, String part8]) {
81 if (!?part2) return _builder.join(part1);
82 if (!?part3) return _builder.join(part1, part2);
83 if (!?part4) return _builder.join(part1, part2, part3);
84 if (!?part5) return _builder.join(part1, part2, part3, part4);
85 if (!?part6) return _builder.join(part1, part2, part3, part4, part5);
86 if (!?part7) return _builder.join(part1, part2, part3, part4, part5, part6);
87 if (!?part8) return _builder.join(part1, part2, part3, part4, part5, part6,
88 part7);
89 return _builder.join(part1, part2, part3, part4, part5, part6, part7, part8);
90 }
91
92 /// Normalizes [path], simplifying it by handling `..`, and `.`, and
93 /// removing redundant path separators whenever possible.
94 ///
95 /// path.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
96 String normalize(String path) => _builder.normalize(path);
97
98 /// Converts [path] to an equivalent relative path from the current directory.
99 ///
100 /// // Given current directory is /root/path:
101 /// path.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
102 /// path.relative('/root/other.dart'); // -> '../other.dart'
103 String relative(String path) => _builder.relative(path);
104
105 /// Removes a trailing extension from the last part of [path].
106 ///
107 /// withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
108 String withoutExtension(String path) => _builder.withoutExtension(path);
109
110 /// An instantiable class for manipulating paths. Unlike the top-level
111 /// functions, this lets you explicitly select what platform the paths will use.
112 class Builder {
113 /// Creates a new path builder for the given style and root directory.
114 ///
115 /// If [style] is omitted, it uses the host operating system's path style. If
116 /// [root] is omitted, it defaults to the current working directory.
117 factory Builder({Style style, String root}) {
118 if (style == null) {
119 if (io.Platform.operatingSystem == 'windows') {
120 style = Style.windows;
121 } else {
122 style = Style.posix;
123 }
124 }
125
126 if (root == null) root = new io.Directory.current().path;
127
128 return new Builder._(style, root);
129 }
130
131 Builder._(this.style, this.root);
132
133 /// The style of path that this builder works with.
134 final Style style;
135
136 /// The root directory that relative paths will be relative to.
137 final String root;
138
139 /// Gets the path separator for the builder's [style]. On Mac and Linux,
140 /// this is `/`. On Windows, it's `\`.
141 String get separator => style.separator;
142
143 /// Gets the file extension of [path]; the portion after the last `.` in the
144 /// [basename] of the path.
145 ///
146 /// builder.extension('path/to/foo.dart'); // -> '.dart'
147 /// builder.extension('path/to/foo'); // -> ''
148 /// builder.extension('path.to/foo'); // -> ''
149 /// builder.extension('path/to/foo.dart.js'); // -> '.js'
150 ///
151 /// If the file name starts with a `.`, then it is not considered an
152 /// extension:
153 ///
154 /// builder.extension('~/.bashrc'); // -> ''
155 String extension(String path) => _parse(path).extension;
156
157 /// Gets the part of [path] after the last separator on the builder's
158 /// platform.
159 ///
160 /// builder.filename('path/to/foo.dart'); // -> 'foo.dart'
161 /// builder.filename('path/to'); // -> 'to'
162 String filename(String path) => _parse(path).filename;
163
164 /// Gets the part of [path] after the last separator on the builder's
165 /// platform, and without any trailing file extension.
166 ///
167 /// builder.filenameWithoutExtension('path/to/foo.dart'); // -> 'foo'
168 String filenameWithoutExtension(String path) =>
169 _parse(path).filenameWithoutExtension;
170
171 /// Returns `true` if [path] is an absolute path and `false` if it is a
172 /// relative path. On Mac and Unix systems, relative paths start with a `/`
173 /// (forward slash). On Windows, an absolute path starts with `\\`, or a drive
174 /// letter followed by `:/` or `:\`.
175 bool isAbsolute(String path) => _parse(path).isAbsolute;
176
177 /// Returns `true` if [path] is a relative path and `false` if it is absolute.
178 /// On Mac and Unix systems, relative paths start with a `/` (forward slash).
179 /// On Windows, an absolute path starts with `\\`, or a drive letter followed
180 /// by `:/` or `:\`.
181 bool isRelative(String path) => !isAbsolute(path);
182
183 /// Joins the given path parts into a single path. Example:
184 ///
185 /// builder.join('path', 'to', 'foo'); // -> 'path/to/foo'
186 ///
187 /// If any part ends in a path separator, then a redundant separator will not
188 /// be added:
189 ///
190 /// builder.join('path/', 'to', 'foo'); // -> 'path/to/foo
191 ///
192 /// If a part is an absolute path, then anything before that will be ignored:
193 ///
194 /// builder.join('path', '/to', 'foo'); // -> '/to/foo'
195 ///
196 String join(String part1, [String part2, String part3, String part4,
197 String part5, String part6, String part7, String part8]) {
198 var buffer = new StringBuffer();
199 var needsSeparator = false;
200
201 addPart(condition, part) {
202 if (!condition) return;
203
204 if (this.isAbsolute(part)) {
205 // An absolute path discards everything before it.
206 buffer.clear();
207 buffer.add(part);
208 } else {
209 if (part.length > 0 && style.separatorPattern.hasMatch(part[0])) {
210 // The part starts with a separator, so we don't need to add one.
211 } else if (needsSeparator) {
212 buffer.add(separator);
213 }
214
215 buffer.add(part);
216 }
217
218 // Unless this part ends with a separator, we'll need to add one before
219 // the next part.
220 needsSeparator = part.length > 0 &&
221 !style.separatorPattern.hasMatch(part[part.length - 1]);
222 }
223
224 addPart(true, part1);
225 addPart(?part2, part2);
226 addPart(?part3, part3);
227 addPart(?part4, part4);
228 addPart(?part5, part5);
229 addPart(?part6, part6);
230 addPart(?part7, part7);
231 addPart(?part8, part8);
232
233 return buffer.toString();
234 }
235
236 /// Normalizes [path], simplifying it by handling `..`, and `.`, and
237 /// removing redundant path separators whenever possible.
238 ///
239 /// builder.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
240 String normalize(String path) {
241 if (path == '') return path;
242
243 var parsed = _parse(path);
244 parsed.normalize();
245 return parsed.toString();
246 }
247
248 /// Creates a new path by appending the given path parts to the [root].
249 /// Equivalent to [join()] with [root] as the first argument. Example:
250 ///
251 /// var builder = new Builder(root: 'root');
252 /// builder.join('path', 'to', 'foo'); // -> 'root/path/to/foo'
253 String resolve(String part1, [String part2, String part3, String part4,
254 String part5, String part6, String part7]) {
255 if (!?part2) return join(root, part1);
256 if (!?part3) return join(root, part1, part2);
257 if (!?part4) return join(root, part1, part2, part3);
258 if (!?part5) return join(root, part1, part2, part3, part4);
259 if (!?part6) return join(root, part1, part2, part3, part4, part5);
260 if (!?part7) return join(root, part1, part2, part3, part4, part5, part6);
261 return join(root, part1, part2, part3, part4, part5, part6, part7);
262 }
263
264 /// Converts [path] to an equivalent relative path starting at [root].
265 ///
266 /// var builder = new Builder(root: '/root/path');
267 /// builder.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
268 /// builder.relative('/root/other.dart'); // -> '../other.dart'
269 String relative(String path) {
270 // If the base path is relative, resolve it relative to the current
271 // directory.
272 var base = root;
273 if (this.isRelative(base)) base = absolute(base);
274
275 // If the given path is relative, resolve it relative to the base.
276 path = this.join(base, path);
277
278 var baseParsed = _parse(base)..normalize();
279 var pathParsed = _parse(path)..normalize();
280
281 // If the root prefixes don't match (for example, different drive letters
282 // on Windows), then there is no relative path, so just return the absolute
283 // one.
284 if (baseParsed.root != pathParsed.root) return pathParsed.toString();
285
286 // Strip off their common prefix.
287 while (baseParsed.parts.length > 0 && pathParsed.parts.length > 0) {
288 if (baseParsed.parts[0] != pathParsed.parts[0]) break;
289 baseParsed.parts.removeAt(0);
290 baseParsed.separators.removeAt(0);
291 pathParsed.parts.removeAt(0);
292 pathParsed.separators.removeAt(0);
293 }
294
295 // If there are any directories left in the root path, we need to walk up
296 // out of them.
297 pathParsed.parts.insertRange(0, baseParsed.parts.length, '..');
298 pathParsed.separators.insertRange(0, baseParsed.parts.length,
299 style.separator);
300
301 // Corner case: the paths completely collapsed.
302 if (pathParsed.parts.length == 0) return '.';
303
304 // Make it relative.
305 pathParsed.root = '';
306 pathParsed.removeTrailingSeparator();
307
308 return pathParsed.toString();
309 }
310
311 /// Removes a trailing extension from the last part of [path].
312 ///
313 /// builder.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
314 String withoutExtension(String path) {
315 var lastSeparator = path.lastIndexOf(separator);
316 var lastDot = path.lastIndexOf('.');
317
318 // Ignore '.' in anything but the last component.
319 if (lastSeparator != -1 && lastDot <= lastSeparator + 1) lastDot = -1;
320
321 if (lastDot <= 0) return path;
322 return path.substring(0, lastDot);
323 }
324
325 _ParsedPath _parse(String path) {
326 var before = path;
327
328 // Remove the root prefix, if any.
329 var root = style.getRoot(path);
330 if (root != null) path = path.substring(root.length);
331
332 // Split the parts on path separators.
333 var parts = [];
334 var separators = [];
335 var start = 0;
336 for (var match in style.separatorPattern.allMatches(path)) {
337 parts.add(path.substring(start, match.start));
338 separators.add(match[0]);
339 start = match.end;
340 }
341
342 // Add the final part, if any.
343 if (start < path.length) {
344 parts.add(path.substring(start));
345 separators.add('');
346 }
347
348 // Separate out the file extension.
349 var extension = '';
350 if (parts.length > 0) {
351 var file = parts.last;
352 if (file != '..') {
353 var lastDot = file.lastIndexOf('.');
354
355 // If there is a dot (and it's not the first character, like '.bashrc').
356 if (lastDot > 0) {
357 parts[parts.length - 1] = file.substring(0, lastDot);
358 extension = file.substring(lastDot);
359 }
360 }
361 }
362
363 return new _ParsedPath(style, root, parts, separators, extension);
364 }
365 }
366
367 /// An enum type describing a "flavor" of path.
368 class Style {
369 /// POSIX-style paths use "/" (forward slash) as separators. Absolute paths
370 /// start with "/". Used by UNIX, Linux, Mac OS X, and others.
371 static final posix = new Style._('posix', '/', '/', '/');
372
373 /// Windows paths use "\" (backslash) as separators. Absolute paths start with
374 /// a drive letter followed by a colon (example, "C:") or two backslashes
375 /// ("\\") for UNC paths.
376 static final windows = new Style._('windows', '\\', r'[/\\]',
377 r'\\\\|[a-zA-Z]:[/\\]');
378
379 Style._(this.name, this.separator, String separatorPattern, String rootPattern )
380 : separatorPattern = new RegExp(separatorPattern),
381 _rootPattern = new RegExp('^$rootPattern');
382
383 /// The name of this path style. Will be "posix" or "windows".
384 final String name;
385
386 /// The path separator for this style. On POSIX, this is `/`. On Windows,
387 /// it's `\`.
388 final String separator;
389
390 /// The [Pattern] that can be used to match a separator for a path in this
391 /// style. Windows allows both "/" and "\" as path separators even though
392 /// "\" is the canonical one.
393 final Pattern separatorPattern;
394
395 /// The [Pattern] that can be used to match the root prefix of an absolute
396 /// path in this style.
397 final Pattern _rootPattern;
398
399 /// Gets the root prefix of [path] if path is absolute. If [path] is relative,
400 /// returns `null`.
401 String getRoot(String path) {
402 var match = _rootPattern.firstMatch(path);
403 if (match == null) return null;
404 return match[0];
405 }
406
407 String toString() => name;
408 }
409
410 // TODO(rnystrom): Make this public?
411 class _ParsedPath {
412 /// The [Style] that was used to parse this path.
413 Style style;
414
415 /// The absolute root portion of the path, or `null` if the path is relative.
416 /// On POSIX systems, this will be `null` or "/". On Windows, it can be
417 /// `null`, "//" for a UNC path, or something like "C:\" for paths with drive
418 /// letters.
419 String root;
420
421 /// The path-separated parts of the path. All but the last will be
422 /// directories. The last could be a directory, or could be the file name
423 /// without its extension.
424 List<String> parts;
425
426 /// The path separators following each part. The last one will be an empty
427 /// string unless the path ends with a trailing separator.
428 List<String> separators;
429
430 /// The file's extension, or "" if it doesn't have one.
431 String extension;
432
433 /// `true` if the path ends with a trailing separator.
434 bool get hasTrailingSeparator {
435 if (separators.length == 0) return false;
436 return separators[separators.length - 1] != '';
437 }
438
439 /// `true` if this is an absolute path.
440 bool get isAbsolute => root != null;
441
442 _ParsedPath(this.style, this.root, this.parts, this.separators,
443 this.extension);
444
445 String get filename {
446 if (parts.length == 0) return extension;
447 if (hasTrailingSeparator) return '';
448 return '${parts.last}$extension';
449 }
450
451 String get filenameWithoutExtension {
452 if (parts.length == 0) return '';
453 if (hasTrailingSeparator) return '';
454 return parts.last;
455 }
456
457 void removeTrailingSeparator() {
458 if (separators.length > 0) {
459 separators[separators.length - 1] = '';
460 }
461 }
462
463 void normalize() {
464 // Handle '.', '..', and empty parts.
465 var leadingDoubles = 0;
466 var newParts = [];
467 for (var part in parts) {
468 if (part == '.' || part == '') {
469 // Do nothing. Ignore it.
470 } else if (part == '..') {
471 // Pop the last part off.
472 if (newParts.length > 0) {
473 newParts.removeLast();
474 } else {
475 // Backed out past the beginning, so preserve the "..".
476 leadingDoubles++;
477 }
478 } else {
479 newParts.add(part);
480 }
481 }
482
483 // A relative path can back out from the start directory.
484 if (!isAbsolute) {
485 newParts.insertRange(0, leadingDoubles, '..');
486 }
487
488 // If we collapsed down to nothing, do ".".
489 if (newParts.length == 0 && !isAbsolute) {
490 newParts.add('.');
491 }
492
493 // Canonicalize separators.
494 var newSeparators = [];
495 newSeparators.insertRange(0, newParts.length, style.separator);
496
497 parts = newParts;
498 separators = newSeparators;
499
500 removeTrailingSeparator();
501 }
502
503 String toString() {
504 var builder = new StringBuffer();
505 if (root != null) builder.add(root);
506 for (var i = 0; i < parts.length; i++) {
507 builder.add(parts[i]);
508 if (extension != null && i == parts.length - 1) builder.add(extension);
509 builder.add(separators[i]);
510 }
511
512 return builder.toString();
513 }
514 }
OLDNEW
« no previous file with comments | « utils/pub/io.dart ('k') | utils/pub/pub.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698