Chromium Code Reviews| 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 sti ll | |
|
Bob Nystrom
2013/06/06 22:50:40
Long lines.
nweiz
2013/06/07 01:04:30
Done.
| |
| 365 /// absolute in every other sense, [isAbsolute] will return true for them. The y | |
| 366 /// 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 sti ll | |
|
Bob Nystrom
2013/06/06 22:50:40
Ditto.
nweiz
2013/06/07 01:04:30
Done.
| |
| 379 /// absolute in every other sense, [isAbsolute] will return true for them. The y | |
| 380 /// 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 19 matching lines...) Expand all Loading... | |
| 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; |
| 391 | 422 |
| 392 for (var part in parts) { | 423 for (var part in parts) { |
| 393 if (this.isAbsolute(part)) { | 424 if (this.isRootRelative(part) && |
| 425 this.isAbsolute(buffer.toString()) && | |
| 426 !this.isRootRelative(buffer.toString())) { | |
| 427 var oldRoot = this.rootPrefix(buffer.toString()); | |
|
Bob Nystrom
2013/06/06 22:50:40
A couple of things here:
1. Calling toString() on
nweiz
2013/06/07 01:04:30
Done.
| |
| 428 buffer = new StringBuffer(); | |
|
Bob Nystrom
2013/06/06 22:50:40
buffer.clear() seems cleaner to me. Below too?
nweiz
2013/06/07 01:04:30
Done.
| |
| 429 buffer.write(oldRoot); | |
| 430 buffer.write(part); | |
| 431 } else if (this.isAbsolute(part)) { | |
| 394 // An absolute path discards everything before it. | 432 // An absolute path discards everything before it. |
| 395 buffer = new StringBuffer(); | 433 buffer = new StringBuffer(); |
| 396 buffer.write(part); | 434 buffer.write(part); |
| 397 } else { | 435 } else { |
| 398 if (part.length > 0 && part[0].contains(style.separatorPattern)) { | 436 if (part.length > 0 && part[0].contains(style.separatorPattern)) { |
| 399 // The part starts with a separator, so we don't need to add one. | 437 // The part starts with a separator, so we don't need to add one. |
| 400 } else if (needsSeparator) { | 438 } else if (needsSeparator) { |
| 401 buffer.write(separator); | 439 buffer.write(separator); |
| 402 } | 440 } |
| 403 | 441 |
| 404 buffer.write(part); | 442 buffer.write(part); |
| 405 } | 443 } |
| 406 | 444 |
| 407 // Unless this part ends with a separator, we'll need to add one before | 445 // Unless this part ends with a separator, we'll need to add one before |
| 408 // the next part. | 446 // the next part. |
| 409 needsSeparator = part.length > 0 && | 447 needsSeparator = part.contains(style.needsSeparatorPattern); |
| 410 !part[part.length - 1].contains(style.separatorPattern); | |
| 411 } | 448 } |
| 412 | 449 |
| 413 return buffer.toString(); | 450 return buffer.toString(); |
| 414 } | 451 } |
| 415 | 452 |
| 416 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed. | 453 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed. |
| 417 /// Splits [path] into its components using the current platform's | 454 /// Splits [path] into its components using the current platform's |
| 418 /// [separator]. Example: | 455 /// [separator]. Example: |
| 419 /// | 456 /// |
| 420 /// builder.split('path/to/foo'); // -> ['path', 'to', 'foo'] | 457 /// builder.split('path/to/foo'); // -> ['path', 'to', 'foo'] |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 491 | 528 |
| 492 from = from == null ? root : this.join(root, from); | 529 from = from == null ? root : this.join(root, from); |
| 493 | 530 |
| 494 // We can't determine the path from a relative path to an absolute path. | 531 // We can't determine the path from a relative path to an absolute path. |
| 495 if (this.isRelative(from) && this.isAbsolute(path)) { | 532 if (this.isRelative(from) && this.isAbsolute(path)) { |
| 496 return this.normalize(path); | 533 return this.normalize(path); |
| 497 } | 534 } |
| 498 | 535 |
| 499 // If the given path is relative, resolve it relative to the root of the | 536 // If the given path is relative, resolve it relative to the root of the |
| 500 // builder. | 537 // builder. |
| 501 if (this.isRelative(path)) path = this.resolve(path); | 538 if (this.isRelative(path) || this.isRootRelative(path)) { |
| 539 path = this.resolve(path); | |
| 540 } | |
| 502 | 541 |
| 503 // If the path is still relative and `from` is absolute, we're unable to | 542 // If the path is still relative and `from` is absolute, we're unable to |
| 504 // find a path from `from` to `path`. | 543 // find a path from `from` to `path`. |
| 505 if (this.isRelative(path) && this.isAbsolute(from)) { | 544 if (this.isRelative(path) && this.isAbsolute(from)) { |
| 506 throw new ArgumentError('Unable to find a path to "$path" from "$from".'); | 545 throw new ArgumentError('Unable to find a path to "$path" from "$from".'); |
| 507 } | 546 } |
| 508 | 547 |
| 509 var fromParsed = _parse(from)..normalize(); | 548 var fromParsed = _parse(from)..normalize(); |
| 510 var pathParsed = _parse(path)..normalize(); | 549 var pathParsed = _parse(path)..normalize(); |
| 511 | 550 |
| 512 // If the root prefixes don't match (for example, different drive letters | 551 // 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 | 552 // 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 | 553 // one. In Windows, drive letters are case-insenstive and we allow |
| 515 // calculation of relative paths, even if a path has not been normalized. | 554 // calculation of relative paths, even if a path has not been normalized. |
| 516 if (fromParsed.root != pathParsed.root && | 555 if (fromParsed.root != pathParsed.root && |
| 517 ((fromParsed.root == null || pathParsed.root == null) || | 556 ((fromParsed.root == null || pathParsed.root == null) || |
| 518 fromParsed.root.toLowerCase().replaceAll('/', '\\') != | 557 fromParsed.root.toLowerCase().replaceAll('/', '\\') != |
| 519 pathParsed.root.toLowerCase().replaceAll('/', '\\'))) { | 558 pathParsed.root.toLowerCase().replaceAll('/', '\\'))) { |
| 520 return pathParsed.toString(); | 559 return pathParsed.toString(); |
| 521 } | 560 } |
| 522 | 561 |
| 523 // Strip off their common prefix. | 562 // Strip off their common prefix. |
| 524 while (fromParsed.parts.length > 0 && pathParsed.parts.length > 0 && | 563 while (fromParsed.parts.length > 0 && pathParsed.parts.length > 0 && |
| 525 fromParsed.parts[0] == pathParsed.parts[0]) { | 564 fromParsed.parts[0] == pathParsed.parts[0]) { |
| 526 fromParsed.parts.removeAt(0); | 565 fromParsed.parts.removeAt(0); |
| 527 fromParsed.separators.removeAt(0); | 566 fromParsed.separators.removeAt(1); |
| 528 pathParsed.parts.removeAt(0); | 567 pathParsed.parts.removeAt(0); |
| 529 pathParsed.separators.removeAt(0); | 568 pathParsed.separators.removeAt(1); |
| 530 } | 569 } |
| 531 | 570 |
| 532 // If there are any directories left in the root path, we need to walk up | 571 // If there are any directories left in the root path, we need to walk up |
| 533 // out of them. | 572 // out of them. |
| 534 _growListFront(pathParsed.parts, fromParsed.parts.length, '..'); | 573 _growListFront(pathParsed.parts, fromParsed.parts.length, '..'); |
| 535 _growListFront( | 574 pathParsed.separators[0] = ''; |
| 536 pathParsed.separators, fromParsed.parts.length, style.separator); | 575 pathParsed.separators.insertAll(1, |
| 576 new List.filled(fromParsed.parts.length, style.separator)); | |
| 537 | 577 |
| 538 // Corner case: the paths completely collapsed. | 578 // Corner case: the paths completely collapsed. |
| 539 if (pathParsed.parts.length == 0) return '.'; | 579 if (pathParsed.parts.length == 0) return '.'; |
| 540 | 580 |
| 541 // Make it relative. | 581 // Make it relative. |
| 542 pathParsed.root = ''; | 582 pathParsed.root = ''; |
| 543 pathParsed.removeTrailingSeparators(); | 583 pathParsed.removeTrailingSeparators(); |
| 544 | 584 |
| 545 return pathParsed.toString(); | 585 return pathParsed.toString(); |
| 546 } | 586 } |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 559 } | 599 } |
| 560 | 600 |
| 561 return parsed.toString(); | 601 return parsed.toString(); |
| 562 } | 602 } |
| 563 | 603 |
| 564 _ParsedPath _parse(String path) { | 604 _ParsedPath _parse(String path) { |
| 565 var before = path; | 605 var before = path; |
| 566 | 606 |
| 567 // Remove the root prefix, if any. | 607 // Remove the root prefix, if any. |
| 568 var root = style.getRoot(path); | 608 var root = style.getRoot(path); |
| 609 var isRootRelative = style.getRelativeRoot(path) != null; | |
| 569 if (root != null) path = path.substring(root.length); | 610 if (root != null) path = path.substring(root.length); |
| 570 | 611 |
| 571 // Split the parts on path separators. | 612 // Split the parts on path separators. |
| 572 var parts = []; | 613 var parts = []; |
| 573 var separators = []; | 614 var separators = []; |
| 615 | |
| 616 var firstSeparator = style.separatorPattern.firstMatch(path); | |
| 617 if (firstSeparator != null && firstSeparator.start == 0) { | |
| 618 separators.add(firstSeparator[0]); | |
| 619 path = path.substring(firstSeparator[0].length); | |
| 620 } else { | |
| 621 separators.add(''); | |
| 622 } | |
| 623 | |
| 574 var start = 0; | 624 var start = 0; |
| 575 for (var match in style.separatorPattern.allMatches(path)) { | 625 for (var match in style.separatorPattern.allMatches(path)) { |
| 576 parts.add(path.substring(start, match.start)); | 626 parts.add(path.substring(start, match.start)); |
| 577 separators.add(match[0]); | 627 separators.add(match[0]); |
| 578 start = match.end; | 628 start = match.end; |
| 579 } | 629 } |
| 580 | 630 |
| 581 // Add the final part, if any. | 631 // Add the final part, if any. |
| 582 if (start < path.length) { | 632 if (start < path.length) { |
| 583 parts.add(path.substring(start)); | 633 parts.add(path.substring(start)); |
| 584 separators.add(''); | 634 separators.add(''); |
| 585 } | 635 } |
| 586 | 636 |
| 587 return new _ParsedPath(style, root, parts, separators); | 637 return new _ParsedPath(style, root, isRootRelative, parts, separators); |
| 588 } | 638 } |
| 589 } | 639 } |
| 590 | 640 |
| 591 /// An enum type describing a "flavor" of path. | 641 /// An enum type describing a "flavor" of path. |
| 592 class Style { | 642 class Style { |
| 593 /// POSIX-style paths use "/" (forward slash) as separators. Absolute paths | 643 /// POSIX-style paths use "/" (forward slash) as separators. Absolute paths |
| 594 /// start with "/". Used by UNIX, Linux, Mac OS X, and others. | 644 /// start with "/". Used by UNIX, Linux, Mac OS X, and others. |
| 595 static final posix = new Style._('posix', '/', '/', '/'); | 645 static final posix = new Style._('posix', '/', '/', r'[^/]$', '/'); |
| 596 | 646 |
| 597 /// Windows paths use "\" (backslash) as separators. Absolute paths start with | 647 /// Windows paths use "\" (backslash) as separators. Absolute paths start with |
| 598 /// a drive letter followed by a colon (example, "C:") or two backslashes | 648 /// a drive letter followed by a colon (example, "C:") or two backslashes |
| 599 /// ("\\") for UNC paths. | 649 /// ("\\") for UNC paths. |
| 600 // TODO(rnystrom): The UNC root prefix should include the drive name too, not | 650 // TODO(rnystrom): The UNC root prefix should include the drive name too, not |
| 601 // just the "\\". | 651 // just the "\\". |
| 602 static final windows = new Style._('windows', '\\', r'[/\\]', | 652 static final windows = new Style._('windows', '\\', r'[/\\]', r'[^/\\]$', |
| 603 r'\\\\|[a-zA-Z]:[/\\]'); | 653 r'\\\\|[a-zA-Z]:[/\\]'); |
| 604 | 654 |
| 655 /// URLs aren't filesystem paths, but they're supported by Pathos to make it | |
| 656 /// easier to manipulate URL paths in the browser. | |
| 657 /// | |
| 658 /// URLs use "/" (forward slash) as separators. Absolute paths either start | |
| 659 /// with a protocol and optional hostname (e.g. `http://dartlang.org`, | |
| 660 /// `file://`) or with "/". | |
| 661 static final url = new Style._('url', '/', '/', | |
| 662 r"(^[a-zA-Z][-+.a-zA-Z\d]*://|[^/])$", | |
| 663 r"[a-zA-Z][-+.a-zA-Z\d]*://[^/]*", r"/"); | |
| 664 | |
| 605 Style._(this.name, this.separator, String separatorPattern, | 665 Style._(this.name, this.separator, String separatorPattern, |
| 606 String rootPattern) | 666 String needsSeparatorPattern, String rootPattern, |
| 667 [String relativeRootPattern]) | |
| 607 : separatorPattern = new RegExp(separatorPattern), | 668 : separatorPattern = new RegExp(separatorPattern), |
| 608 _rootPattern = new RegExp('^$rootPattern'); | 669 needsSeparatorPattern = new RegExp(needsSeparatorPattern), |
| 670 _rootPattern = new RegExp('^$rootPattern'), | |
| 671 _relativeRootPattern = relativeRootPattern == null ? null : | |
| 672 new RegExp('^$relativeRootPattern'); | |
| 609 | 673 |
| 610 /// The name of this path style. Will be "posix" or "windows". | 674 /// The name of this path style. Will be "posix" or "windows". |
| 611 final String name; | 675 final String name; |
| 612 | 676 |
| 613 /// The path separator for this style. On POSIX, this is `/`. On Windows, | 677 /// The path separator for this style. On POSIX, this is `/`. On Windows, |
| 614 /// it's `\`. | 678 /// it's `\`. |
| 615 final String separator; | 679 final String separator; |
| 616 | 680 |
| 617 /// The [Pattern] that can be used to match a separator for a path in this | 681 /// 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 | 682 /// style. Windows allows both "/" and "\" as path separators even though |
| 619 /// "\" is the canonical one. | 683 /// "\" is the canonical one. |
| 620 final Pattern separatorPattern; | 684 final Pattern separatorPattern; |
| 621 | 685 |
| 686 /// The [Pattern] that matches path components that need a separator after | |
| 687 /// them. | |
| 688 /// | |
| 689 /// Some styles ([url] in particlar) have moderately complex rules about when | |
| 690 /// a separator is needed, especially for root path components. | |
|
Bob Nystrom
2013/06/06 22:50:40
Can you explain what these rules are somewhere?
nweiz
2013/06/07 01:04:30
Done.
| |
| 691 final Pattern needsSeparatorPattern; | |
| 692 | |
| 622 // TODO(nweiz): make this a Pattern when issue 7080 is fixed. | 693 // 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 | 694 /// The [RegExp] that can be used to match the root prefix of an absolute |
| 624 /// path in this style. | 695 /// path in this style. |
| 625 final RegExp _rootPattern; | 696 final RegExp _rootPattern; |
| 626 | 697 |
| 698 /// The [RegExp] that can be used to match the root prefix of a root-relative | |
| 699 /// path in this style. | |
| 700 /// | |
| 701 /// This can be null to indicate that this style doesn't support root-relative | |
| 702 /// paths. | |
| 703 final RegExp _relativeRootPattern; | |
| 704 | |
| 627 /// Gets the root prefix of [path] if path is absolute. If [path] is relative, | 705 /// Gets the root prefix of [path] if path is absolute. If [path] is relative, |
| 628 /// returns `null`. | 706 /// returns `null`. |
| 629 String getRoot(String path) { | 707 String getRoot(String path) { |
| 630 var match = _rootPattern.firstMatch(path); | 708 var match = _rootPattern.firstMatch(path); |
| 709 if (match == null) return getRelativeRoot(path); | |
| 710 return match[0]; | |
|
Bob Nystrom
2013/06/06 22:50:40
Seems weird to refer back to match here after dele
nweiz
2013/06/07 01:04:30
Done.
| |
| 711 } | |
| 712 | |
| 713 /// Gets the root prefix of [path] if it's root-relative. | |
| 714 /// | |
| 715 /// If [path] is relative or absolute and not root-relative, returns `null`. | |
| 716 String getRelativeRoot(String path) { | |
| 717 if (_relativeRootPattern == null) return null; | |
| 718 var match = _relativeRootPattern.firstMatch(path); | |
| 631 if (match == null) return null; | 719 if (match == null) return null; |
| 632 return match[0]; | 720 return match[0]; |
| 633 } | 721 } |
| 634 | 722 |
| 635 String toString() => name; | 723 String toString() => name; |
| 636 } | 724 } |
| 637 | 725 |
| 638 // TODO(rnystrom): Make this public? | 726 // TODO(rnystrom): Make this public? |
| 639 class _ParsedPath { | 727 class _ParsedPath { |
| 640 /// The [Style] that was used to parse this path. | 728 /// The [Style] that was used to parse this path. |
| 641 Style style; | 729 Style style; |
| 642 | 730 |
| 643 /// The absolute root portion of the path, or `null` if the path is relative. | 731 /// 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 | 732 /// 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 | 733 /// `null`, "//" for a UNC path, or something like "C:\" for paths with drive |
| 646 /// letters. | 734 /// letters. |
| 647 String root; | 735 String root; |
| 648 | 736 |
| 737 /// Whether this path is root-relative. | |
| 738 /// | |
| 739 /// See [Builder.isRootRelative]. | |
| 740 bool isRootRelative; | |
| 741 | |
| 649 /// The path-separated parts of the path. All but the last will be | 742 /// The path-separated parts of the path. All but the last will be |
| 650 /// directories. | 743 /// directories. |
| 651 List<String> parts; | 744 List<String> parts; |
| 652 | 745 |
| 653 /// The path separators following each part. The last one will be an empty | 746 /// The path separators preceeding each part. |
|
Bob Nystrom
2013/06/06 22:50:40
"preceeding" -> "preceding".
nweiz
2013/06/07 01:04:30
Done.
| |
| 654 /// string unless the path ends with a trailing separator. | 747 /// |
| 748 /// The first one will be an empty string unless the root requires a separator | |
| 749 /// between it and the path. The last one will be an empty string unless the | |
| 750 /// path ends with a trailing separator. | |
| 655 List<String> separators; | 751 List<String> separators; |
| 656 | 752 |
| 657 /// The file extension of the last part, or "" if it doesn't have one. | 753 /// The file extension of the last part, or "" if it doesn't have one. |
| 658 String get extension => _splitExtension()[1]; | 754 String get extension => _splitExtension()[1]; |
| 659 | 755 |
| 660 /// `true` if this is an absolute path. | 756 /// `true` if this is an absolute path. |
| 661 bool get isAbsolute => root != null; | 757 bool get isAbsolute => root != null; |
| 662 | 758 |
| 663 _ParsedPath(this.style, this.root, this.parts, this.separators); | 759 _ParsedPath(this.style, this.root, this.isRootRelative, this.parts, |
| 760 this.separators); | |
| 664 | 761 |
| 665 String get basename { | 762 String get basename { |
| 666 var copy = this.clone(); | 763 var copy = this.clone(); |
| 667 copy.removeTrailingSeparators(); | 764 copy.removeTrailingSeparators(); |
| 668 if (copy.parts.isEmpty) return root == null ? '' : root; | 765 if (copy.parts.isEmpty) return root == null ? '' : root; |
| 669 return copy.parts.last; | 766 return copy.parts.last; |
| 670 } | 767 } |
| 671 | 768 |
| 672 String get basenameWithoutExtension { | 769 String get basenameWithoutExtension { |
| 673 var copy = this.clone(); | 770 var copy = this.clone(); |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 708 if (!isAbsolute) { | 805 if (!isAbsolute) { |
| 709 _growListFront(newParts, leadingDoubles, '..'); | 806 _growListFront(newParts, leadingDoubles, '..'); |
| 710 } | 807 } |
| 711 | 808 |
| 712 // If we collapsed down to nothing, do ".". | 809 // If we collapsed down to nothing, do ".". |
| 713 if (newParts.length == 0 && !isAbsolute) { | 810 if (newParts.length == 0 && !isAbsolute) { |
| 714 newParts.add('.'); | 811 newParts.add('.'); |
| 715 } | 812 } |
| 716 | 813 |
| 717 // Canonicalize separators. | 814 // Canonicalize separators. |
| 718 var newSeparators = []; | 815 var newSeparators = new List.generate( |
| 719 _growListFront(newSeparators, newParts.length, style.separator); | 816 newParts.length, (_) => style.separator, growable: true); |
| 817 newSeparators.insert(0, | |
| 818 isAbsolute && newParts.length > 0 && | |
| 819 root.contains(style.needsSeparatorPattern) ? | |
| 820 style.separator : ''); | |
| 720 | 821 |
| 721 parts = newParts; | 822 parts = newParts; |
| 722 separators = newSeparators; | 823 separators = newSeparators; |
| 723 | 824 |
| 724 // Normalize the Windows root if needed. | 825 // Normalize the Windows root if needed. |
| 725 if (root != null && style == Style.windows) { | 826 if (root != null && style == Style.windows) { |
| 726 root = root.replaceAll('/', '\\'); | 827 root = root.replaceAll('/', '\\'); |
| 727 } | 828 } |
| 728 removeTrailingSeparators(); | 829 removeTrailingSeparators(); |
| 729 } | 830 } |
| 730 | 831 |
| 731 String toString() { | 832 String toString() { |
| 732 var builder = new StringBuffer(); | 833 var builder = new StringBuffer(); |
| 733 if (root != null) builder.write(root); | 834 if (root != null) builder.write(root); |
| 734 for (var i = 0; i < parts.length; i++) { | 835 for (var i = 0; i < parts.length; i++) { |
| 836 builder.write(separators[i]); | |
| 735 builder.write(parts[i]); | 837 builder.write(parts[i]); |
| 736 builder.write(separators[i]); | |
| 737 } | 838 } |
| 839 builder.write(separators.last); | |
| 738 | 840 |
| 739 return builder.toString(); | 841 return builder.toString(); |
| 740 } | 842 } |
| 741 | 843 |
| 742 /// Splits the last part of the path into a two-element list. The first is | 844 /// 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 | 845 /// the name of the file without any extension. The second is the extension |
| 744 /// or "" if it has none. | 846 /// or "" if it has none. |
| 745 List<String> _splitExtension() { | 847 List<String> _splitExtension() { |
| 746 if (parts.isEmpty) return ['', '']; | 848 if (parts.isEmpty) return ['', '']; |
| 747 | 849 |
| 748 var file = parts.last; | 850 var file = parts.last; |
| 749 if (file == '..') return ['..', '']; | 851 if (file == '..') return ['..', '']; |
| 750 | 852 |
| 751 var lastDot = file.lastIndexOf('.'); | 853 var lastDot = file.lastIndexOf('.'); |
| 752 | 854 |
| 753 // If there is no dot, or it's the first character, like '.bashrc', it | 855 // If there is no dot, or it's the first character, like '.bashrc', it |
| 754 // doesn't count. | 856 // doesn't count. |
| 755 if (lastDot <= 0) return [file, '']; | 857 if (lastDot <= 0) return [file, '']; |
| 756 | 858 |
| 757 return [file.substring(0, lastDot), file.substring(lastDot)]; | 859 return [file.substring(0, lastDot), file.substring(lastDot)]; |
| 758 } | 860 } |
| 759 | 861 |
| 760 _ParsedPath clone() => new _ParsedPath( | 862 _ParsedPath clone() => new _ParsedPath( |
| 761 style, root, new List.from(parts), new List.from(separators)); | 863 style, root, isRootRelative, |
| 864 new List.from(parts), new List.from(separators)); | |
| 762 } | 865 } |
| OLD | NEW |