| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 } |
| OLD | NEW |