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

Side by Side Diff: pkg/path/lib/src/context.dart

Issue 62753005: Refactor pkg/path. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: code review Created 7 years, 1 month 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 | « pkg/path/lib/path.dart ('k') | pkg/path/lib/src/parsed_path.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) 2013, 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 library path.context;
6
7 import 'style.dart';
8 import 'parsed_path.dart';
9 import 'path_exception.dart';
10 import '../path.dart' as p;
11
12 /// An instantiable class for manipulating paths. Unlike the top-level
13 /// functions, this lets you explicitly select what platform the paths will use.
14 class Context {
15 /// Creates a new path context for the given style and current directory.
16 ///
17 /// If [style] is omitted, it uses the host operating system's path style. If
18 /// only [current] is omitted, it defaults ".". If *both* [style] and
19 /// [current] are omitted, [current] defaults to the real current working
20 /// directory.
21 ///
22 /// On the browser, [style] defaults to [Style.url] and [current] defaults to
23 /// the current URL.
24 factory Context({Style style, String current}) {
25 if (current == null) {
26 if (style == null) {
27 current = p.current;
28 } else {
29 current = ".";
30 }
31 }
32
33 if (style == null) style = Style.platform;
34
35 return new Context._(style, current);
36 }
37
38 Context._(this.style, this.current);
39
40 /// The style of path that this context works with.
41 final Style style;
42
43 /// The current directory that relative paths will be relative to.
44 final String current;
45
46 /// Gets the path separator for the context's [style]. On Mac and Linux,
47 /// this is `/`. On Windows, it's `\`.
48 String get separator => style.separator;
49
50 /// Creates a new path by appending the given path parts to [current].
51 /// Equivalent to [join()] with [current] as the first argument. Example:
52 ///
53 /// var context = new Context(current: '/root');
54 /// context.absolute('path', 'to', 'foo'); // -> '/root/path/to/foo'
55 ///
56 /// If [current] isn't absolute, this won't return an absolute path.
57 String absolute(String part1, [String part2, String part3, String part4,
58 String part5, String part6, String part7]) {
59 return join(current, part1, part2, part3, part4, part5, part6, part7);
60 }
61
62 /// Gets the part of [path] after the last separator on the context's
63 /// platform.
64 ///
65 /// context.basename('path/to/foo.dart'); // -> 'foo.dart'
66 /// context.basename('path/to'); // -> 'to'
67 ///
68 /// Trailing separators are ignored.
69 ///
70 /// context.basename('path/to/'); // -> 'to'
71 String basename(String path) => _parse(path).basename;
72
73 /// Gets the part of [path] after the last separator on the context's
74 /// platform, and without any trailing file extension.
75 ///
76 /// context.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo'
77 ///
78 /// Trailing separators are ignored.
79 ///
80 /// context.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo'
81 String basenameWithoutExtension(String path) =>
82 _parse(path).basenameWithoutExtension;
83
84 /// Gets the part of [path] before the last separator.
85 ///
86 /// context.dirname('path/to/foo.dart'); // -> 'path/to'
87 /// context.dirname('path/to'); // -> 'path'
88 ///
89 /// Trailing separators are ignored.
90 ///
91 /// context.dirname('path/to/'); // -> 'path'
92 String dirname(String path) {
93 var parsed = _parse(path);
94 parsed.removeTrailingSeparators();
95 if (parsed.parts.isEmpty) return parsed.root == null ? '.' : parsed.root;
96 if (parsed.parts.length == 1) {
97 return parsed.root == null ? '.' : parsed.root;
98 }
99 parsed.parts.removeLast();
100 parsed.separators.removeLast();
101 parsed.removeTrailingSeparators();
102 return parsed.toString();
103 }
104
105 /// Gets the file extension of [path]: the portion of [basename] from the last
106 /// `.` to the end (including the `.` itself).
107 ///
108 /// context.extension('path/to/foo.dart'); // -> '.dart'
109 /// context.extension('path/to/foo'); // -> ''
110 /// context.extension('path.to/foo'); // -> ''
111 /// context.extension('path/to/foo.dart.js'); // -> '.js'
112 ///
113 /// If the file name starts with a `.`, then it is not considered an
114 /// extension:
115 ///
116 /// context.extension('~/.bashrc'); // -> ''
117 /// context.extension('~/.notes.txt'); // -> '.txt'
118 String extension(String path) => _parse(path).extension;
119
120 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
121 /// Returns the root of [path] if it's absolute, or an empty string if it's
122 /// relative.
123 ///
124 /// // Unix
125 /// context.rootPrefix('path/to/foo'); // -> ''
126 /// context.rootPrefix('/path/to/foo'); // -> '/'
127 ///
128 /// // Windows
129 /// context.rootPrefix(r'path\to\foo'); // -> ''
130 /// context.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
131 ///
132 /// // URL
133 /// context.rootPrefix('path/to/foo'); // -> ''
134 /// context.rootPrefix('http://dartlang.org/path/to/foo');
135 /// // -> 'http://dartlang.org'
136 String rootPrefix(String path) {
137 var root = _parse(path).root;
138 return root == null ? '' : root;
139 }
140
141 /// Returns `true` if [path] is an absolute path and `false` if it is a
142 /// relative path.
143 ///
144 /// On POSIX systems, absolute paths start with a `/` (forward slash). On
145 /// Windows, an absolute path starts with `\\`, or a drive letter followed by
146 /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and
147 /// optional hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`.
148 ///
149 /// URLs that start with `/` are known as "root-relative", since they're
150 /// relative to the root of the current URL. Since root-relative paths are
151 /// still absolute in every other sense, [isAbsolute] will return true for
152 /// them. They can be detected using [isRootRelative].
153 bool isAbsolute(String path) => _parse(path).isAbsolute;
154
155 /// Returns `true` if [path] is a relative path and `false` if it is absolute.
156 /// On POSIX systems, absolute paths start with a `/` (forward slash). On
157 /// Windows, an absolute path starts with `\\`, or a drive letter followed by
158 /// `:/` or `:\`.
159 bool isRelative(String path) => !this.isAbsolute(path);
160
161 /// Returns `true` if [path] is a root-relative path and `false` if it's not.
162 ///
163 /// URLs that start with `/` are known as "root-relative", since they're
164 /// relative to the root of the current URL. Since root-relative paths are
165 /// still absolute in every other sense, [isAbsolute] will return true for
166 /// them. They can be detected using [isRootRelative].
167 ///
168 /// No POSIX and Windows paths are root-relative.
169 bool isRootRelative(String path) => _parse(path).isRootRelative;
170
171 /// Joins the given path parts into a single path. Example:
172 ///
173 /// context.join('path', 'to', 'foo'); // -> 'path/to/foo'
174 ///
175 /// If any part ends in a path separator, then a redundant separator will not
176 /// be added:
177 ///
178 /// context.join('path/', 'to', 'foo'); // -> 'path/to/foo
179 ///
180 /// If a part is an absolute path, then anything before that will be ignored:
181 ///
182 /// context.join('path', '/to', 'foo'); // -> '/to/foo'
183 ///
184 String join(String part1, [String part2, String part3, String part4,
185 String part5, String part6, String part7, String part8]) {
186 var parts = [part1, part2, part3, part4, part5, part6, part7, part8];
187 _validateArgList("join", parts);
188 return joinAll(parts.where((part) => part != null));
189 }
190
191 /// Joins the given path parts into a single path. Example:
192 ///
193 /// context.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo'
194 ///
195 /// If any part ends in a path separator, then a redundant separator will not
196 /// be added:
197 ///
198 /// context.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo
199 ///
200 /// If a part is an absolute path, then anything before that will be ignored:
201 ///
202 /// context.joinAll(['path', '/to', 'foo']); // -> '/to/foo'
203 ///
204 /// For a fixed number of parts, [join] is usually terser.
205 String joinAll(Iterable<String> parts) {
206 var buffer = new StringBuffer();
207 var needsSeparator = false;
208 var isAbsoluteAndNotRootRelative = false;
209
210 for (var part in parts.where((part) => part != '')) {
211 if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) {
212 // If the new part is root-relative, it preserves the previous root but
213 // replaces the path after it.
214 var parsed = _parse(part);
215 parsed.root = this.rootPrefix(buffer.toString());
216 if (parsed.root.contains(style.needsSeparatorPattern)) {
217 parsed.separators[0] = style.separator;
218 }
219 buffer.clear();
220 buffer.write(parsed.toString());
221 } else if (this.isAbsolute(part)) {
222 isAbsoluteAndNotRootRelative = !this.isRootRelative(part);
223 // An absolute path discards everything before it.
224 buffer.clear();
225 buffer.write(part);
226 } else {
227 if (part.length > 0 && part[0].contains(style.separatorPattern)) {
228 // The part starts with a separator, so we don't need to add one.
229 } else if (needsSeparator) {
230 buffer.write(separator);
231 }
232
233 buffer.write(part);
234 }
235
236 // Unless this part ends with a separator, we'll need to add one before
237 // the next part.
238 needsSeparator = part.contains(style.needsSeparatorPattern);
239 }
240
241 return buffer.toString();
242 }
243
244 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
245 /// Splits [path] into its components using the current platform's
246 /// [separator]. Example:
247 ///
248 /// context.split('path/to/foo'); // -> ['path', 'to', 'foo']
249 ///
250 /// The path will *not* be normalized before splitting.
251 ///
252 /// context.split('path/../foo'); // -> ['path', '..', 'foo']
253 ///
254 /// If [path] is absolute, the root directory will be the first element in the
255 /// array. Example:
256 ///
257 /// // Unix
258 /// context.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo']
259 ///
260 /// // Windows
261 /// context.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
262 List<String> split(String path) {
263 var parsed = _parse(path);
264 // Filter out empty parts that exist due to multiple separators in a row.
265 parsed.parts = parsed.parts.where((part) => !part.isEmpty)
266 .toList();
267 if (parsed.root != null) parsed.parts.insert(0, parsed.root);
268 return parsed.parts;
269 }
270
271 /// Normalizes [path], simplifying it by handling `..`, and `.`, and
272 /// removing redundant path separators whenever possible.
273 ///
274 /// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
275 String normalize(String path) {
276 var parsed = _parse(path);
277 parsed.normalize();
278 return parsed.toString();
279 }
280
281 /// Attempts to convert [path] to an equivalent relative path relative to
282 /// [root].
283 ///
284 /// var context = new Context(current: '/root/path');
285 /// context.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
286 /// context.relative('/root/other.dart'); // -> '../other.dart'
287 ///
288 /// If the [from] argument is passed, [path] is made relative to that instead.
289 ///
290 /// context.relative('/root/path/a/b.dart',
291 /// from: '/root/path'); // -> 'a/b.dart'
292 /// context.relative('/root/other.dart',
293 /// from: '/root/path'); // -> '../other.dart'
294 ///
295 /// If [path] and/or [from] are relative paths, they are assumed to be
296 /// relative to [current].
297 ///
298 /// Since there is no relative path from one drive letter to another on
299 /// Windows, this will return an absolute path in that case.
300 ///
301 /// context.relative(r'D:\other', from: r'C:\other'); // -> 'D:\other'
302 ///
303 /// This will also return an absolute path if an absolute [path] is passed to
304 /// a context with a relative path for [current].
305 ///
306 /// var context = new Context(r'some/relative/path');
307 /// context.relative(r'/absolute/path'); // -> '/absolute/path'
308 ///
309 /// If [root] is relative, it may be impossible to determine a path from
310 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is
311 /// "/", no path can be determined. In this case, a [PathException] will be
312 /// thrown.
313 String relative(String path, {String from}) {
314 from = from == null ? current : this.join(current, from);
315
316 // We can't determine the path from a relative path to an absolute path.
317 if (this.isRelative(from) && this.isAbsolute(path)) {
318 return this.normalize(path);
319 }
320
321 // If the given path is relative, resolve it relative to the context's
322 // current directory.
323 if (this.isRelative(path) || this.isRootRelative(path)) {
324 path = this.absolute(path);
325 }
326
327 // If the path is still relative and `from` is absolute, we're unable to
328 // find a path from `from` to `path`.
329 if (this.isRelative(path) && this.isAbsolute(from)) {
330 throw new PathException('Unable to find a path to "$path" from "$from".');
331 }
332
333 var fromParsed = _parse(from)..normalize();
334 var pathParsed = _parse(path)..normalize();
335
336 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') {
337 return pathParsed.toString();
338 }
339
340 // If the root prefixes don't match (for example, different drive letters
341 // on Windows), then there is no relative path, so just return the absolute
342 // one. In Windows, drive letters are case-insenstive and we allow
343 // calculation of relative paths, even if a path has not been normalized.
344 if (fromParsed.root != pathParsed.root &&
345 ((fromParsed.root == null || pathParsed.root == null) ||
346 fromParsed.root.toLowerCase().replaceAll('/', '\\') !=
347 pathParsed.root.toLowerCase().replaceAll('/', '\\'))) {
348 return pathParsed.toString();
349 }
350
351 // Strip off their common prefix.
352 while (fromParsed.parts.length > 0 && pathParsed.parts.length > 0 &&
353 fromParsed.parts[0] == pathParsed.parts[0]) {
354 fromParsed.parts.removeAt(0);
355 fromParsed.separators.removeAt(1);
356 pathParsed.parts.removeAt(0);
357 pathParsed.separators.removeAt(1);
358 }
359
360 // If there are any directories left in the from path, we need to walk up
361 // out of them. If a directory left in the from path is '..', it cannot
362 // be cancelled by adding a '..'.
363 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') {
364 throw new PathException('Unable to find a path to "$path" from "$from".');
365 }
366 pathParsed.parts.insertAll(0,
367 new List.filled(fromParsed.parts.length, '..'));
368 pathParsed.separators[0] = '';
369 pathParsed.separators.insertAll(1,
370 new List.filled(fromParsed.parts.length, style.separator));
371
372 // Corner case: the paths completely collapsed.
373 if (pathParsed.parts.length == 0) return '.';
374
375 // Corner case: path was '.' and some '..' directories were added in front.
376 // Don't add a final '/.' in that case.
377 if (pathParsed.parts.length > 1 && pathParsed.parts.last == '.') {
378 pathParsed.parts.removeLast();
379 pathParsed.separators..removeLast()..removeLast()..add('');
380 }
381
382 // Make it relative.
383 pathParsed.root = '';
384 pathParsed.removeTrailingSeparators();
385
386 return pathParsed.toString();
387 }
388
389 /// Returns `true` if [child] is a path beneath `parent`, and `false`
390 /// otherwise.
391 ///
392 /// path.isWithin('/root/path', '/root/path/a'); // -> true
393 /// path.isWithin('/root/path', '/root/other'); // -> false
394 /// path.isWithin('/root/path', '/root/path'); // -> false
395 bool isWithin(String parent, String child) {
396 var relative;
397 try {
398 relative = this.relative(child, from: parent);
399 } on PathException catch (_) {
400 // If no relative path from [parent] to [child] is found, [child]
401 // definitely isn't a child of [parent].
402 return false;
403 }
404
405 var parts = this.split(relative);
406 return this.isRelative(relative) && parts.first != '..' &&
407 parts.first != '.';
408 }
409
410 /// Removes a trailing extension from the last part of [path].
411 ///
412 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
413 String withoutExtension(String path) {
414 var parsed = _parse(path);
415
416 for (var i = parsed.parts.length - 1; i >= 0; i--) {
417 if (!parsed.parts[i].isEmpty) {
418 parsed.parts[i] = parsed.basenameWithoutExtension;
419 break;
420 }
421 }
422
423 return parsed.toString();
424 }
425
426 /// Returns the path represented by [uri].
427 ///
428 /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL
429 /// style, this will just convert [uri] to a string.
430 ///
431 /// // POSIX
432 /// context.fromUri(Uri.parse('file:///path/to/foo'))
433 /// // -> '/path/to/foo'
434 ///
435 /// // Windows
436 /// context.fromUri(Uri.parse('file:///C:/path/to/foo'))
437 /// // -> r'C:\path\to\foo'
438 ///
439 /// // URL
440 /// context.fromUri(Uri.parse('http://dartlang.org/path/to/foo'))
441 /// // -> 'http://dartlang.org/path/to/foo'
442 String fromUri(Uri uri) => style.pathFromUri(uri);
443
444 /// Returns the URI that represents [path].
445 ///
446 /// For POSIX and Windows styles, this will return a `file:` URI. For the URL
447 /// style, this will just convert [path] to a [Uri].
448 ///
449 /// // POSIX
450 /// context.toUri('/path/to/foo')
451 /// // -> Uri.parse('file:///path/to/foo')
452 ///
453 /// // Windows
454 /// context.toUri(r'C:\path\to\foo')
455 /// // -> Uri.parse('file:///C:/path/to/foo')
456 ///
457 /// // URL
458 /// context.toUri('http://dartlang.org/path/to/foo')
459 /// // -> Uri.parse('http://dartlang.org/path/to/foo')
460 Uri toUri(String path) {
461 if (isRelative(path)) {
462 return style.relativePathToUri(path);
463 } else {
464 return style.absolutePathToUri(join(current, path));
465 }
466 }
467
468 ParsedPath _parse(String path) => new ParsedPath.parse(path, style);
469 }
470
471 /// Validates that there are no non-null arguments following a null one and
472 /// throws an appropriate [ArgumentError] on failure.
473 _validateArgList(String method, List<String> args) {
474 for (var i = 1; i < args.length; i++) {
475 // Ignore nulls hanging off the end.
476 if (args[i] == null || args[i - 1] != null) continue;
477
478 var numArgs;
479 for (numArgs = args.length; numArgs >= 1; numArgs--) {
480 if (args[numArgs - 1] != null) break;
481 }
482
483 // Show the arguments.
484 var message = new StringBuffer();
485 message.write("$method(");
486 message.write(args.take(numArgs)
487 .map((arg) => arg == null ? "null" : '"$arg"')
488 .join(", "));
489 message.write("): part ${i - 1} was null, but part $i was not.");
490 throw new ArgumentError(message.toString());
491 }
492 }
OLDNEW
« no previous file with comments | « pkg/path/lib/path.dart ('k') | pkg/path/lib/src/parsed_path.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698