OLD | NEW |
| (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 /// | |
7 /// ## Installing ## | |
8 /// | |
9 /// Use [pub][] to install this package. Add the following to your | |
10 /// `pubspec.yaml` file. | |
11 /// | |
12 /// dependencies: | |
13 /// pathos: any | |
14 /// | |
15 /// Then run `pub install`. | |
16 /// | |
17 /// For more information, see the | |
18 /// [pathos package on pub.dartlang.org][pkg]. | |
19 /// | |
20 /// [pub]: http://pub.dartlang.org | |
21 /// [pkg]: http://pub.dartlang.org/packages/pathos | |
22 library path; | |
23 | |
24 import 'dart:mirrors'; | |
25 | |
26 /// An internal builder for the current OS so we can provide a straight | |
27 /// functional interface and not require users to create one. | |
28 final _builder = new Builder(); | |
29 | |
30 /** | |
31 * Inserts [length] elements in front of the [list] and fills them with the | |
32 * [fillValue]. | |
33 */ | |
34 void _growListFront(List list, int length, fillValue) => | |
35 list.insertAll(0, new List.filled(length, fillValue)); | |
36 | |
37 /// If we're running in the server-side Dart VM, this will return a | |
38 /// [LibraryMirror] that gives access to the `dart:io` library. | |
39 /// | |
40 /// If `dart:io` is not available, this returns null. | |
41 LibraryMirror get _io { | |
42 try { | |
43 return currentMirrorSystem().libraries[Uri.parse('dart:io')]; | |
44 } catch (_) { | |
45 return null; | |
46 } | |
47 } | |
48 | |
49 // TODO(nweiz): when issue 6490 or 6943 are fixed, make this work under dart2js. | |
50 /// If we're running in Dartium, this will return a [LibraryMirror] that gives | |
51 /// access to the `dart:html` library. | |
52 /// | |
53 /// If `dart:html` is not available, this returns null. | |
54 LibraryMirror get _html { | |
55 try { | |
56 return currentMirrorSystem().libraries[Uri.parse('dart:html')]; | |
57 } catch (_) { | |
58 return null; | |
59 } | |
60 } | |
61 | |
62 /// Gets the path to the current working directory. | |
63 /// | |
64 /// In the browser, this means the current URL. When using dart2js, this | |
65 /// currently returns `.` due to technical constraints. In the future, it will | |
66 /// return the current URL. | |
67 String get current { | |
68 if (_io != null) { | |
69 return _io.classes[const Symbol('Directory')] | |
70 .getField(const Symbol('current')).reflectee.path; | |
71 } else if (_html != null) { | |
72 return _html.getField(const Symbol('window')) | |
73 .reflectee.location.href; | |
74 } else { | |
75 return '.'; | |
76 } | |
77 } | |
78 | |
79 /// Gets the path separator for the current platform. On Mac and Linux, this | |
80 /// is `/`. On Windows, it's `\`. | |
81 String get separator => _builder.separator; | |
82 | |
83 /// Converts [path] to an absolute path by resolving it relative to the current | |
84 /// working directory. If [path] is already an absolute path, just returns it. | |
85 /// | |
86 /// path.absolute('foo/bar.txt'); // -> /your/current/dir/foo/bar.txt | |
87 String absolute(String path) => join(current, path); | |
88 | |
89 /// Gets the part of [path] after the last separator. | |
90 /// | |
91 /// path.basename('path/to/foo.dart'); // -> 'foo.dart' | |
92 /// path.basename('path/to'); // -> 'to' | |
93 /// | |
94 /// Trailing separators are ignored. | |
95 /// | |
96 /// builder.basename('path/to/'); // -> 'to' | |
97 String basename(String path) => _builder.basename(path); | |
98 | |
99 /// Gets the part of [path] after the last separator, and without any trailing | |
100 /// file extension. | |
101 /// | |
102 /// path.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo' | |
103 /// | |
104 /// Trailing separators are ignored. | |
105 /// | |
106 /// builder.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo' | |
107 String basenameWithoutExtension(String path) => | |
108 _builder.basenameWithoutExtension(path); | |
109 | |
110 /// Gets the part of [path] before the last separator. | |
111 /// | |
112 /// path.dirname('path/to/foo.dart'); // -> 'path/to' | |
113 /// path.dirname('path/to'); // -> 'to' | |
114 /// | |
115 /// Trailing separators are ignored. | |
116 /// | |
117 /// builder.dirname('path/to/'); // -> 'path' | |
118 String dirname(String path) => _builder.dirname(path); | |
119 | |
120 /// Gets the file extension of [path]: the portion of [basename] from the last | |
121 /// `.` to the end (including the `.` itself). | |
122 /// | |
123 /// path.extension('path/to/foo.dart'); // -> '.dart' | |
124 /// path.extension('path/to/foo'); // -> '' | |
125 /// path.extension('path.to/foo'); // -> '' | |
126 /// path.extension('path/to/foo.dart.js'); // -> '.js' | |
127 /// | |
128 /// If the file name starts with a `.`, then that is not considered the | |
129 /// extension: | |
130 /// | |
131 /// path.extension('~/.bashrc'); // -> '' | |
132 /// path.extension('~/.notes.txt'); // -> '.txt' | |
133 String extension(String path) => _builder.extension(path); | |
134 | |
135 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed. | |
136 /// Returns the root of [path], if it's absolute, or the empty string if it's | |
137 /// relative. | |
138 /// | |
139 /// // Unix | |
140 /// path.rootPrefix('path/to/foo'); // -> '' | |
141 /// path.rootPrefix('/path/to/foo'); // -> '/' | |
142 /// | |
143 /// // Windows | |
144 /// path.rootPrefix(r'path\to\foo'); // -> '' | |
145 /// path.rootPrefix(r'C:\path\to\foo'); // -> r'C:\' | |
146 /// | |
147 /// // URL | |
148 /// path.rootPrefix('path/to/foo'); // -> '' | |
149 /// path.rootPrefix('http://dartlang.org/path/to/foo'); | |
150 /// // -> 'http://dartlang.org' | |
151 String rootPrefix(String path) => _builder.rootPrefix(path); | |
152 | |
153 /// Returns `true` if [path] is an absolute path and `false` if it is a | |
154 /// relative path. | |
155 /// | |
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 `:\`. For URLs, absolute paths either start with a protocol and | |
159 /// optional hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`. | |
160 /// | |
161 /// URLs that start with `/` are known as "root-relative", since they're | |
162 /// relative to the root of the current URL. Since root-relative paths are still | |
163 /// absolute in every other sense, [isAbsolute] will return true for them. They | |
164 /// can be detected using [isRootRelative]. | |
165 bool isAbsolute(String path) => _builder.isAbsolute(path); | |
166 | |
167 /// Returns `true` if [path] is a relative path and `false` if it is absolute. | |
168 /// On POSIX systems, absolute paths start with a `/` (forward slash). On | |
169 /// Windows, an absolute path starts with `\\`, or a drive letter followed by | |
170 /// `:/` or `:\`. | |
171 bool isRelative(String path) => _builder.isRelative(path); | |
172 | |
173 /// Returns `true` if [path] is a root-relative path and `false` if it's not. | |
174 /// | |
175 /// URLs that start with `/` are known as "root-relative", since they're | |
176 /// relative to the root of the current URL. Since root-relative paths are still | |
177 /// absolute in every other sense, [isAbsolute] will return true for them. They | |
178 /// can be detected using [isRootRelative]. | |
179 /// | |
180 /// No POSIX and Windows paths are root-relative. | |
181 bool isRootRelative(String path) => _builder.isRootRelative(path); | |
182 | |
183 /// Joins the given path parts into a single path using the current platform's | |
184 /// [separator]. Example: | |
185 /// | |
186 /// path.join('path', 'to', 'foo'); // -> 'path/to/foo' | |
187 /// | |
188 /// If any part ends in a path separator, then a redundant separator will not | |
189 /// be added: | |
190 /// | |
191 /// path.join('path/', 'to', 'foo'); // -> 'path/to/foo | |
192 /// | |
193 /// If a part is an absolute path, then anything before that will be ignored: | |
194 /// | |
195 /// path.join('path', '/to', 'foo'); // -> '/to/foo' | |
196 String join(String part1, [String part2, String part3, String part4, | |
197 String part5, String part6, String part7, String part8]) => | |
198 _builder.join(part1, part2, part3, part4, part5, part6, part7, part8); | |
199 | |
200 /// Joins the given path parts into a single path using the current platform's | |
201 /// [separator]. Example: | |
202 /// | |
203 /// path.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo' | |
204 /// | |
205 /// If any part ends in a path separator, then a redundant separator will not | |
206 /// be added: | |
207 /// | |
208 /// path.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo | |
209 /// | |
210 /// If a part is an absolute path, then anything before that will be ignored: | |
211 /// | |
212 /// path.joinAll(['path', '/to', 'foo']); // -> '/to/foo' | |
213 /// | |
214 /// For a fixed number of parts, [join] is usually terser. | |
215 String joinAll(Iterable<String> parts) => _builder.joinAll(parts); | |
216 | |
217 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed. | |
218 /// Splits [path] into its components using the current platform's [separator]. | |
219 /// | |
220 /// path.split('path/to/foo'); // -> ['path', 'to', 'foo'] | |
221 /// | |
222 /// The path will *not* be normalized before splitting. | |
223 /// | |
224 /// path.split('path/../foo'); // -> ['path', '..', 'foo'] | |
225 /// | |
226 /// If [path] is absolute, the root directory will be the first element in the | |
227 /// array. Example: | |
228 /// | |
229 /// // Unix | |
230 /// path.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo'] | |
231 /// | |
232 /// // Windows | |
233 /// path.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo'] | |
234 /// | |
235 /// // Browser | |
236 /// path.split('http://dartlang.org/path/to/foo'); | |
237 /// // -> ['http://dartlang.org', 'path', 'to', 'foo'] | |
238 List<String> split(String path) => _builder.split(path); | |
239 | |
240 /// Normalizes [path], simplifying it by handling `..`, and `.`, and | |
241 /// removing redundant path separators whenever possible. | |
242 /// | |
243 /// path.normalize('path/./to/..//file.text'); // -> 'path/file.txt' | |
244 String normalize(String path) => _builder.normalize(path); | |
245 | |
246 /// Attempts to convert [path] to an equivalent relative path from the current | |
247 /// directory. | |
248 /// | |
249 /// // Given current directory is /root/path: | |
250 /// path.relative('/root/path/a/b.dart'); // -> 'a/b.dart' | |
251 /// path.relative('/root/other.dart'); // -> '../other.dart' | |
252 /// | |
253 /// If the [from] argument is passed, [path] is made relative to that instead. | |
254 /// | |
255 /// path.relative('/root/path/a/b.dart', | |
256 /// from: '/root/path'); // -> 'a/b.dart' | |
257 /// path.relative('/root/other.dart', | |
258 /// from: '/root/path'); // -> '../other.dart' | |
259 /// | |
260 /// Since there is no relative path from one drive letter to another on Windows, | |
261 /// or from one hostname to another for URLs, this will return an absolute path | |
262 /// in those cases. | |
263 /// | |
264 /// // Windows | |
265 /// path.relative(r'D:\other', from: r'C:\home'); // -> 'D:\other' | |
266 /// | |
267 /// // URL | |
268 /// path.relative('http://dartlang.org', from: 'http://pub.dartlang.org'); | |
269 /// // -> 'http://dartlang.org' | |
270 String relative(String path, {String from}) => | |
271 _builder.relative(path, from: from); | |
272 | |
273 /// Removes a trailing extension from the last part of [path]. | |
274 /// | |
275 /// withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' | |
276 String withoutExtension(String path) => _builder.withoutExtension(path); | |
277 | |
278 /// Returns the path represented by [uri]. | |
279 /// | |
280 /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL | |
281 /// style, this will just convert [uri] to a string. | |
282 /// | |
283 /// // POSIX | |
284 /// path.fromUri(Uri.parse('file:///path/to/foo')) | |
285 /// // -> '/path/to/foo' | |
286 /// | |
287 /// // Windows | |
288 /// path.fromUri(Uri.parse('file:///C:/path/to/foo')) | |
289 /// // -> r'C:\path\to\foo' | |
290 /// | |
291 /// // URL | |
292 /// path.fromUri(Uri.parse('http://dartlang.org/path/to/foo')) | |
293 /// // -> 'http://dartlang.org/path/to/foo' | |
294 String fromUri(Uri uri) => _builder.fromUri(uri); | |
295 | |
296 /// Returns the URI that represents [path]. | |
297 /// | |
298 /// For POSIX and Windows styles, this will return a `file:` URI. For the URL | |
299 /// style, this will just convert [path] to a [Uri]. | |
300 /// | |
301 /// This will always convert relative paths to absolute ones before converting | |
302 /// to a URI. | |
303 /// | |
304 /// // POSIX | |
305 /// path.toUri('/path/to/foo') | |
306 /// // -> Uri.parse('file:///path/to/foo') | |
307 /// | |
308 /// // Windows | |
309 /// path.toUri(r'C:\path\to\foo') | |
310 /// // -> Uri.parse('file:///C:/path/to/foo') | |
311 /// | |
312 /// // URL | |
313 /// path.toUri('http://dartlang.org/path/to/foo') | |
314 /// // -> Uri.parse('http://dartlang.org/path/to/foo') | |
315 Uri toUri(String path) => _builder.toUri(path); | |
316 | |
317 /// Validates that there are no non-null arguments following a null one and | |
318 /// throws an appropriate [ArgumentError] on failure. | |
319 _validateArgList(String method, List<String> args) { | |
320 for (var i = 1; i < args.length; i++) { | |
321 // Ignore nulls hanging off the end. | |
322 if (args[i] == null || args[i - 1] != null) continue; | |
323 | |
324 var numArgs; | |
325 for (numArgs = args.length; numArgs >= 1; numArgs--) { | |
326 if (args[numArgs - 1] != null) break; | |
327 } | |
328 | |
329 // Show the arguments. | |
330 var message = new StringBuffer(); | |
331 message.write("$method("); | |
332 message.write(args.take(numArgs) | |
333 .map((arg) => arg == null ? "null" : '"$arg"') | |
334 .join(", ")); | |
335 message.write("): part ${i - 1} was null, but part $i was not."); | |
336 throw new ArgumentError(message.toString()); | |
337 } | |
338 } | |
339 | |
340 /// An instantiable class for manipulating paths. Unlike the top-level | |
341 /// functions, this lets you explicitly select what platform the paths will use. | |
342 class Builder { | |
343 /// Creates a new path builder for the given style and root directory. | |
344 /// | |
345 /// If [style] is omitted, it uses the host operating system's path style. If | |
346 /// [root] is omitted, it defaults to the current working directory. If [root] | |
347 /// is relative, it is considered relative to the current working directory. | |
348 /// | |
349 /// On the browser, the path style is [Style.url]. In Dartium, [root] defaults | |
350 /// to the current URL. When using dart2js, it currently defaults to `.` due | |
351 /// to technical constraints. | |
352 factory Builder({Style style, String root}) { | |
353 if (style == null) { | |
354 if (_io == null) { | |
355 style = Style.url; | |
356 } else if (_io.classes[const Symbol('Platform')] | |
357 .getField(const Symbol('operatingSystem')).reflectee == 'windows') { | |
358 style = Style.windows; | |
359 } else { | |
360 style = Style.posix; | |
361 } | |
362 } | |
363 | |
364 if (root == null) root = current; | |
365 | |
366 return new Builder._(style, root); | |
367 } | |
368 | |
369 Builder._(this.style, this.root); | |
370 | |
371 /// The style of path that this builder works with. | |
372 final Style style; | |
373 | |
374 /// The root directory that relative paths will be relative to. | |
375 final String root; | |
376 | |
377 /// Gets the path separator for the builder's [style]. On Mac and Linux, | |
378 /// this is `/`. On Windows, it's `\`. | |
379 String get separator => style.separator; | |
380 | |
381 /// Gets the part of [path] after the last separator on the builder's | |
382 /// platform. | |
383 /// | |
384 /// builder.basename('path/to/foo.dart'); // -> 'foo.dart' | |
385 /// builder.basename('path/to'); // -> 'to' | |
386 /// | |
387 /// Trailing separators are ignored. | |
388 /// | |
389 /// builder.basename('path/to/'); // -> 'to' | |
390 String basename(String path) => _parse(path).basename; | |
391 | |
392 /// Gets the part of [path] after the last separator on the builder's | |
393 /// platform, and without any trailing file extension. | |
394 /// | |
395 /// builder.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo' | |
396 /// | |
397 /// Trailing separators are ignored. | |
398 /// | |
399 /// builder.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo' | |
400 String basenameWithoutExtension(String path) => | |
401 _parse(path).basenameWithoutExtension; | |
402 | |
403 /// Gets the part of [path] before the last separator. | |
404 /// | |
405 /// builder.dirname('path/to/foo.dart'); // -> 'path/to' | |
406 /// builder.dirname('path/to'); // -> 'path' | |
407 /// | |
408 /// Trailing separators are ignored. | |
409 /// | |
410 /// builder.dirname('path/to/'); // -> 'path' | |
411 String dirname(String path) { | |
412 var parsed = _parse(path); | |
413 parsed.removeTrailingSeparators(); | |
414 if (parsed.parts.isEmpty) return parsed.root == null ? '.' : parsed.root; | |
415 if (parsed.parts.length == 1) { | |
416 return parsed.root == null ? '.' : parsed.root; | |
417 } | |
418 parsed.parts.removeLast(); | |
419 parsed.separators.removeLast(); | |
420 parsed.removeTrailingSeparators(); | |
421 return parsed.toString(); | |
422 } | |
423 | |
424 /// Gets the file extension of [path]: the portion of [basename] from the last | |
425 /// `.` to the end (including the `.` itself). | |
426 /// | |
427 /// builder.extension('path/to/foo.dart'); // -> '.dart' | |
428 /// builder.extension('path/to/foo'); // -> '' | |
429 /// builder.extension('path.to/foo'); // -> '' | |
430 /// builder.extension('path/to/foo.dart.js'); // -> '.js' | |
431 /// | |
432 /// If the file name starts with a `.`, then it is not considered an | |
433 /// extension: | |
434 /// | |
435 /// builder.extension('~/.bashrc'); // -> '' | |
436 /// builder.extension('~/.notes.txt'); // -> '.txt' | |
437 String extension(String path) => _parse(path).extension; | |
438 | |
439 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed. | |
440 /// Returns the root of [path], if it's absolute, or an empty string if it's | |
441 /// relative. | |
442 /// | |
443 /// // Unix | |
444 /// builder.rootPrefix('path/to/foo'); // -> '' | |
445 /// builder.rootPrefix('/path/to/foo'); // -> '/' | |
446 /// | |
447 /// // Windows | |
448 /// builder.rootPrefix(r'path\to\foo'); // -> '' | |
449 /// builder.rootPrefix(r'C:\path\to\foo'); // -> r'C:\' | |
450 /// | |
451 /// // URL | |
452 /// builder.rootPrefix('path/to/foo'); // -> '' | |
453 /// builder.rootPrefix('http://dartlang.org/path/to/foo'); | |
454 /// // -> 'http://dartlang.org' | |
455 String rootPrefix(String path) { | |
456 var root = _parse(path).root; | |
457 return root == null ? '' : root; | |
458 } | |
459 | |
460 /// Returns `true` if [path] is an absolute path and `false` if it is a | |
461 /// relative path. | |
462 /// | |
463 /// On POSIX systems, absolute paths start with a `/` (forward slash). On | |
464 /// Windows, an absolute path starts with `\\`, or a drive letter followed by | |
465 /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and | |
466 /// optional hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`. | |
467 /// | |
468 /// URLs that start with `/` are known as "root-relative", since they're | |
469 /// relative to the root of the current URL. Since root-relative paths are | |
470 /// still absolute in every other sense, [isAbsolute] will return true for | |
471 /// them. They can be detected using [isRootRelative]. | |
472 bool isAbsolute(String path) => _parse(path).isAbsolute; | |
473 | |
474 /// Returns `true` if [path] is a relative path and `false` if it is absolute. | |
475 /// On POSIX systems, absolute paths start with a `/` (forward slash). On | |
476 /// Windows, an absolute path starts with `\\`, or a drive letter followed by | |
477 /// `:/` or `:\`. | |
478 bool isRelative(String path) => !this.isAbsolute(path); | |
479 | |
480 /// Returns `true` if [path] is a root-relative path and `false` if it's not. | |
481 /// | |
482 /// URLs that start with `/` are known as "root-relative", since they're | |
483 /// relative to the root of the current URL. Since root-relative paths are | |
484 /// still absolute in every other sense, [isAbsolute] will return true for | |
485 /// them. They can be detected using [isRootRelative]. | |
486 /// | |
487 /// No POSIX and Windows paths are root-relative. | |
488 bool isRootRelative(String path) => _parse(path).isRootRelative; | |
489 | |
490 /// Joins the given path parts into a single path. Example: | |
491 /// | |
492 /// builder.join('path', 'to', 'foo'); // -> 'path/to/foo' | |
493 /// | |
494 /// If any part ends in a path separator, then a redundant separator will not | |
495 /// be added: | |
496 /// | |
497 /// builder.join('path/', 'to', 'foo'); // -> 'path/to/foo | |
498 /// | |
499 /// If a part is an absolute path, then anything before that will be ignored: | |
500 /// | |
501 /// builder.join('path', '/to', 'foo'); // -> '/to/foo' | |
502 /// | |
503 String join(String part1, [String part2, String part3, String part4, | |
504 String part5, String part6, String part7, String part8]) { | |
505 var parts = [part1, part2, part3, part4, part5, part6, part7, part8]; | |
506 _validateArgList("join", parts); | |
507 return joinAll(parts.where((part) => part != null)); | |
508 } | |
509 | |
510 /// Joins the given path parts into a single path. Example: | |
511 /// | |
512 /// builder.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo' | |
513 /// | |
514 /// If any part ends in a path separator, then a redundant separator will not | |
515 /// be added: | |
516 /// | |
517 /// builder.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo | |
518 /// | |
519 /// If a part is an absolute path, then anything before that will be ignored: | |
520 /// | |
521 /// builder.joinAll(['path', '/to', 'foo']); // -> '/to/foo' | |
522 /// | |
523 /// For a fixed number of parts, [join] is usually terser. | |
524 String joinAll(Iterable<String> parts) { | |
525 var buffer = new StringBuffer(); | |
526 var needsSeparator = false; | |
527 var isAbsoluteAndNotRootRelative = false; | |
528 | |
529 for (var part in parts) { | |
530 if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) { | |
531 // If the new part is root-relative, it preserves the previous root but | |
532 // replaces the path after it. | |
533 var oldRoot = this.rootPrefix(buffer.toString()); | |
534 buffer.clear(); | |
535 buffer.write(oldRoot); | |
536 buffer.write(part); | |
537 } else if (this.isAbsolute(part)) { | |
538 isAbsoluteAndNotRootRelative = !this.isRootRelative(part); | |
539 // An absolute path discards everything before it. | |
540 buffer.clear(); | |
541 buffer.write(part); | |
542 } else { | |
543 if (part.length > 0 && part[0].contains(style.separatorPattern)) { | |
544 // The part starts with a separator, so we don't need to add one. | |
545 } else if (needsSeparator) { | |
546 buffer.write(separator); | |
547 } | |
548 | |
549 buffer.write(part); | |
550 } | |
551 | |
552 // Unless this part ends with a separator, we'll need to add one before | |
553 // the next part. | |
554 needsSeparator = part.contains(style.needsSeparatorPattern); | |
555 } | |
556 | |
557 return buffer.toString(); | |
558 } | |
559 | |
560 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed. | |
561 /// Splits [path] into its components using the current platform's | |
562 /// [separator]. Example: | |
563 /// | |
564 /// builder.split('path/to/foo'); // -> ['path', 'to', 'foo'] | |
565 /// | |
566 /// The path will *not* be normalized before splitting. | |
567 /// | |
568 /// builder.split('path/../foo'); // -> ['path', '..', 'foo'] | |
569 /// | |
570 /// If [path] is absolute, the root directory will be the first element in the | |
571 /// array. Example: | |
572 /// | |
573 /// // Unix | |
574 /// builder.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo'] | |
575 /// | |
576 /// // Windows | |
577 /// builder.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo'] | |
578 List<String> split(String path) { | |
579 var parsed = _parse(path); | |
580 // Filter out empty parts that exist due to multiple separators in a row. | |
581 parsed.parts = parsed.parts.where((part) => !part.isEmpty) | |
582 .toList(); | |
583 if (parsed.root != null) parsed.parts.insert(0, parsed.root); | |
584 return parsed.parts; | |
585 } | |
586 | |
587 /// Normalizes [path], simplifying it by handling `..`, and `.`, and | |
588 /// removing redundant path separators whenever possible. | |
589 /// | |
590 /// builder.normalize('path/./to/..//file.text'); // -> 'path/file.txt' | |
591 String normalize(String path) { | |
592 if (path == '') return path; | |
593 | |
594 var parsed = _parse(path); | |
595 parsed.normalize(); | |
596 return parsed.toString(); | |
597 } | |
598 | |
599 /// Creates a new path by appending the given path parts to the [root]. | |
600 /// Equivalent to [join()] with [root] as the first argument. Example: | |
601 /// | |
602 /// var builder = new Builder(root: 'root'); | |
603 /// builder.resolve('path', 'to', 'foo'); // -> 'root/path/to/foo' | |
604 String resolve(String part1, [String part2, String part3, String part4, | |
605 String part5, String part6, String part7]) { | |
606 return join(root, part1, part2, part3, part4, part5, part6, part7); | |
607 } | |
608 | |
609 /// Attempts to convert [path] to an equivalent relative path relative to | |
610 /// [root]. | |
611 /// | |
612 /// var builder = new Builder(root: '/root/path'); | |
613 /// builder.relative('/root/path/a/b.dart'); // -> 'a/b.dart' | |
614 /// builder.relative('/root/other.dart'); // -> '../other.dart' | |
615 /// | |
616 /// If the [from] argument is passed, [path] is made relative to that instead. | |
617 /// | |
618 /// builder.relative('/root/path/a/b.dart', | |
619 /// from: '/root/path'); // -> 'a/b.dart' | |
620 /// builder.relative('/root/other.dart', | |
621 /// from: '/root/path'); // -> '../other.dart' | |
622 /// | |
623 /// Since there is no relative path from one drive letter to another on | |
624 /// Windows, this will return an absolute path in that case. | |
625 /// | |
626 /// builder.relative(r'D:\other', from: r'C:\other'); // -> 'D:\other' | |
627 /// | |
628 /// This will also return an absolute path if an absolute [path] is passed to | |
629 /// a builder with a relative [root]. | |
630 /// | |
631 /// var builder = new Builder(r'some/relative/path'); | |
632 /// builder.relative(r'/absolute/path'); // -> '/absolute/path' | |
633 String relative(String path, {String from}) { | |
634 if (path == '') return '.'; | |
635 | |
636 from = from == null ? root : this.join(root, from); | |
637 | |
638 // We can't determine the path from a relative path to an absolute path. | |
639 if (this.isRelative(from) && this.isAbsolute(path)) { | |
640 return this.normalize(path); | |
641 } | |
642 | |
643 // If the given path is relative, resolve it relative to the root of the | |
644 // builder. | |
645 if (this.isRelative(path) || this.isRootRelative(path)) { | |
646 path = this.resolve(path); | |
647 } | |
648 | |
649 // If the path is still relative and `from` is absolute, we're unable to | |
650 // find a path from `from` to `path`. | |
651 if (this.isRelative(path) && this.isAbsolute(from)) { | |
652 throw new ArgumentError('Unable to find a path to "$path" from "$from".'); | |
653 } | |
654 | |
655 var fromParsed = _parse(from)..normalize(); | |
656 var pathParsed = _parse(path)..normalize(); | |
657 | |
658 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') { | |
659 return pathParsed.toString(); | |
660 } | |
661 | |
662 // If the root prefixes don't match (for example, different drive letters | |
663 // on Windows), then there is no relative path, so just return the absolute | |
664 // one. In Windows, drive letters are case-insenstive and we allow | |
665 // calculation of relative paths, even if a path has not been normalized. | |
666 if (fromParsed.root != pathParsed.root && | |
667 ((fromParsed.root == null || pathParsed.root == null) || | |
668 fromParsed.root.toLowerCase().replaceAll('/', '\\') != | |
669 pathParsed.root.toLowerCase().replaceAll('/', '\\'))) { | |
670 return pathParsed.toString(); | |
671 } | |
672 | |
673 // Strip off their common prefix. | |
674 while (fromParsed.parts.length > 0 && pathParsed.parts.length > 0 && | |
675 fromParsed.parts[0] == pathParsed.parts[0]) { | |
676 fromParsed.parts.removeAt(0); | |
677 fromParsed.separators.removeAt(1); | |
678 pathParsed.parts.removeAt(0); | |
679 pathParsed.separators.removeAt(1); | |
680 } | |
681 | |
682 // If there are any directories left in the root path, we need to walk up | |
683 // out of them. | |
684 _growListFront(pathParsed.parts, fromParsed.parts.length, '..'); | |
685 pathParsed.separators[0] = ''; | |
686 pathParsed.separators.insertAll(1, | |
687 new List.filled(fromParsed.parts.length, style.separator)); | |
688 | |
689 // Corner case: the paths completely collapsed. | |
690 if (pathParsed.parts.length == 0) return '.'; | |
691 | |
692 // Make it relative. | |
693 pathParsed.root = ''; | |
694 pathParsed.removeTrailingSeparators(); | |
695 | |
696 return pathParsed.toString(); | |
697 } | |
698 | |
699 /// Removes a trailing extension from the last part of [path]. | |
700 /// | |
701 /// builder.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' | |
702 String withoutExtension(String path) { | |
703 var parsed = _parse(path); | |
704 | |
705 for (var i = parsed.parts.length - 1; i >= 0; i--) { | |
706 if (!parsed.parts[i].isEmpty) { | |
707 parsed.parts[i] = parsed.basenameWithoutExtension; | |
708 break; | |
709 } | |
710 } | |
711 | |
712 return parsed.toString(); | |
713 } | |
714 | |
715 /// Returns the path represented by [uri]. | |
716 /// | |
717 /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL | |
718 /// style, this will just convert [uri] to a string. | |
719 /// | |
720 /// // POSIX | |
721 /// builder.fromUri(Uri.parse('file:///path/to/foo')) | |
722 /// // -> '/path/to/foo' | |
723 /// | |
724 /// // Windows | |
725 /// builder.fromUri(Uri.parse('file:///C:/path/to/foo')) | |
726 /// // -> r'C:\path\to\foo' | |
727 /// | |
728 /// // URL | |
729 /// builder.fromUri(Uri.parse('http://dartlang.org/path/to/foo')) | |
730 /// // -> 'http://dartlang.org/path/to/foo' | |
731 String fromUri(Uri uri) => style.pathFromUri(uri); | |
732 | |
733 /// Returns the URI that represents [path]. | |
734 /// | |
735 /// For POSIX and Windows styles, this will return a `file:` URI. For the URL | |
736 /// style, this will just convert [path] to a [Uri]. | |
737 /// | |
738 /// // POSIX | |
739 /// builder.toUri('/path/to/foo') | |
740 /// // -> Uri.parse('file:///path/to/foo') | |
741 /// | |
742 /// // Windows | |
743 /// builder.toUri(r'C:\path\to\foo') | |
744 /// // -> Uri.parse('file:///C:/path/to/foo') | |
745 /// | |
746 /// // URL | |
747 /// builder.toUri('http://dartlang.org/path/to/foo') | |
748 /// // -> Uri.parse('http://dartlang.org/path/to/foo') | |
749 Uri toUri(String path) { | |
750 if (isRelative(path)) { | |
751 return Uri.parse(path.replaceAll(style.separatorPattern, '/')); | |
752 } else { | |
753 return style.pathToUri(join(root, path)); | |
754 } | |
755 } | |
756 | |
757 _ParsedPath _parse(String path) { | |
758 var before = path; | |
759 | |
760 // Remove the root prefix, if any. | |
761 var root = style.getRoot(path); | |
762 var isRootRelative = style.getRelativeRoot(path) != null; | |
763 if (root != null) path = path.substring(root.length); | |
764 | |
765 // Split the parts on path separators. | |
766 var parts = []; | |
767 var separators = []; | |
768 | |
769 var firstSeparator = style.separatorPattern.firstMatch(path); | |
770 if (firstSeparator != null && firstSeparator.start == 0) { | |
771 separators.add(firstSeparator[0]); | |
772 path = path.substring(firstSeparator[0].length); | |
773 } else { | |
774 separators.add(''); | |
775 } | |
776 | |
777 var start = 0; | |
778 for (var match in style.separatorPattern.allMatches(path)) { | |
779 parts.add(path.substring(start, match.start)); | |
780 separators.add(match[0]); | |
781 start = match.end; | |
782 } | |
783 | |
784 // Add the final part, if any. | |
785 if (start < path.length) { | |
786 parts.add(path.substring(start)); | |
787 separators.add(''); | |
788 } | |
789 | |
790 return new _ParsedPath(style, root, isRootRelative, parts, separators); | |
791 } | |
792 } | |
793 | |
794 /// An enum type describing a "flavor" of path. | |
795 abstract class Style { | |
796 /// POSIX-style paths use "/" (forward slash) as separators. Absolute paths | |
797 /// start with "/". Used by UNIX, Linux, Mac OS X, and others. | |
798 static final posix = new _PosixStyle(); | |
799 | |
800 /// Windows paths use "\" (backslash) as separators. Absolute paths start with | |
801 /// a drive letter followed by a colon (example, "C:") or two backslashes | |
802 /// ("\\") for UNC paths. | |
803 // TODO(rnystrom): The UNC root prefix should include the drive name too, not | |
804 // just the "\\". | |
805 static final windows = new _WindowsStyle(); | |
806 | |
807 /// URLs aren't filesystem paths, but they're supported by Pathos to make it | |
808 /// easier to manipulate URL paths in the browser. | |
809 /// | |
810 /// URLs use "/" (forward slash) as separators. Absolute paths either start | |
811 /// with a protocol and optional hostname (e.g. `http://dartlang.org`, | |
812 /// `file://`) or with "/". | |
813 static final url = new _UrlStyle(); | |
814 | |
815 /// The name of this path style. Will be "posix" or "windows". | |
816 String get name; | |
817 | |
818 /// The path separator for this style. On POSIX, this is `/`. On Windows, | |
819 /// it's `\`. | |
820 String get separator; | |
821 | |
822 /// The [Pattern] that can be used to match a separator for a path in this | |
823 /// style. Windows allows both "/" and "\" as path separators even though "\" | |
824 /// is the canonical one. | |
825 Pattern get separatorPattern; | |
826 | |
827 /// The [Pattern] that matches path components that need a separator after | |
828 /// them. | |
829 /// | |
830 /// Windows and POSIX styles just need separators when the previous component | |
831 /// doesn't already end in a separator, but the URL always needs to place a | |
832 /// separator between the root and the first component, even if the root | |
833 /// already ends in a separator character. For example, to join "file://" and | |
834 /// "usr", an additional "/" is needed (making "file:///usr"). | |
835 Pattern get needsSeparatorPattern; | |
836 | |
837 /// The [Pattern] that can be used to match the root prefix of an absolute | |
838 /// path in this style. | |
839 Pattern get rootPattern; | |
840 | |
841 /// The [Pattern] that can be used to match the root prefix of a root-relative | |
842 /// path in this style. | |
843 /// | |
844 /// This can be null to indicate that this style doesn't support root-relative | |
845 /// paths. | |
846 final Pattern relativeRootPattern = null; | |
847 | |
848 /// Gets the root prefix of [path] if path is absolute. If [path] is relative, | |
849 /// returns `null`. | |
850 String getRoot(String path) { | |
851 var match = rootPattern.firstMatch(path); | |
852 if (match != null) return match[0]; | |
853 return getRelativeRoot(path); | |
854 } | |
855 | |
856 /// Gets the root prefix of [path] if it's root-relative. | |
857 /// | |
858 /// If [path] is relative or absolute and not root-relative, returns `null`. | |
859 String getRelativeRoot(String path) { | |
860 if (relativeRootPattern == null) return null; | |
861 var match = relativeRootPattern.firstMatch(path); | |
862 if (match == null) return null; | |
863 return match[0]; | |
864 } | |
865 | |
866 /// Returns the path represented by [uri] in this style. | |
867 String pathFromUri(Uri uri); | |
868 | |
869 /// Returns the URI that represents [path]. | |
870 /// | |
871 /// Pathos will always path an absolute path for [path]. Relative paths are | |
872 /// handled automatically by [Builder]. | |
873 Uri pathToUri(String path); | |
874 | |
875 String toString() => name; | |
876 } | |
877 | |
878 /// The style for POSIX paths. | |
879 class _PosixStyle extends Style { | |
880 _PosixStyle(); | |
881 | |
882 static final _builder = new Builder(style: Style.posix); | |
883 | |
884 final name = 'posix'; | |
885 final separator = '/'; | |
886 final separatorPattern = new RegExp(r'/'); | |
887 final needsSeparatorPattern = new RegExp(r'[^/]$'); | |
888 final rootPattern = new RegExp(r'^/'); | |
889 | |
890 String pathFromUri(Uri uri) { | |
891 if (uri.scheme == '' || uri.scheme == 'file') { | |
892 return Uri.decodeComponent(uri.path); | |
893 } | |
894 throw new ArgumentError("Uri $uri must have scheme 'file:'."); | |
895 } | |
896 | |
897 Uri pathToUri(String path) { | |
898 var parsed = _builder._parse(path); | |
899 | |
900 if (parsed.parts.isEmpty) { | |
901 // If the path is a bare root (e.g. "/"), [components] will | |
902 // currently be empty. We add two empty components so the URL constructor | |
903 // produces "file:///", with a trailing slash. | |
904 parsed.parts.addAll(["", ""]); | |
905 } else if (parsed.hasTrailingSeparator) { | |
906 // If the path has a trailing slash, add a single empty component so the | |
907 // URI has a trailing slash as well. | |
908 parsed.parts.add(""); | |
909 } | |
910 | |
911 return new Uri(scheme: 'file', pathSegments: parsed.parts); | |
912 } | |
913 } | |
914 | |
915 /// The style for Windows paths. | |
916 class _WindowsStyle extends Style { | |
917 _WindowsStyle(); | |
918 | |
919 static final _builder = new Builder(style: Style.windows); | |
920 | |
921 final name = 'windows'; | |
922 final separator = '\\'; | |
923 final separatorPattern = new RegExp(r'[/\\]'); | |
924 final needsSeparatorPattern = new RegExp(r'[^/\\]$'); | |
925 final rootPattern = new RegExp(r'^(\\\\|[a-zA-Z]:[/\\])'); | |
926 | |
927 String pathFromUri(Uri uri) { | |
928 if (uri.scheme != '' && uri.scheme != 'file') { | |
929 throw new ArgumentError("Uri $uri must have scheme 'file:'."); | |
930 } | |
931 | |
932 var path = uri.path; | |
933 if (uri.host == '') { | |
934 // Drive-letter paths look like "file:///C:/path/to/file". The | |
935 // replaceFirst removes the extra initial slash. | |
936 if (path.startsWith('/')) path = path.replaceFirst("/", ""); | |
937 } else { | |
938 // Network paths look like "file://hostname/path/to/file". | |
939 path = '\\\\${uri.host}$path'; | |
940 } | |
941 return Uri.decodeComponent(path.replaceAll("/", "\\")); | |
942 } | |
943 | |
944 Uri pathToUri(String path) { | |
945 var parsed = _builder._parse(path); | |
946 if (parsed.root == r'\\') { | |
947 // Network paths become "file://hostname/path/to/file". | |
948 | |
949 var host = parsed.parts.removeAt(0); | |
950 | |
951 if (parsed.parts.isEmpty) { | |
952 // If the path is a bare root (e.g. "\\hostname"), [parsed.parts] will | |
953 // currently be empty. We add two empty components so the URL | |
954 // constructor produces "file://hostname/", with a trailing slash. | |
955 parsed.parts.addAll(["", ""]); | |
956 } else if (parsed.hasTrailingSeparator) { | |
957 // If the path has a trailing slash, add a single empty component so the | |
958 // URI has a trailing slash as well. | |
959 parsed.parts.add(""); | |
960 } | |
961 | |
962 return new Uri(scheme: 'file', host: host, pathSegments: parsed.parts); | |
963 } else { | |
964 // Drive-letter paths become "file:///C:/path/to/file". | |
965 | |
966 // If the path is a bare root (e.g. "C:\"), [parsed.parts] will currently | |
967 // be empty. We add an empty component so the URL constructor produces | |
968 // "file:///C:/", with a trailing slash. We also add an empty component if | |
969 // the URL otherwise has a trailing slash. | |
970 if (parsed.parts.length == 0 || parsed.hasTrailingSeparator) { | |
971 parsed.parts.add(""); | |
972 } | |
973 | |
974 // Get rid of the trailing "\" in "C:\" because the URI constructor will | |
975 // add a separator on its own. | |
976 parsed.parts.insert(0, parsed.root.replaceAll(separatorPattern, "")); | |
977 | |
978 return new Uri(scheme: 'file', pathSegments: parsed.parts); | |
979 } | |
980 } | |
981 } | |
982 | |
983 /// The style for URL paths. | |
984 class _UrlStyle extends Style { | |
985 _UrlStyle(); | |
986 | |
987 final name = 'url'; | |
988 final separator = '/'; | |
989 final separatorPattern = new RegExp(r'/'); | |
990 final needsSeparatorPattern = new RegExp( | |
991 r"(^[a-zA-Z][-+.a-zA-Z\d]*://|[^/])$"); | |
992 final rootPattern = new RegExp(r"[a-zA-Z][-+.a-zA-Z\d]*://[^/]*"); | |
993 final relativeRootPattern = new RegExp(r"^/"); | |
994 | |
995 String pathFromUri(Uri uri) => uri.toString(); | |
996 | |
997 Uri pathToUri(String path) => Uri.parse(path); | |
998 } | |
999 | |
1000 // TODO(rnystrom): Make this public? | |
1001 class _ParsedPath { | |
1002 /// The [Style] that was used to parse this path. | |
1003 Style style; | |
1004 | |
1005 /// The absolute root portion of the path, or `null` if the path is relative. | |
1006 /// On POSIX systems, this will be `null` or "/". On Windows, it can be | |
1007 /// `null`, "//" for a UNC path, or something like "C:\" for paths with drive | |
1008 /// letters. | |
1009 String root; | |
1010 | |
1011 /// Whether this path is root-relative. | |
1012 /// | |
1013 /// See [Builder.isRootRelative]. | |
1014 bool isRootRelative; | |
1015 | |
1016 /// The path-separated parts of the path. All but the last will be | |
1017 /// directories. | |
1018 List<String> parts; | |
1019 | |
1020 /// The path separators preceding each part. | |
1021 /// | |
1022 /// The first one will be an empty string unless the root requires a separator | |
1023 /// between it and the path. The last one will be an empty string unless the | |
1024 /// path ends with a trailing separator. | |
1025 List<String> separators; | |
1026 | |
1027 /// The file extension of the last part, or "" if it doesn't have one. | |
1028 String get extension => _splitExtension()[1]; | |
1029 | |
1030 /// `true` if this is an absolute path. | |
1031 bool get isAbsolute => root != null; | |
1032 | |
1033 _ParsedPath(this.style, this.root, this.isRootRelative, this.parts, | |
1034 this.separators); | |
1035 | |
1036 String get basename { | |
1037 var copy = this.clone(); | |
1038 copy.removeTrailingSeparators(); | |
1039 if (copy.parts.isEmpty) return root == null ? '' : root; | |
1040 return copy.parts.last; | |
1041 } | |
1042 | |
1043 String get basenameWithoutExtension { | |
1044 var copy = this.clone(); | |
1045 copy.removeTrailingSeparators(); | |
1046 if (copy.parts.isEmpty) return root == null ? '' : root; | |
1047 return copy._splitExtension()[0]; | |
1048 } | |
1049 | |
1050 bool get hasTrailingSeparator => !parts.isEmpty && (parts.last == '' || separa
tors.last != ''); | |
1051 | |
1052 void removeTrailingSeparators() { | |
1053 while (!parts.isEmpty && parts.last == '') { | |
1054 parts.removeLast(); | |
1055 separators.removeLast(); | |
1056 } | |
1057 if (separators.length > 0) separators[separators.length - 1] = ''; | |
1058 } | |
1059 | |
1060 void normalize() { | |
1061 // Handle '.', '..', and empty parts. | |
1062 var leadingDoubles = 0; | |
1063 var newParts = []; | |
1064 for (var part in parts) { | |
1065 if (part == '.' || part == '') { | |
1066 // Do nothing. Ignore it. | |
1067 } else if (part == '..') { | |
1068 // Pop the last part off. | |
1069 if (newParts.length > 0) { | |
1070 newParts.removeLast(); | |
1071 } else { | |
1072 // Backed out past the beginning, so preserve the "..". | |
1073 leadingDoubles++; | |
1074 } | |
1075 } else { | |
1076 newParts.add(part); | |
1077 } | |
1078 } | |
1079 | |
1080 // A relative path can back out from the start directory. | |
1081 if (!isAbsolute) { | |
1082 _growListFront(newParts, leadingDoubles, '..'); | |
1083 } | |
1084 | |
1085 // If we collapsed down to nothing, do ".". | |
1086 if (newParts.length == 0 && !isAbsolute) { | |
1087 newParts.add('.'); | |
1088 } | |
1089 | |
1090 // Canonicalize separators. | |
1091 var newSeparators = new List.generate( | |
1092 newParts.length, (_) => style.separator, growable: true); | |
1093 newSeparators.insert(0, | |
1094 isAbsolute && newParts.length > 0 && | |
1095 root.contains(style.needsSeparatorPattern) ? | |
1096 style.separator : ''); | |
1097 | |
1098 parts = newParts; | |
1099 separators = newSeparators; | |
1100 | |
1101 // Normalize the Windows root if needed. | |
1102 if (root != null && style == Style.windows) { | |
1103 root = root.replaceAll('/', '\\'); | |
1104 } | |
1105 removeTrailingSeparators(); | |
1106 } | |
1107 | |
1108 String toString() { | |
1109 var builder = new StringBuffer(); | |
1110 if (root != null) builder.write(root); | |
1111 for (var i = 0; i < parts.length; i++) { | |
1112 builder.write(separators[i]); | |
1113 builder.write(parts[i]); | |
1114 } | |
1115 builder.write(separators.last); | |
1116 | |
1117 return builder.toString(); | |
1118 } | |
1119 | |
1120 /// Splits the last part of the path into a two-element list. The first is | |
1121 /// the name of the file without any extension. The second is the extension | |
1122 /// or "" if it has none. | |
1123 List<String> _splitExtension() { | |
1124 if (parts.isEmpty) return ['', '']; | |
1125 | |
1126 var file = parts.last; | |
1127 if (file == '..') return ['..', '']; | |
1128 | |
1129 var lastDot = file.lastIndexOf('.'); | |
1130 | |
1131 // If there is no dot, or it's the first character, like '.bashrc', it | |
1132 // doesn't count. | |
1133 if (lastDot <= 0) return [file, '']; | |
1134 | |
1135 return [file.substring(0, lastDot), file.substring(lastDot)]; | |
1136 } | |
1137 | |
1138 _ParsedPath clone() => new _ParsedPath( | |
1139 style, root, isRootRelative, | |
1140 new List.from(parts), new List.from(separators)); | |
1141 } | |
OLD | NEW |