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

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

Issue 2989763002: Update charted to 0.4.8 and roll (Closed)
Patch Set: Removed Cutch from list of reviewers Created 3 years, 4 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
« no previous file with comments | « packages/path/lib/src/characters.dart ('k') | packages/path/lib/src/internal_style.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) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 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 library path.context; 5 import 'dart:math' as math;
6 6
7 import 'characters.dart' as chars; 7 import 'characters.dart' as chars;
8 import 'internal_style.dart'; 8 import 'internal_style.dart';
9 import 'style.dart'; 9 import 'style.dart';
10 import 'parsed_path.dart'; 10 import 'parsed_path.dart';
11 import 'path_exception.dart'; 11 import 'path_exception.dart';
12 import '../path.dart' as p; 12 import '../path.dart' as p;
13 13
14 Context createInternal() => new Context._internal(); 14 Context createInternal() => new Context._internal();
15 15
(...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after
238 String joinAll(Iterable<String> parts) { 238 String joinAll(Iterable<String> parts) {
239 var buffer = new StringBuffer(); 239 var buffer = new StringBuffer();
240 var needsSeparator = false; 240 var needsSeparator = false;
241 var isAbsoluteAndNotRootRelative = false; 241 var isAbsoluteAndNotRootRelative = false;
242 242
243 for (var part in parts.where((part) => part != '')) { 243 for (var part in parts.where((part) => part != '')) {
244 if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) { 244 if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) {
245 // If the new part is root-relative, it preserves the previous root but 245 // If the new part is root-relative, it preserves the previous root but
246 // replaces the path after it. 246 // replaces the path after it.
247 var parsed = _parse(part); 247 var parsed = _parse(part);
248 parsed.root = this.rootPrefix(buffer.toString()); 248 var path = buffer.toString();
249 parsed.root = path.substring(
250 0, style.rootLength(path, withDrive: true));
249 if (style.needsSeparator(parsed.root)) { 251 if (style.needsSeparator(parsed.root)) {
250 parsed.separators[0] = style.separator; 252 parsed.separators[0] = style.separator;
251 } 253 }
252 buffer.clear(); 254 buffer.clear();
253 buffer.write(parsed.toString()); 255 buffer.write(parsed.toString());
254 } else if (this.isAbsolute(part)) { 256 } else if (this.isAbsolute(part)) {
255 isAbsoluteAndNotRootRelative = !this.isRootRelative(part); 257 isAbsoluteAndNotRootRelative = !this.isRootRelative(part);
256 // An absolute path discards everything before it. 258 // An absolute path discards everything before it.
257 buffer.clear(); 259 buffer.clear();
258 buffer.write(part); 260 buffer.write(part);
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
293 /// // Windows 295 /// // Windows
294 /// context.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo'] 296 /// context.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
295 List<String> split(String path) { 297 List<String> split(String path) {
296 var parsed = _parse(path); 298 var parsed = _parse(path);
297 // Filter out empty parts that exist due to multiple separators in a row. 299 // Filter out empty parts that exist due to multiple separators in a row.
298 parsed.parts = parsed.parts.where((part) => !part.isEmpty).toList(); 300 parsed.parts = parsed.parts.where((part) => !part.isEmpty).toList();
299 if (parsed.root != null) parsed.parts.insert(0, parsed.root); 301 if (parsed.root != null) parsed.parts.insert(0, parsed.root);
300 return parsed.parts; 302 return parsed.parts;
301 } 303 }
302 304
305 /// Canonicalizes [path].
306 ///
307 /// This is guaranteed to return the same path for two different input paths
308 /// if and only if both input paths point to the same location. Unlike
309 /// [normalize], it returns absolute paths when possible and canonicalizes
310 /// ASCII case on Windows.
311 ///
312 /// Note that this does not resolve symlinks.
313 ///
314 /// If you want a map that uses path keys, it's probably more efficient to
315 /// pass [equals] and [hash] to [new HashMap] than it is to canonicalize every
316 /// key.
317 String canonicalize(String path) {
318 path = absolute(path);
319 if (style != Style.windows && !_needsNormalization(path)) return path;
320
321 var parsed = _parse(path);
322 parsed.normalize(canonicalize: true);
323 return parsed.toString();
324 }
325
303 /// Normalizes [path], simplifying it by handling `..`, and `.`, and 326 /// Normalizes [path], simplifying it by handling `..`, and `.`, and
304 /// removing redundant path separators whenever possible. 327 /// removing redundant path separators whenever possible.
305 /// 328 ///
329 /// Note that this is *not* guaranteed to return the same result for two
330 /// equivalent input paths. For that, see [canonicalize]. Or, if you're using
331 /// paths as map keys, pass [equals] and [hash] to [new HashMap].
332 ///
306 /// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt' 333 /// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
307 String normalize(String path) { 334 String normalize(String path) {
308 if (!_needsNormalization(path)) return path; 335 if (!_needsNormalization(path)) return path;
309 336
310 var parsed = _parse(path); 337 var parsed = _parse(path);
311 parsed.normalize(); 338 parsed.normalize();
312 return parsed.toString(); 339 return parsed.toString();
313 } 340 }
314 341
315 /// Returns whether [path] needs to be normalized. 342 /// Returns whether [path] needs to be normalized.
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
363 390
364 // Empty paths are normalized to ".". 391 // Empty paths are normalized to ".".
365 if (previous == null) return true; 392 if (previous == null) return true;
366 393
367 // Trailing separators are removed. 394 // Trailing separators are removed.
368 if (style.isSeparator(previous)) return true; 395 if (style.isSeparator(previous)) return true;
369 396
370 // Single dots and double dots are normalized to directory traversals. 397 // Single dots and double dots are normalized to directory traversals.
371 if (previous == chars.PERIOD && 398 if (previous == chars.PERIOD &&
372 (previousPrevious == null || 399 (previousPrevious == null ||
373 previousPrevious == chars.SLASH || 400 style.isSeparator(previousPrevious) ||
374 previousPrevious == chars.PERIOD)) { 401 previousPrevious == chars.PERIOD)) {
375 return true; 402 return true;
376 } 403 }
377 404
378 return false; 405 return false;
379 } 406 }
380 407
381 /// Attempts to convert [path] to an equivalent relative path relative to 408 /// Attempts to convert [path] to an equivalent relative path relative to
382 /// [root]. 409 /// [root].
383 /// 410 ///
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
439 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') { 466 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') {
440 return pathParsed.toString(); 467 return pathParsed.toString();
441 } 468 }
442 469
443 // If the root prefixes don't match (for example, different drive letters 470 // If the root prefixes don't match (for example, different drive letters
444 // on Windows), then there is no relative path, so just return the absolute 471 // on Windows), then there is no relative path, so just return the absolute
445 // one. In Windows, drive letters are case-insenstive and we allow 472 // one. In Windows, drive letters are case-insenstive and we allow
446 // calculation of relative paths, even if a path has not been normalized. 473 // calculation of relative paths, even if a path has not been normalized.
447 if (fromParsed.root != pathParsed.root && 474 if (fromParsed.root != pathParsed.root &&
448 ((fromParsed.root == null || pathParsed.root == null) || 475 ((fromParsed.root == null || pathParsed.root == null) ||
449 fromParsed.root.toLowerCase().replaceAll('/', '\\') != 476 !style.pathsEqual(fromParsed.root, pathParsed.root))) {
450 pathParsed.root.toLowerCase().replaceAll('/', '\\'))) {
451 return pathParsed.toString(); 477 return pathParsed.toString();
452 } 478 }
453 479
454 // Strip off their common prefix. 480 // Strip off their common prefix.
455 while (fromParsed.parts.length > 0 && 481 while (fromParsed.parts.length > 0 &&
456 pathParsed.parts.length > 0 && 482 pathParsed.parts.length > 0 &&
457 fromParsed.parts[0] == pathParsed.parts[0]) { 483 style.pathsEqual(fromParsed.parts[0], pathParsed.parts[0])) {
458 fromParsed.parts.removeAt(0); 484 fromParsed.parts.removeAt(0);
459 fromParsed.separators.removeAt(1); 485 fromParsed.separators.removeAt(1);
460 pathParsed.parts.removeAt(0); 486 pathParsed.parts.removeAt(0);
461 pathParsed.separators.removeAt(1); 487 pathParsed.separators.removeAt(1);
462 } 488 }
463 489
464 // If there are any directories left in the from path, we need to walk up 490 // If there are any directories left in the from path, we need to walk up
465 // out of them. If a directory left in the from path is '..', it cannot 491 // out of them. If a directory left in the from path is '..', it cannot
466 // be cancelled by adding a '..'. 492 // be cancelled by adding a '..'.
467 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') { 493 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') {
(...skipping 24 matching lines...) Expand all
492 518
493 return pathParsed.toString(); 519 return pathParsed.toString();
494 } 520 }
495 521
496 /// Returns `true` if [child] is a path beneath `parent`, and `false` 522 /// Returns `true` if [child] is a path beneath `parent`, and `false`
497 /// otherwise. 523 /// otherwise.
498 /// 524 ///
499 /// path.isWithin('/root/path', '/root/path/a'); // -> true 525 /// path.isWithin('/root/path', '/root/path/a'); // -> true
500 /// path.isWithin('/root/path', '/root/other'); // -> false 526 /// path.isWithin('/root/path', '/root/other'); // -> false
501 /// path.isWithin('/root/path', '/root/path'); // -> false 527 /// path.isWithin('/root/path', '/root/path'); // -> false
502 bool isWithin(String parent, String child) { 528 bool isWithin(String parent, String child) =>
529 _isWithinOrEquals(parent, child) == _PathRelation.within;
530
531 /// Returns `true` if [path1] points to the same location as [path2], and
532 /// `false` otherwise.
533 ///
534 /// The [hash] function returns a hash code that matches these equality
535 /// semantics.
536 bool equals(String path1, String path2) =>
537 _isWithinOrEquals(path1, path2) == _PathRelation.equal;
538
539 /// Compares two paths and returns an enum value indicating their relationship
540 /// to one another.
541 ///
542 /// This never returns [_PathRelation.inconclusive].
543 _PathRelation _isWithinOrEquals(String parent, String child) {
503 // Make both paths the same level of relative. We're only able to do the 544 // Make both paths the same level of relative. We're only able to do the
504 // quick comparison if both paths are in the same format, and making a path 545 // quick comparison if both paths are in the same format, and making a path
505 // absolute is faster than making it relative. 546 // absolute is faster than making it relative.
506 var parentIsAbsolute = isAbsolute(parent); 547 var parentIsAbsolute = isAbsolute(parent);
507 var childIsAbsolute = isAbsolute(child); 548 var childIsAbsolute = isAbsolute(child);
508 if (parentIsAbsolute && !childIsAbsolute) { 549 if (parentIsAbsolute && !childIsAbsolute) {
509 child = absolute(child); 550 child = absolute(child);
510 if (style.isRootRelative(parent)) parent = absolute(parent); 551 if (style.isRootRelative(parent)) parent = absolute(parent);
511 } else if (childIsAbsolute && !parentIsAbsolute) { 552 } else if (childIsAbsolute && !parentIsAbsolute) {
512 parent = absolute(parent); 553 parent = absolute(parent);
513 if (style.isRootRelative(child)) child = absolute(child); 554 if (style.isRootRelative(child)) child = absolute(child);
514 } else if (childIsAbsolute && parentIsAbsolute) { 555 } else if (childIsAbsolute && parentIsAbsolute) {
515 var childIsRootRelative = style.isRootRelative(child); 556 var childIsRootRelative = style.isRootRelative(child);
516 var parentIsRootRelative = style.isRootRelative(parent); 557 var parentIsRootRelative = style.isRootRelative(parent);
517 558
518 if (childIsRootRelative && !parentIsRootRelative) { 559 if (childIsRootRelative && !parentIsRootRelative) {
519 child = absolute(child); 560 child = absolute(child);
520 } else if (parentIsRootRelative && !childIsRootRelative) { 561 } else if (parentIsRootRelative && !childIsRootRelative) {
521 parent = absolute(parent); 562 parent = absolute(parent);
522 } 563 }
523 } 564 }
524 565
525 var fastResult = _isWithinFast(parent, child); 566 var result = _isWithinOrEqualsFast(parent, child);
526 if (fastResult != null) return fastResult; 567 if (result != _PathRelation.inconclusive) return result;
527 568
528 var relative; 569 var relative;
529 try { 570 try {
530 relative = this.relative(child, from: parent); 571 relative = this.relative(child, from: parent);
531 } on PathException catch (_) { 572 } on PathException catch (_) {
532 // If no relative path from [parent] to [child] is found, [child] 573 // If no relative path from [parent] to [child] is found, [child]
533 // definitely isn't a child of [parent]. 574 // definitely isn't a child of [parent].
534 return false; 575 return _PathRelation.different;
535 } 576 }
536 577
537 var parts = this.split(relative); 578 if (!this.isRelative(relative)) return _PathRelation.different;
538 return this.isRelative(relative) && 579 if (relative == '.') return _PathRelation.equal;
539 parts.first != '..' && 580 if (relative == '..') return _PathRelation.different;
540 parts.first != '.'; 581 return (relative.length >= 3 &&
582 relative.startsWith('..') &&
583 style.isSeparator(relative.codeUnitAt(2)))
584 ? _PathRelation.different
585 : _PathRelation.within;
541 } 586 }
542 587
543 /// An optimized implementation of [isWithin] that doesn't handle a few 588 /// An optimized implementation of [_isWithinOrEquals] that doesn't handle a
544 /// complex cases. 589 /// few complex cases.
545 bool _isWithinFast(String parent, String child) { 590 _PathRelation _isWithinOrEqualsFast(String parent, String child) {
546 // Normally we just bail when we see "." path components, but we can handle 591 // Normally we just bail when we see "." path components, but we can handle
547 // a single dot easily enough. 592 // a single dot easily enough.
548 if (parent == '.') parent = ''; 593 if (parent == '.') parent = '';
549 594
550 var parentRootLength = style.rootLength(parent); 595 var parentRootLength = style.rootLength(parent);
551 var childRootLength = style.rootLength(child); 596 var childRootLength = style.rootLength(child);
552 597
553 // If the roots aren't the same length, we know both paths are absolute or 598 // If the roots aren't the same length, we know both paths are absolute or
554 // both are root-relative, and thus that the roots are meaningfully 599 // both are root-relative, and thus that the roots are meaningfully
555 // different. 600 // different.
556 // 601 //
557 // isWithin("C:/bar", "//foo/bar/baz") //=> false 602 // isWithin("C:/bar", "//foo/bar/baz") //=> false
558 // isWithin("http://example.com/", "http://google.com/bar") //=> false 603 // isWithin("http://example.com/", "http://google.com/bar") //=> false
559 if (parentRootLength != childRootLength) return false; 604 if (parentRootLength != childRootLength) return _PathRelation.different;
560
561 var parentCodeUnits = parent.codeUnits;
562 var childCodeUnits = child.codeUnits;
563 605
564 // Make sure that the roots are textually the same as well. 606 // Make sure that the roots are textually the same as well.
565 // 607 //
566 // isWithin("C:/bar", "D:/bar/baz") //=> false 608 // isWithin("C:/bar", "D:/bar/baz") //=> false
567 // isWithin("http://example.com/", "http://example.org/bar") //=> false 609 // isWithin("http://example.com/", "http://example.org/bar") //=> false
568 for (var i = 0; i < parentRootLength; i++) { 610 for (var i = 0; i < parentRootLength; i++) {
569 var parentCodeUnit = parentCodeUnits[i]; 611 var parentCodeUnit = parent.codeUnitAt(i);
570 var childCodeUnit = childCodeUnits[i]; 612 var childCodeUnit = child.codeUnitAt(i);
571 if (parentCodeUnit == childCodeUnit) continue; 613 if (!style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) {
572 614 return _PathRelation.different;
573 // If both code units are separators, that's fine too.
574 //
575 // isWithin("C:/", r"C:\foo") //=> true
576 if (!style.isSeparator(parentCodeUnit) ||
577 !style.isSeparator(childCodeUnit)) {
578 return false;
579 } 615 }
580 } 616 }
581 617
582 // Start by considering the last code unit as a separator, since 618 // Start by considering the last code unit as a separator, since
583 // semantically we're starting at a new path component even if we're 619 // semantically we're starting at a new path component even if we're
584 // comparing relative paths. 620 // comparing relative paths.
585 var lastCodeUnit = chars.SLASH; 621 var lastCodeUnit = chars.SLASH;
586 622
623 /// The index of the last separator in [parent].
624 int lastParentSeparator;
625
587 // Iterate through both paths as long as they're semantically identical. 626 // Iterate through both paths as long as they're semantically identical.
588 var parentIndex = parentRootLength; 627 var parentIndex = parentRootLength;
589 var childIndex = childRootLength; 628 var childIndex = childRootLength;
590 while (parentIndex < parent.length && childIndex < child.length) { 629 while (parentIndex < parent.length && childIndex < child.length) {
591 var parentCodeUnit = parentCodeUnits[parentIndex]; 630 var parentCodeUnit = parent.codeUnitAt(parentIndex);
592 var childCodeUnit = childCodeUnits[childIndex]; 631 var childCodeUnit = child.codeUnitAt(childIndex);
593 if (parentCodeUnit == childCodeUnit) { 632 if (style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) {
633 if (style.isSeparator(parentCodeUnit)) {
634 lastParentSeparator = parentIndex;
635 }
636
594 lastCodeUnit = parentCodeUnit; 637 lastCodeUnit = parentCodeUnit;
595 parentIndex++; 638 parentIndex++;
596 childIndex++; 639 childIndex++;
597 continue;
598 }
599
600 // Different separators are considered identical.
601 var parentIsSeparator = style.isSeparator(parentCodeUnit);
602 var childIsSeparator = style.isSeparator(childCodeUnit);
603 if (parentIsSeparator && childIsSeparator) {
604 lastCodeUnit = parentCodeUnit;
605 parentIndex++;
606 childIndex++;
607 continue; 640 continue;
608 } 641 }
609 642
610 // Ignore multiple separators in a row. 643 // Ignore multiple separators in a row.
611 if (parentIsSeparator && style.isSeparator(lastCodeUnit)) { 644 if (style.isSeparator(parentCodeUnit) &&
645 style.isSeparator(lastCodeUnit)) {
646 lastParentSeparator = parentIndex;
612 parentIndex++; 647 parentIndex++;
613 continue; 648 continue;
614 } else if (childIsSeparator && style.isSeparator(lastCodeUnit)) { 649 } else if (style.isSeparator(childCodeUnit) &&
650 style.isSeparator(lastCodeUnit)) {
615 childIndex++; 651 childIndex++;
616 continue; 652 continue;
617 } 653 }
618 654
619 if (parentCodeUnit == chars.PERIOD) { 655 // If a dot comes after a separator, it may be a directory traversal
620 // If a dot comes after a separator, it may be a directory traversal 656 // operator. To check that, we need to know if it's followed by either
621 // operator. To check that, we need to know if it's followed by either 657 // "/" or "./". Otherwise, it's just a normal non-matching character.
622 // "/" or "./". Otherwise, it's just a normal non-matching character. 658 //
623 // 659 // isWithin("foo/./bar", "foo/bar/baz") //=> true
624 // isWithin("foo/./bar", "foo/bar/baz") //=> true 660 // isWithin("foo/bar/../baz", "foo/bar/.foo") //=> false
625 // isWithin("foo/bar/../baz", "foo/bar/.foo") //=> false 661 if (parentCodeUnit == chars.PERIOD && style.isSeparator(lastCodeUnit)) {
626 if (style.isSeparator(lastCodeUnit)) { 662 parentIndex++;
663
664 // We've hit "/." at the end of the parent path, which we can ignore,
665 // since the paths were equivalent up to this point.
666 if (parentIndex == parent.length) break;
667 parentCodeUnit = parent.codeUnitAt(parentIndex);
668
669 // We've hit "/./", which we can ignore.
670 if (style.isSeparator(parentCodeUnit)) {
671 lastParentSeparator = parentIndex;
627 parentIndex++; 672 parentIndex++;
673 continue;
674 }
628 675
629 // We've hit "/." at the end of the parent path, which we can ignore, 676 // We've hit "/..", which may be a directory traversal operator that
630 // since the paths were equivalent up to this point. 677 // we can't handle on the fast track.
631 if (parentIndex == parent.length) break; 678 if (parentCodeUnit == chars.PERIOD) {
632 parentCodeUnit = parentCodeUnits[parentIndex]; 679 parentIndex++;
633 680 if (parentIndex == parent.length ||
634 // We've hit "/./", which we can ignore. 681 style.isSeparator(parent.codeUnitAt(parentIndex))) {
635 if (style.isSeparator(parentCodeUnit)) { 682 return _PathRelation.inconclusive;
636 parentIndex++;
637 continue;
638 }
639
640 // We've hit "/..", which may be a directory traversal operator that
641 // we can't handle on the fast track.
642 if (parentCodeUnit == chars.PERIOD) {
643 parentIndex++;
644 if (parentIndex == parent.length ||
645 style.isSeparator(parentCodeUnits[parentIndex])) {
646 return null;
647 }
648 } 683 }
649 } 684 }
650 685
651 // If this isn't a directory traversal, fall through so we hit the 686 // If this isn't a directory traversal, fall through so we hit the
652 // normal handling for mismatched paths. 687 // normal handling for mismatched paths.
653 } 688 }
654 689
655 // This is the same logic as above, but for the child path instead of the 690 // This is the same logic as above, but for the child path instead of the
656 // parent. 691 // parent.
657 if (childCodeUnit == chars.PERIOD) { 692 if (childCodeUnit == chars.PERIOD && style.isSeparator(lastCodeUnit)) {
658 if (style.isSeparator(lastCodeUnit)) { 693 childIndex++;
694 if (childIndex == child.length) break;
695 childCodeUnit = child.codeUnitAt(childIndex);
696
697 if (style.isSeparator(childCodeUnit)) {
659 childIndex++; 698 childIndex++;
660 if (childIndex == child.length) break; 699 continue;
661 childCodeUnit = childCodeUnits[childIndex]; 700 }
662 701
663 if (style.isSeparator(childCodeUnit)) { 702 if (childCodeUnit == chars.PERIOD) {
664 childIndex++; 703 childIndex++;
665 continue; 704 if (childIndex == child.length ||
666 } 705 style.isSeparator(child.codeUnitAt(childIndex))) {
667 706 return _PathRelation.inconclusive;
668 if (childCodeUnit == chars.PERIOD) {
669 childIndex++;
670 if (childIndex == child.length ||
671 style.isSeparator(childCodeUnits[childIndex])) {
672 return null;
673 }
674 } 707 }
675 } 708 }
676 } 709 }
677 710
678 // If we're here, we've hit two non-matching, non-significant characters. 711 // If we're here, we've hit two non-matching, non-significant characters.
679 // As long as the remainders of the two paths don't have any unresolved 712 // As long as the remainders of the two paths don't have any unresolved
680 // ".." components, we can be confident that [child] is not within 713 // ".." components, we can be confident that [child] is not within
681 // [parent]. 714 // [parent].
682 var childDirection = _pathDirection(childCodeUnits, childIndex); 715 var childDirection = _pathDirection(child, childIndex);
683 if (childDirection != _PathDirection.belowRoot) return null; 716 if (childDirection != _PathDirection.belowRoot) {
684 var parentDirection = _pathDirection(parentCodeUnits, parentIndex); 717 return _PathRelation.inconclusive;
685 if (parentDirection != _PathDirection.belowRoot) return null; 718 }
686 719
687 return false; 720 var parentDirection = _pathDirection(parent, parentIndex);
721 if (parentDirection != _PathDirection.belowRoot) {
722 return _PathRelation.inconclusive;
723 }
724
725 return _PathRelation.different;
688 } 726 }
689 727
690 // If the child is shorter than the parent, it's probably not within the 728 // If the child is shorter than the parent, it's probably not within the
691 // parent. The only exception is if the parent has some weird ".." stuff 729 // parent. The only exception is if the parent has some weird ".." stuff
692 // going on, in which case we do the slow check. 730 // going on, in which case we do the slow check.
693 // 731 //
694 // isWithin("foo/bar/baz", "foo/bar") //=> false 732 // isWithin("foo/bar/baz", "foo/bar") //=> false
695 // isWithin("foo/bar/baz/../..", "foo/bar") //=> true 733 // isWithin("foo/bar/baz/../..", "foo/bar") //=> true
696 if (childIndex == child.length) { 734 if (childIndex == child.length) {
697 var direction = _pathDirection(parentCodeUnits, parentIndex); 735 if (parentIndex == parent.length ||
698 return direction == _PathDirection.aboveRoot ? null : false; 736 style.isSeparator(parent.codeUnitAt(parentIndex))) {
737 lastParentSeparator = parentIndex;
738 } else {
739 lastParentSeparator ??= math.max(0, parentRootLength - 1);
740 }
741
742 var direction = _pathDirection(parent,
743 lastParentSeparator ?? parentRootLength - 1);
744 if (direction == _PathDirection.atRoot) return _PathRelation.equal;
745 return direction == _PathDirection.aboveRoot
746 ? _PathRelation.inconclusive
747 : _PathRelation.different;
699 } 748 }
700 749
701 // We've reached the end of the parent path, which means it's time to make a 750 // We've reached the end of the parent path, which means it's time to make a
702 // decision. Before we do, though, we'll check the rest of the child to see 751 // decision. Before we do, though, we'll check the rest of the child to see
703 // what that tells us. 752 // what that tells us.
704 var direction = _pathDirection(childCodeUnits, childIndex); 753 var direction = _pathDirection(child, childIndex);
705 754
706 // If there are no more components in the child, then it's the same as 755 // If there are no more components in the child, then it's the same as
707 // the parent, not within it. 756 // the parent.
708 // 757 //
709 // isWithin("foo/bar", "foo/bar") //=> false 758 // isWithin("foo/bar", "foo/bar") //=> false
710 // isWithin("foo/bar", "foo/bar//") //=> false 759 // isWithin("foo/bar", "foo/bar//") //=> false
711 if (direction == _PathDirection.atRoot) return false; 760 // equals("foo/bar", "foo/bar") //=> true
761 // equals("foo/bar", "foo/bar//") //=> true
762 if (direction == _PathDirection.atRoot) return _PathRelation.equal;
712 763
713 // If there are unresolved ".." components in the child, no decision we make 764 // If there are unresolved ".." components in the child, no decision we make
714 // will be valid. We'll abort and do the slow check instead. 765 // will be valid. We'll abort and do the slow check instead.
715 // 766 //
716 // isWithin("foo/bar", "foo/bar/..") //=> false 767 // isWithin("foo/bar", "foo/bar/..") //=> false
717 // isWithin("foo/bar", "foo/bar/baz/bang/../../..") //=> false 768 // isWithin("foo/bar", "foo/bar/baz/bang/../../..") //=> false
718 // isWithin("foo/bar", "foo/bar/baz/bang/../../../bar/baz") //=> true 769 // isWithin("foo/bar", "foo/bar/baz/bang/../../../bar/baz") //=> true
719 if (direction == _PathDirection.aboveRoot) return null; 770 if (direction == _PathDirection.aboveRoot) {
771 return _PathRelation.inconclusive;
772 }
720 773
721 // The child is within the parent if and only if we're on a separator 774 // The child is within the parent if and only if we're on a separator
722 // boundary. 775 // boundary.
723 // 776 //
724 // isWithin("foo/bar", "foo/bar/baz") //=> true 777 // isWithin("foo/bar", "foo/bar/baz") //=> true
725 // isWithin("foo/bar/", "foo/bar/baz") //=> true 778 // isWithin("foo/bar/", "foo/bar/baz") //=> true
726 // isWithin("foo/bar", "foo/barbaz") //=> false 779 // isWithin("foo/bar", "foo/barbaz") //=> false
727 return style.isSeparator(childCodeUnits[childIndex]) || 780 return (style.isSeparator(child.codeUnitAt(childIndex)) ||
728 style.isSeparator(lastCodeUnit); 781 style.isSeparator(lastCodeUnit))
782 ? _PathRelation.within
783 : _PathRelation.different;
729 } 784 }
730 785
731 // Returns a [_PathDirection] describing the path represented by [codeUnits] 786 // Returns a [_PathDirection] describing the path represented by [codeUnits]
732 // after [index]. 787 // starting at [index].
733 // 788 //
734 // This ignores leading separators. 789 // This ignores leading separators.
735 // 790 //
736 // pathDirection("foo") //=> below root 791 // pathDirection("foo") //=> below root
737 // pathDirection("foo/bar/../baz") //=> below root 792 // pathDirection("foo/bar/../baz") //=> below root
738 // pathDirection("//foo/bar/baz") //=> below root 793 // pathDirection("//foo/bar/baz") //=> below root
739 // pathDirection("/") //=> at root 794 // pathDirection("/") //=> at root
740 // pathDirection("foo/..") //=> at root 795 // pathDirection("foo/..") //=> at root
741 // pathDirection("foo/../baz") //=> reaches root 796 // pathDirection("foo/../baz") //=> reaches root
742 // pathDirection("foo/../..") //=> above root 797 // pathDirection("foo/../..") //=> above root
743 // pathDirection("foo/../../foo/bar/baz") //=> above root 798 // pathDirection("foo/../../foo/bar/baz") //=> above root
744 _PathDirection _pathDirection(List<int> codeUnits, int index) { 799 _PathDirection _pathDirection(String path, int index) {
745 var depth = 0; 800 var depth = 0;
746 var reachedRoot = false; 801 var reachedRoot = false;
747 var i = index; 802 var i = index;
748 while (i < codeUnits.length) { 803 while (i < path.length) {
749 // Ignore initial separators or doubled separators. 804 // Ignore initial separators or doubled separators.
750 while (i < codeUnits.length && style.isSeparator(codeUnits[i])) { 805 while (i < path.length && style.isSeparator(path.codeUnitAt(i))) {
751 i++; 806 i++;
752 } 807 }
753 808
754 // If we're at the end, stop. 809 // If we're at the end, stop.
755 if (i == codeUnits.length) break; 810 if (i == path.length) break;
756 811
757 // Move through the path component to the next separator. 812 // Move through the path component to the next separator.
758 var start = i; 813 var start = i;
759 while (i < codeUnits.length && !style.isSeparator(codeUnits[i])) { 814 while (i < path.length && !style.isSeparator(path.codeUnitAt(i))) {
760 i++; 815 i++;
761 } 816 }
762 817
763 // See if the path component is ".", "..", or a name. 818 // See if the path component is ".", "..", or a name.
764 if (i - start == 1 && codeUnits[start] == chars.PERIOD) { 819 if (i - start == 1 && path.codeUnitAt(start) == chars.PERIOD) {
765 // Don't change the depth. 820 // Don't change the depth.
766 } else if (i - start == 2 && 821 } else if (i - start == 2 &&
767 codeUnits[start] == chars.PERIOD && 822 path.codeUnitAt(start) == chars.PERIOD &&
768 codeUnits[start + 1] == chars.PERIOD) { 823 path.codeUnitAt(start + 1) == chars.PERIOD) {
769 // ".." backs out a directory. 824 // ".." backs out a directory.
770 depth--; 825 depth--;
771 826
772 // If we work back beyond the root, stop. 827 // If we work back beyond the root, stop.
773 if (depth < 0) break; 828 if (depth < 0) break;
774 829
775 // Record that we reached the root so we don't return 830 // Record that we reached the root so we don't return
776 // [_PathDirection.belowRoot]. 831 // [_PathDirection.belowRoot].
777 if (depth == 0) reachedRoot = true; 832 if (depth == 0) reachedRoot = true;
778 } else { 833 } else {
779 // Step inside a directory. 834 // Step inside a directory.
780 depth++; 835 depth++;
781 } 836 }
782 837
783 // If we're at the end, stop. 838 // If we're at the end, stop.
784 if (i == codeUnits.length) break; 839 if (i == path.length) break;
785 840
786 // Move past the separator. 841 // Move past the separator.
787 i++; 842 i++;
788 } 843 }
789 844
790 if (depth < 0) return _PathDirection.aboveRoot; 845 if (depth < 0) return _PathDirection.aboveRoot;
791 if (depth == 0) return _PathDirection.atRoot; 846 if (depth == 0) return _PathDirection.atRoot;
792 if (reachedRoot) return _PathDirection.reachesRoot; 847 if (reachedRoot) return _PathDirection.reachesRoot;
793 return _PathDirection.belowRoot; 848 return _PathDirection.belowRoot;
794 } 849 }
795 850
851 /// Returns a hash code for [path] that matches the semantics of [equals].
852 ///
853 /// Note that the same path may have different hash codes in different
854 /// [Context]s.
855 int hash(String path) {
856 // Make [path] absolute to ensure that equivalent relative and absolute
857 // paths have the same hash code.
858 path = absolute(path);
859
860 var result = _hashFast(path);
861 if (result != null) return result;
862
863 var parsed = _parse(path);
864 parsed.normalize();
865 return _hashFast(parsed.toString());
866 }
867
868 /// An optimized implementation of [hash] that doesn't handle internal `..`
869 /// components.
870 ///
871 /// This will handle `..` components that appear at the beginning of the path.
872 int _hashFast(String path) {
873 var hash = 4603;
874 var beginning = true;
875 var wasSeparator = true;
876 for (var i = 0; i < path.length; i++) {
877 var codeUnit = style.canonicalizeCodeUnit(path.codeUnitAt(i));
878
879 // Take advantage of the fact that collisions are allowed to ignore
880 // separators entirely. This lets us avoid worrying about cases like
881 // multiple trailing slashes.
882 if (style.isSeparator(codeUnit)) {
883 wasSeparator = true;
884 continue;
885 }
886
887 if (codeUnit == chars.PERIOD && wasSeparator) {
888 // If a dot comes after a separator, it may be a directory traversal
889 // operator. To check that, we need to know if it's followed by either
890 // "/" or "./". Otherwise, it's just a normal character.
891 //
892 // hash("foo/./bar") == hash("foo/bar")
893
894 // We've hit "/." at the end of the path, which we can ignore.
895 if (i + 1 == path.length) break;
896
897 var next = path.codeUnitAt(i + 1);
898
899 // We can just ignore "/./", since they don't affect the semantics of
900 // the path.
901 if (style.isSeparator(next)) continue;
902
903 // If the path ends with "/.." or contains "/../", we need to
904 // canonicalize it before we can hash it. We make an exception for ".."s
905 // at the beginning of the path, since those may appear even in a
906 // canonicalized path.
907 if (!beginning &&
908 next == chars.PERIOD &&
909 (i + 2 == path.length ||
910 style.isSeparator(path.codeUnitAt(i + 2)))) {
911 return null;
912 }
913 }
914
915 // Make sure [hash] stays under 32 bits even after multiplication.
916 hash &= 0x3FFFFFF;
917 hash *= 33;
918 hash ^= codeUnit;
919 wasSeparator = false;
920 beginning = false;
921 }
922 return hash;
923 }
924
796 /// Removes a trailing extension from the last part of [path]. 925 /// Removes a trailing extension from the last part of [path].
797 /// 926 ///
798 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' 927 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
799 String withoutExtension(String path) { 928 String withoutExtension(String path) {
800 var parsed = _parse(path); 929 var parsed = _parse(path);
801 930
802 for (var i = parsed.parts.length - 1; i >= 0; i--) { 931 for (var i = parsed.parts.length - 1; i >= 0; i--) {
803 if (!parsed.parts[i].isEmpty) { 932 if (!parsed.parts[i].isEmpty) {
804 parsed.parts[i] = parsed.basenameWithoutExtension; 933 parsed.parts[i] = parsed.basenameWithoutExtension;
805 break; 934 break;
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after
945 1074
946 /// The path never reaches to or above its original root. 1075 /// The path never reaches to or above its original root.
947 static const belowRoot = const _PathDirection("below root"); 1076 static const belowRoot = const _PathDirection("below root");
948 1077
949 final String name; 1078 final String name;
950 1079
951 const _PathDirection(this.name); 1080 const _PathDirection(this.name);
952 1081
953 String toString() => name; 1082 String toString() => name;
954 } 1083 }
1084
1085 /// An enum of possible return values for [Context._isWithinOrEquals].
1086 class _PathRelation {
1087 /// The first path is a proper parent of the second.
1088 ///
1089 /// For example, `foo` is a proper parent of `foo/bar`, but not of `foo`.
1090 static const within = const _PathRelation("within");
1091
1092 /// The two paths are equivalent.
1093 ///
1094 /// For example, `foo//bar` is equivalent to `foo/bar`.
1095 static const equal = const _PathRelation("equal");
1096
1097 /// The first path is neither a parent of nor equal to the second.
1098 static const different = const _PathRelation("different");
1099
1100 /// We couldn't quickly determine any information about the paths'
1101 /// relationship to each other.
1102 ///
1103 /// Only returned by [Context._isWithinOrEqualsFast].
1104 static const inconclusive = const _PathRelation("inconclusive");
1105
1106 final String name;
1107
1108 const _PathRelation(this.name);
1109
1110 String toString() => name;
1111 }
1112
OLDNEW
« no previous file with comments | « packages/path/lib/src/characters.dart ('k') | packages/path/lib/src/internal_style.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698