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

Side by Side Diff: pkg/pathos/lib/path.dart

Issue 16580005: Support a URL style for pathos. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Code review changes Created 7 years, 6 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | pkg/pathos/test/pathos_url_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /// A comprehensive, cross-platform path manipulation library. 5 /// A comprehensive, cross-platform path manipulation library.
6 /// 6 ///
7 /// ## Installing ## 7 /// ## Installing ##
8 /// 8 ///
9 /// Use [pub][] to install this package. Add the following to your 9 /// Use [pub][] to install this package. Add the following to your
10 /// `pubspec.yaml` file. 10 /// `pubspec.yaml` file.
(...skipping 13 matching lines...) Expand all
24 import 'dart:io' as io; 24 import 'dart:io' as io;
25 25
26 /// An internal builder for the current OS so we can provide a straight 26 /// An internal builder for the current OS so we can provide a straight
27 /// functional interface and not require users to create one. 27 /// functional interface and not require users to create one.
28 final _builder = new Builder(); 28 final _builder = new Builder();
29 29
30 /** 30 /**
31 * Inserts [length] elements in front of the [list] and fills them with the 31 * Inserts [length] elements in front of the [list] and fills them with the
32 * [fillValue]. 32 * [fillValue].
33 */ 33 */
34 void _growListFront(List list, int length, fillValue) { 34 void _growListFront(List list, int length, fillValue) =>
35 list.length += length; 35 list.insertAll(0, new List.filled(length, fillValue));
36 list.setRange(length, list.length, list);
37 for (var i = 0; i < length; i++) {
38 list[i] = fillValue;
39 }
40 }
41 36
42 /// Gets the path to the current working directory. 37 /// Gets the path to the current working directory.
43 String get current => io.Directory.current.path; 38 String get current => io.Directory.current.path;
44 39
45 /// Gets the path separator for the current platform. On Mac and Linux, this 40 /// Gets the path separator for the current platform. On Mac and Linux, this
46 /// is `/`. On Windows, it's `\`. 41 /// is `/`. On Windows, it's `\`.
47 String get separator => _builder.separator; 42 String get separator => _builder.separator;
48 43
49 /// Converts [path] to an absolute path by resolving it relative to the current 44 /// Converts [path] to an absolute path by resolving it relative to the current
50 /// working directory. If [path] is already an absolute path, just returns it. 45 /// working directory. If [path] is already an absolute path, just returns it.
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
105 /// // Unix 100 /// // Unix
106 /// path.rootPrefix('path/to/foo'); // -> '' 101 /// path.rootPrefix('path/to/foo'); // -> ''
107 /// path.rootPrefix('/path/to/foo'); // -> '/' 102 /// path.rootPrefix('/path/to/foo'); // -> '/'
108 /// 103 ///
109 /// // Windows 104 /// // Windows
110 /// path.rootPrefix(r'path\to\foo'); // -> '' 105 /// path.rootPrefix(r'path\to\foo'); // -> ''
111 /// path.rootPrefix(r'C:\path\to\foo'); // -> r'C:\' 106 /// path.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
112 String rootPrefix(String path) => _builder.rootPrefix(path); 107 String rootPrefix(String path) => _builder.rootPrefix(path);
113 108
114 /// Returns `true` if [path] is an absolute path and `false` if it is a 109 /// Returns `true` if [path] is an absolute path and `false` if it is a
115 /// relative path. On POSIX systems, absolute paths start with a `/` (forward 110 /// relative path.
116 /// slash). On Windows, an absolute path starts with `\\`, or a drive letter 111 ///
117 /// followed by `:/` or `:\`. 112 /// On POSIX systems, absolute paths start with a `/` (forward slash). On
113 /// Windows, an absolute path starts with `\\`, or a drive letter followed by
114 /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and
115 /// optional hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`.
116 ///
117 /// URLs that start with `/` are known as "root-relative", since they're
118 /// relative to the root of the current URL. Since root-relative paths are still
119 /// absolute in every other sense, [isAbsolute] will return true for them. They
120 /// can be detected using [isRootRelative].
118 bool isAbsolute(String path) => _builder.isAbsolute(path); 121 bool isAbsolute(String path) => _builder.isAbsolute(path);
119 122
120 /// Returns `true` if [path] is a relative path and `false` if it is absolute. 123 /// Returns `true` if [path] is a relative path and `false` if it is absolute.
121 /// On POSIX systems, absolute paths start with a `/` (forward slash). On 124 /// On POSIX systems, absolute paths start with a `/` (forward slash). On
122 /// Windows, an absolute path starts with `\\`, or a drive letter followed by 125 /// Windows, an absolute path starts with `\\`, or a drive letter followed by
123 /// `:/` or `:\`. 126 /// `:/` or `:\`.
124 bool isRelative(String path) => _builder.isRelative(path); 127 bool isRelative(String path) => _builder.isRelative(path);
125 128
129 /// Returns `true` if [path] is a root-relative path and `false` if it's not.
130 ///
131 /// URLs that start with `/` are known as "root-relative", since they're
132 /// relative to the root of the current URL. Since root-relative paths are still
133 /// absolute in every other sense, [isAbsolute] will return true for them. They
134 /// can be detected using [isRootRelative].
135 ///
136 /// No POSIX and Windows paths are root-relative.
137 bool isRootRelative(String path) => _builder.isRootRelative(path);
138
126 /// Joins the given path parts into a single path using the current platform's 139 /// Joins the given path parts into a single path using the current platform's
127 /// [separator]. Example: 140 /// [separator]. Example:
128 /// 141 ///
129 /// path.join('path', 'to', 'foo'); // -> 'path/to/foo' 142 /// path.join('path', 'to', 'foo'); // -> 'path/to/foo'
130 /// 143 ///
131 /// If any part ends in a path separator, then a redundant separator will not 144 /// If any part ends in a path separator, then a redundant separator will not
132 /// be added: 145 /// be added:
133 /// 146 ///
134 /// path.join('path/', 'to', 'foo'); // -> 'path/to/foo 147 /// path.join('path/', 'to', 'foo'); // -> 'path/to/foo
135 /// 148 ///
(...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after
333 /// 346 ///
334 /// // Windows 347 /// // Windows
335 /// builder.rootPrefix(r'path\to\foo'); // -> '' 348 /// builder.rootPrefix(r'path\to\foo'); // -> ''
336 /// builder.rootPrefix(r'C:\path\to\foo'); // -> r'C:\' 349 /// builder.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
337 String rootPrefix(String path) { 350 String rootPrefix(String path) {
338 var root = _parse(path).root; 351 var root = _parse(path).root;
339 return root == null ? '' : root; 352 return root == null ? '' : root;
340 } 353 }
341 354
342 /// Returns `true` if [path] is an absolute path and `false` if it is a 355 /// Returns `true` if [path] is an absolute path and `false` if it is a
343 /// relative path. On POSIX systems, absolute paths start with a `/` (forward 356 /// relative path.
344 /// slash). On Windows, an absolute path starts with `\\`, or a drive letter 357 ///
345 /// followed by `:/` or `:\`. 358 /// On POSIX systems, absolute paths start with a `/` (forward slash). On
359 /// Windows, an absolute path starts with `\\`, or a drive letter followed by
360 /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and
361 /// optional hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`.
362 ///
363 /// URLs that start with `/` are known as "root-relative", since they're
364 /// relative to the root of the current URL. Since root-relative paths are
365 /// still absolute in every other sense, [isAbsolute] will return true for
366 /// them. They can be detected using [isRootRelative].
346 bool isAbsolute(String path) => _parse(path).isAbsolute; 367 bool isAbsolute(String path) => _parse(path).isAbsolute;
347 368
348 /// Returns `true` if [path] is a relative path and `false` if it is absolute. 369 /// Returns `true` if [path] is a relative path and `false` if it is absolute.
349 /// On POSIX systems, absolute paths start with a `/` (forward slash). On 370 /// On POSIX systems, absolute paths start with a `/` (forward slash). On
350 /// Windows, an absolute path starts with `\\`, or a drive letter followed by 371 /// Windows, an absolute path starts with `\\`, or a drive letter followed by
351 /// `:/` or `:\`. 372 /// `:/` or `:\`.
352 bool isRelative(String path) => !isAbsolute(path); 373 bool isRelative(String path) => !isAbsolute(path);
353 374
375 /// Returns `true` if [path] is a root-relative path and `false` if it's not.
376 ///
377 /// URLs that start with `/` are known as "root-relative", since they're
378 /// relative to the root of the current URL. Since root-relative paths are
379 /// still absolute in every other sense, [isAbsolute] will return true for
380 /// them. They can be detected using [isRootRelative].
381 ///
382 /// No POSIX and Windows paths are root-relative.
383 bool isRootRelative(String path) => _parse(path).isRootRelative;
384
354 /// Joins the given path parts into a single path. Example: 385 /// Joins the given path parts into a single path. Example:
355 /// 386 ///
356 /// builder.join('path', 'to', 'foo'); // -> 'path/to/foo' 387 /// builder.join('path', 'to', 'foo'); // -> 'path/to/foo'
357 /// 388 ///
358 /// If any part ends in a path separator, then a redundant separator will not 389 /// If any part ends in a path separator, then a redundant separator will not
359 /// be added: 390 /// be added:
360 /// 391 ///
361 /// builder.join('path/', 'to', 'foo'); // -> 'path/to/foo 392 /// builder.join('path/', 'to', 'foo'); // -> 'path/to/foo
362 /// 393 ///
363 /// If a part is an absolute path, then anything before that will be ignored: 394 /// If a part is an absolute path, then anything before that will be ignored:
(...skipping 17 matching lines...) Expand all
381 /// builder.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo 412 /// builder.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo
382 /// 413 ///
383 /// If a part is an absolute path, then anything before that will be ignored: 414 /// If a part is an absolute path, then anything before that will be ignored:
384 /// 415 ///
385 /// builder.joinAll(['path', '/to', 'foo']); // -> '/to/foo' 416 /// builder.joinAll(['path', '/to', 'foo']); // -> '/to/foo'
386 /// 417 ///
387 /// For a fixed number of parts, [join] is usually terser. 418 /// For a fixed number of parts, [join] is usually terser.
388 String joinAll(Iterable<String> parts) { 419 String joinAll(Iterable<String> parts) {
389 var buffer = new StringBuffer(); 420 var buffer = new StringBuffer();
390 var needsSeparator = false; 421 var needsSeparator = false;
422 var isAbsoluteAndNotRootRelative = false;
391 423
392 for (var part in parts) { 424 for (var part in parts) {
393 if (this.isAbsolute(part)) { 425 if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) {
426 // If the new part is root-relative, it preserves the previous root but
427 // replaces the path after it.
428 var oldRoot = this.rootPrefix(buffer.toString());
429 buffer.clear();
430 buffer.write(oldRoot);
431 buffer.write(part);
432 } else if (this.isAbsolute(part)) {
433 isAbsoluteAndNotRootRelative = !this.isRootRelative(part);
394 // An absolute path discards everything before it. 434 // An absolute path discards everything before it.
395 buffer = new StringBuffer(); 435 buffer.clear();
396 buffer.write(part); 436 buffer.write(part);
397 } else { 437 } else {
398 if (part.length > 0 && part[0].contains(style.separatorPattern)) { 438 if (part.length > 0 && part[0].contains(style.separatorPattern)) {
399 // The part starts with a separator, so we don't need to add one. 439 // The part starts with a separator, so we don't need to add one.
400 } else if (needsSeparator) { 440 } else if (needsSeparator) {
401 buffer.write(separator); 441 buffer.write(separator);
402 } 442 }
403 443
404 buffer.write(part); 444 buffer.write(part);
405 } 445 }
406 446
407 // Unless this part ends with a separator, we'll need to add one before 447 // Unless this part ends with a separator, we'll need to add one before
408 // the next part. 448 // the next part.
409 needsSeparator = part.length > 0 && 449 needsSeparator = part.contains(style.needsSeparatorPattern);
410 !part[part.length - 1].contains(style.separatorPattern);
411 } 450 }
412 451
413 return buffer.toString(); 452 return buffer.toString();
414 } 453 }
415 454
416 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed. 455 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
417 /// Splits [path] into its components using the current platform's 456 /// Splits [path] into its components using the current platform's
418 /// [separator]. Example: 457 /// [separator]. Example:
419 /// 458 ///
420 /// builder.split('path/to/foo'); // -> ['path', 'to', 'foo'] 459 /// builder.split('path/to/foo'); // -> ['path', 'to', 'foo']
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
491 530
492 from = from == null ? root : this.join(root, from); 531 from = from == null ? root : this.join(root, from);
493 532
494 // We can't determine the path from a relative path to an absolute path. 533 // We can't determine the path from a relative path to an absolute path.
495 if (this.isRelative(from) && this.isAbsolute(path)) { 534 if (this.isRelative(from) && this.isAbsolute(path)) {
496 return this.normalize(path); 535 return this.normalize(path);
497 } 536 }
498 537
499 // If the given path is relative, resolve it relative to the root of the 538 // If the given path is relative, resolve it relative to the root of the
500 // builder. 539 // builder.
501 if (this.isRelative(path)) path = this.resolve(path); 540 if (this.isRelative(path) || this.isRootRelative(path)) {
541 path = this.resolve(path);
542 }
502 543
503 // If the path is still relative and `from` is absolute, we're unable to 544 // If the path is still relative and `from` is absolute, we're unable to
504 // find a path from `from` to `path`. 545 // find a path from `from` to `path`.
505 if (this.isRelative(path) && this.isAbsolute(from)) { 546 if (this.isRelative(path) && this.isAbsolute(from)) {
506 throw new ArgumentError('Unable to find a path to "$path" from "$from".'); 547 throw new ArgumentError('Unable to find a path to "$path" from "$from".');
507 } 548 }
508 549
509 var fromParsed = _parse(from)..normalize(); 550 var fromParsed = _parse(from)..normalize();
510 var pathParsed = _parse(path)..normalize(); 551 var pathParsed = _parse(path)..normalize();
511 552
512 // If the root prefixes don't match (for example, different drive letters 553 // If the root prefixes don't match (for example, different drive letters
513 // on Windows), then there is no relative path, so just return the absolute 554 // on Windows), then there is no relative path, so just return the absolute
514 // one. In Windows, drive letters are case-insenstive and we allow 555 // one. In Windows, drive letters are case-insenstive and we allow
515 // calculation of relative paths, even if a path has not been normalized. 556 // calculation of relative paths, even if a path has not been normalized.
516 if (fromParsed.root != pathParsed.root && 557 if (fromParsed.root != pathParsed.root &&
517 ((fromParsed.root == null || pathParsed.root == null) || 558 ((fromParsed.root == null || pathParsed.root == null) ||
518 fromParsed.root.toLowerCase().replaceAll('/', '\\') != 559 fromParsed.root.toLowerCase().replaceAll('/', '\\') !=
519 pathParsed.root.toLowerCase().replaceAll('/', '\\'))) { 560 pathParsed.root.toLowerCase().replaceAll('/', '\\'))) {
520 return pathParsed.toString(); 561 return pathParsed.toString();
521 } 562 }
522 563
523 // Strip off their common prefix. 564 // Strip off their common prefix.
524 while (fromParsed.parts.length > 0 && pathParsed.parts.length > 0 && 565 while (fromParsed.parts.length > 0 && pathParsed.parts.length > 0 &&
525 fromParsed.parts[0] == pathParsed.parts[0]) { 566 fromParsed.parts[0] == pathParsed.parts[0]) {
526 fromParsed.parts.removeAt(0); 567 fromParsed.parts.removeAt(0);
527 fromParsed.separators.removeAt(0); 568 fromParsed.separators.removeAt(1);
528 pathParsed.parts.removeAt(0); 569 pathParsed.parts.removeAt(0);
529 pathParsed.separators.removeAt(0); 570 pathParsed.separators.removeAt(1);
530 } 571 }
531 572
532 // If there are any directories left in the root path, we need to walk up 573 // If there are any directories left in the root path, we need to walk up
533 // out of them. 574 // out of them.
534 _growListFront(pathParsed.parts, fromParsed.parts.length, '..'); 575 _growListFront(pathParsed.parts, fromParsed.parts.length, '..');
535 _growListFront( 576 pathParsed.separators[0] = '';
536 pathParsed.separators, fromParsed.parts.length, style.separator); 577 pathParsed.separators.insertAll(1,
578 new List.filled(fromParsed.parts.length, style.separator));
537 579
538 // Corner case: the paths completely collapsed. 580 // Corner case: the paths completely collapsed.
539 if (pathParsed.parts.length == 0) return '.'; 581 if (pathParsed.parts.length == 0) return '.';
540 582
541 // Make it relative. 583 // Make it relative.
542 pathParsed.root = ''; 584 pathParsed.root = '';
543 pathParsed.removeTrailingSeparators(); 585 pathParsed.removeTrailingSeparators();
544 586
545 return pathParsed.toString(); 587 return pathParsed.toString();
546 } 588 }
(...skipping 12 matching lines...) Expand all
559 } 601 }
560 602
561 return parsed.toString(); 603 return parsed.toString();
562 } 604 }
563 605
564 _ParsedPath _parse(String path) { 606 _ParsedPath _parse(String path) {
565 var before = path; 607 var before = path;
566 608
567 // Remove the root prefix, if any. 609 // Remove the root prefix, if any.
568 var root = style.getRoot(path); 610 var root = style.getRoot(path);
611 var isRootRelative = style.getRelativeRoot(path) != null;
569 if (root != null) path = path.substring(root.length); 612 if (root != null) path = path.substring(root.length);
570 613
571 // Split the parts on path separators. 614 // Split the parts on path separators.
572 var parts = []; 615 var parts = [];
573 var separators = []; 616 var separators = [];
617
618 var firstSeparator = style.separatorPattern.firstMatch(path);
619 if (firstSeparator != null && firstSeparator.start == 0) {
620 separators.add(firstSeparator[0]);
621 path = path.substring(firstSeparator[0].length);
622 } else {
623 separators.add('');
624 }
625
574 var start = 0; 626 var start = 0;
575 for (var match in style.separatorPattern.allMatches(path)) { 627 for (var match in style.separatorPattern.allMatches(path)) {
576 parts.add(path.substring(start, match.start)); 628 parts.add(path.substring(start, match.start));
577 separators.add(match[0]); 629 separators.add(match[0]);
578 start = match.end; 630 start = match.end;
579 } 631 }
580 632
581 // Add the final part, if any. 633 // Add the final part, if any.
582 if (start < path.length) { 634 if (start < path.length) {
583 parts.add(path.substring(start)); 635 parts.add(path.substring(start));
584 separators.add(''); 636 separators.add('');
585 } 637 }
586 638
587 return new _ParsedPath(style, root, parts, separators); 639 return new _ParsedPath(style, root, isRootRelative, parts, separators);
588 } 640 }
589 } 641 }
590 642
591 /// An enum type describing a "flavor" of path. 643 /// An enum type describing a "flavor" of path.
592 class Style { 644 class Style {
593 /// POSIX-style paths use "/" (forward slash) as separators. Absolute paths 645 /// POSIX-style paths use "/" (forward slash) as separators. Absolute paths
594 /// start with "/". Used by UNIX, Linux, Mac OS X, and others. 646 /// start with "/". Used by UNIX, Linux, Mac OS X, and others.
595 static final posix = new Style._('posix', '/', '/', '/'); 647 static final posix = new Style._('posix', '/', '/', r'[^/]$', '/');
596 648
597 /// Windows paths use "\" (backslash) as separators. Absolute paths start with 649 /// Windows paths use "\" (backslash) as separators. Absolute paths start with
598 /// a drive letter followed by a colon (example, "C:") or two backslashes 650 /// a drive letter followed by a colon (example, "C:") or two backslashes
599 /// ("\\") for UNC paths. 651 /// ("\\") for UNC paths.
600 // TODO(rnystrom): The UNC root prefix should include the drive name too, not 652 // TODO(rnystrom): The UNC root prefix should include the drive name too, not
601 // just the "\\". 653 // just the "\\".
602 static final windows = new Style._('windows', '\\', r'[/\\]', 654 static final windows = new Style._('windows', '\\', r'[/\\]', r'[^/\\]$',
603 r'\\\\|[a-zA-Z]:[/\\]'); 655 r'\\\\|[a-zA-Z]:[/\\]');
604 656
657 /// URLs aren't filesystem paths, but they're supported by Pathos to make it
658 /// easier to manipulate URL paths in the browser.
659 ///
660 /// URLs use "/" (forward slash) as separators. Absolute paths either start
661 /// with a protocol and optional hostname (e.g. `http://dartlang.org`,
662 /// `file://`) or with "/".
663 static final url = new Style._('url', '/', '/',
664 r"(^[a-zA-Z][-+.a-zA-Z\d]*://|[^/])$",
665 r"[a-zA-Z][-+.a-zA-Z\d]*://[^/]*", r"/");
666
605 Style._(this.name, this.separator, String separatorPattern, 667 Style._(this.name, this.separator, String separatorPattern,
606 String rootPattern) 668 String needsSeparatorPattern, String rootPattern,
669 [String relativeRootPattern])
607 : separatorPattern = new RegExp(separatorPattern), 670 : separatorPattern = new RegExp(separatorPattern),
608 _rootPattern = new RegExp('^$rootPattern'); 671 needsSeparatorPattern = new RegExp(needsSeparatorPattern),
672 _rootPattern = new RegExp('^$rootPattern'),
673 _relativeRootPattern = relativeRootPattern == null ? null :
674 new RegExp('^$relativeRootPattern');
609 675
610 /// The name of this path style. Will be "posix" or "windows". 676 /// The name of this path style. Will be "posix" or "windows".
611 final String name; 677 final String name;
612 678
613 /// The path separator for this style. On POSIX, this is `/`. On Windows, 679 /// The path separator for this style. On POSIX, this is `/`. On Windows,
614 /// it's `\`. 680 /// it's `\`.
615 final String separator; 681 final String separator;
616 682
617 /// The [Pattern] that can be used to match a separator for a path in this 683 /// The [Pattern] that can be used to match a separator for a path in this
618 /// style. Windows allows both "/" and "\" as path separators even though 684 /// style. Windows allows both "/" and "\" as path separators even though
619 /// "\" is the canonical one. 685 /// "\" is the canonical one.
620 final Pattern separatorPattern; 686 final Pattern separatorPattern;
621 687
688 /// The [Pattern] that matches path components that need a separator after
689 /// them.
690 ///
691 /// Windows and POSIX styles just need separators when the previous component
692 /// doesn't already end in a separator, but the URL always needs to place a
693 /// separator between the root and the first component, even if the root
694 /// already ends in a separator character. For example, to join "file://" and
695 /// "usr", an additional "/" is needed (making "file:///usr").
696 final Pattern needsSeparatorPattern;
697
622 // TODO(nweiz): make this a Pattern when issue 7080 is fixed. 698 // TODO(nweiz): make this a Pattern when issue 7080 is fixed.
623 /// The [RegExp] that can be used to match the root prefix of an absolute 699 /// The [RegExp] that can be used to match the root prefix of an absolute
624 /// path in this style. 700 /// path in this style.
625 final RegExp _rootPattern; 701 final RegExp _rootPattern;
626 702
703 /// The [RegExp] that can be used to match the root prefix of a root-relative
704 /// path in this style.
705 ///
706 /// This can be null to indicate that this style doesn't support root-relative
707 /// paths.
708 final RegExp _relativeRootPattern;
709
627 /// Gets the root prefix of [path] if path is absolute. If [path] is relative, 710 /// Gets the root prefix of [path] if path is absolute. If [path] is relative,
628 /// returns `null`. 711 /// returns `null`.
629 String getRoot(String path) { 712 String getRoot(String path) {
630 var match = _rootPattern.firstMatch(path); 713 var match = _rootPattern.firstMatch(path);
714 if (match != null) return match[0];
715 return getRelativeRoot(path);
716 }
717
718 /// Gets the root prefix of [path] if it's root-relative.
719 ///
720 /// If [path] is relative or absolute and not root-relative, returns `null`.
721 String getRelativeRoot(String path) {
722 if (_relativeRootPattern == null) return null;
723 var match = _relativeRootPattern.firstMatch(path);
631 if (match == null) return null; 724 if (match == null) return null;
632 return match[0]; 725 return match[0];
633 } 726 }
634 727
635 String toString() => name; 728 String toString() => name;
636 } 729 }
637 730
638 // TODO(rnystrom): Make this public? 731 // TODO(rnystrom): Make this public?
639 class _ParsedPath { 732 class _ParsedPath {
640 /// The [Style] that was used to parse this path. 733 /// The [Style] that was used to parse this path.
641 Style style; 734 Style style;
642 735
643 /// The absolute root portion of the path, or `null` if the path is relative. 736 /// The absolute root portion of the path, or `null` if the path is relative.
644 /// On POSIX systems, this will be `null` or "/". On Windows, it can be 737 /// On POSIX systems, this will be `null` or "/". On Windows, it can be
645 /// `null`, "//" for a UNC path, or something like "C:\" for paths with drive 738 /// `null`, "//" for a UNC path, or something like "C:\" for paths with drive
646 /// letters. 739 /// letters.
647 String root; 740 String root;
648 741
742 /// Whether this path is root-relative.
743 ///
744 /// See [Builder.isRootRelative].
745 bool isRootRelative;
746
649 /// The path-separated parts of the path. All but the last will be 747 /// The path-separated parts of the path. All but the last will be
650 /// directories. 748 /// directories.
651 List<String> parts; 749 List<String> parts;
652 750
653 /// The path separators following each part. The last one will be an empty 751 /// The path separators preceding each part.
654 /// string unless the path ends with a trailing separator. 752 ///
753 /// The first one will be an empty string unless the root requires a separator
754 /// between it and the path. The last one will be an empty string unless the
755 /// path ends with a trailing separator.
655 List<String> separators; 756 List<String> separators;
656 757
657 /// The file extension of the last part, or "" if it doesn't have one. 758 /// The file extension of the last part, or "" if it doesn't have one.
658 String get extension => _splitExtension()[1]; 759 String get extension => _splitExtension()[1];
659 760
660 /// `true` if this is an absolute path. 761 /// `true` if this is an absolute path.
661 bool get isAbsolute => root != null; 762 bool get isAbsolute => root != null;
662 763
663 _ParsedPath(this.style, this.root, this.parts, this.separators); 764 _ParsedPath(this.style, this.root, this.isRootRelative, this.parts,
765 this.separators);
664 766
665 String get basename { 767 String get basename {
666 var copy = this.clone(); 768 var copy = this.clone();
667 copy.removeTrailingSeparators(); 769 copy.removeTrailingSeparators();
668 if (copy.parts.isEmpty) return root == null ? '' : root; 770 if (copy.parts.isEmpty) return root == null ? '' : root;
669 return copy.parts.last; 771 return copy.parts.last;
670 } 772 }
671 773
672 String get basenameWithoutExtension { 774 String get basenameWithoutExtension {
673 var copy = this.clone(); 775 var copy = this.clone();
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
708 if (!isAbsolute) { 810 if (!isAbsolute) {
709 _growListFront(newParts, leadingDoubles, '..'); 811 _growListFront(newParts, leadingDoubles, '..');
710 } 812 }
711 813
712 // If we collapsed down to nothing, do ".". 814 // If we collapsed down to nothing, do ".".
713 if (newParts.length == 0 && !isAbsolute) { 815 if (newParts.length == 0 && !isAbsolute) {
714 newParts.add('.'); 816 newParts.add('.');
715 } 817 }
716 818
717 // Canonicalize separators. 819 // Canonicalize separators.
718 var newSeparators = []; 820 var newSeparators = new List.generate(
719 _growListFront(newSeparators, newParts.length, style.separator); 821 newParts.length, (_) => style.separator, growable: true);
822 newSeparators.insert(0,
823 isAbsolute && newParts.length > 0 &&
824 root.contains(style.needsSeparatorPattern) ?
825 style.separator : '');
720 826
721 parts = newParts; 827 parts = newParts;
722 separators = newSeparators; 828 separators = newSeparators;
723 829
724 // Normalize the Windows root if needed. 830 // Normalize the Windows root if needed.
725 if (root != null && style == Style.windows) { 831 if (root != null && style == Style.windows) {
726 root = root.replaceAll('/', '\\'); 832 root = root.replaceAll('/', '\\');
727 } 833 }
728 removeTrailingSeparators(); 834 removeTrailingSeparators();
729 } 835 }
730 836
731 String toString() { 837 String toString() {
732 var builder = new StringBuffer(); 838 var builder = new StringBuffer();
733 if (root != null) builder.write(root); 839 if (root != null) builder.write(root);
734 for (var i = 0; i < parts.length; i++) { 840 for (var i = 0; i < parts.length; i++) {
841 builder.write(separators[i]);
735 builder.write(parts[i]); 842 builder.write(parts[i]);
736 builder.write(separators[i]);
737 } 843 }
844 builder.write(separators.last);
738 845
739 return builder.toString(); 846 return builder.toString();
740 } 847 }
741 848
742 /// Splits the last part of the path into a two-element list. The first is 849 /// Splits the last part of the path into a two-element list. The first is
743 /// the name of the file without any extension. The second is the extension 850 /// the name of the file without any extension. The second is the extension
744 /// or "" if it has none. 851 /// or "" if it has none.
745 List<String> _splitExtension() { 852 List<String> _splitExtension() {
746 if (parts.isEmpty) return ['', '']; 853 if (parts.isEmpty) return ['', ''];
747 854
748 var file = parts.last; 855 var file = parts.last;
749 if (file == '..') return ['..', '']; 856 if (file == '..') return ['..', ''];
750 857
751 var lastDot = file.lastIndexOf('.'); 858 var lastDot = file.lastIndexOf('.');
752 859
753 // If there is no dot, or it's the first character, like '.bashrc', it 860 // If there is no dot, or it's the first character, like '.bashrc', it
754 // doesn't count. 861 // doesn't count.
755 if (lastDot <= 0) return [file, '']; 862 if (lastDot <= 0) return [file, ''];
756 863
757 return [file.substring(0, lastDot), file.substring(lastDot)]; 864 return [file.substring(0, lastDot), file.substring(lastDot)];
758 } 865 }
759 866
760 _ParsedPath clone() => new _ParsedPath( 867 _ParsedPath clone() => new _ParsedPath(
761 style, root, new List.from(parts), new List.from(separators)); 868 style, root, isRootRelative,
869 new List.from(parts), new List.from(separators));
762 } 870 }
OLDNEW
« no previous file with comments | « no previous file | pkg/pathos/test/pathos_url_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698