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

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

Issue 1521693002: Roll Observatory deps (charted -> ^0.3.0) (Closed) Base URL: https://chromium.googlesource.com/external/github.com/dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years 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/path.dart ('k') | packages/path/pubspec.yaml » ('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 library path.context;
6 6
7 import 'characters.dart' as chars;
7 import 'internal_style.dart'; 8 import 'internal_style.dart';
8 import 'style.dart'; 9 import 'style.dart';
9 import 'parsed_path.dart'; 10 import 'parsed_path.dart';
10 import 'path_exception.dart'; 11 import 'path_exception.dart';
11 import '../path.dart' as p; 12 import '../path.dart' as p;
12 13
13 Context createInternal() => new Context._internal(); 14 Context createInternal() => new Context._internal();
14 15
15 /// An instantiable class for manipulating paths. Unlike the top-level 16 /// An instantiable class for manipulating paths. Unlike the top-level
16 /// functions, this lets you explicitly select what platform the paths will use. 17 /// functions, this lets you explicitly select what platform the paths will use.
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
66 67
67 /// Creates a new path by appending the given path parts to [current]. 68 /// Creates a new path by appending the given path parts to [current].
68 /// Equivalent to [join()] with [current] as the first argument. Example: 69 /// Equivalent to [join()] with [current] as the first argument. Example:
69 /// 70 ///
70 /// var context = new Context(current: '/root'); 71 /// var context = new Context(current: '/root');
71 /// context.absolute('path', 'to', 'foo'); // -> '/root/path/to/foo' 72 /// context.absolute('path', 'to', 'foo'); // -> '/root/path/to/foo'
72 /// 73 ///
73 /// If [current] isn't absolute, this won't return an absolute path. 74 /// If [current] isn't absolute, this won't return an absolute path.
74 String absolute(String part1, [String part2, String part3, String part4, 75 String absolute(String part1, [String part2, String part3, String part4,
75 String part5, String part6, String part7]) { 76 String part5, String part6, String part7]) {
77 _validateArgList(
78 "absolute", [part1, part2, part3, part4, part5, part6, part7]);
79
80 // If there's a single absolute path, just return it. This is a lot faster
81 // for the common case of `p.absolute(path)`.
82 if (part2 == null && isAbsolute(part1) && !isRootRelative(part1)) {
83 return part1;
84 }
85
76 return join(current, part1, part2, part3, part4, part5, part6, part7); 86 return join(current, part1, part2, part3, part4, part5, part6, part7);
77 } 87 }
78 88
79 /// Gets the part of [path] after the last separator on the context's 89 /// Gets the part of [path] after the last separator on the context's
80 /// platform. 90 /// platform.
81 /// 91 ///
82 /// context.basename('path/to/foo.dart'); // -> 'foo.dart' 92 /// context.basename('path/to/foo.dart'); // -> 'foo.dart'
83 /// context.basename('path/to'); // -> 'to' 93 /// context.basename('path/to'); // -> 'to'
84 /// 94 ///
85 /// Trailing separators are ignored. 95 /// Trailing separators are ignored.
(...skipping 202 matching lines...) Expand 10 before | Expand all | Expand 10 after
288 parsed.parts = parsed.parts.where((part) => !part.isEmpty).toList(); 298 parsed.parts = parsed.parts.where((part) => !part.isEmpty).toList();
289 if (parsed.root != null) parsed.parts.insert(0, parsed.root); 299 if (parsed.root != null) parsed.parts.insert(0, parsed.root);
290 return parsed.parts; 300 return parsed.parts;
291 } 301 }
292 302
293 /// Normalizes [path], simplifying it by handling `..`, and `.`, and 303 /// Normalizes [path], simplifying it by handling `..`, and `.`, and
294 /// removing redundant path separators whenever possible. 304 /// removing redundant path separators whenever possible.
295 /// 305 ///
296 /// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt' 306 /// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
297 String normalize(String path) { 307 String normalize(String path) {
308 if (!_needsNormalization(path)) return path;
309
298 var parsed = _parse(path); 310 var parsed = _parse(path);
299 parsed.normalize(); 311 parsed.normalize();
300 return parsed.toString(); 312 return parsed.toString();
301 } 313 }
302 314
315 /// Returns whether [path] needs to be normalized.
316 bool _needsNormalization(String path) {
317 var start = 0;
318 var codeUnits = path.codeUnits;
319 var previousPrevious;
320 var previous;
321
322 // Skip past the root before we start looking for snippets that need
323 // normalization. We want to normalize "//", but not when it's part of
324 // "http://".
325 var root = style.rootLength(path);
326 if (root != 0) {
327 start = root;
328 previous = chars.SLASH;
329
330 // On Windows, the root still needs to be normalized if it contains a
331 // forward slash.
332 if (style == Style.windows) {
333 for (var i = 0; i < root; i++) {
334 if (codeUnits[i] == chars.SLASH) return true;
335 }
336 }
337 }
338
339 for (var i = start; i < codeUnits.length; i++) {
340 var codeUnit = codeUnits[i];
341 if (style.isSeparator(codeUnit)) {
342 // Forward slashes in Windows paths are normalized to backslashes.
343 if (style == Style.windows && codeUnit == chars.SLASH) return true;
344
345 // Multiple separators are normalized to single separators.
346 if (previous != null && style.isSeparator(previous)) return true;
347
348 // Single dots and double dots are normalized to directory traversals.
349 //
350 // This can return false positives for ".../", but that's unlikely
351 // enough that it's probably not going to cause performance issues.
352 if (previous == chars.PERIOD &&
353 (previousPrevious == null ||
354 previousPrevious == chars.PERIOD ||
355 style.isSeparator(previousPrevious))) {
356 return true;
357 }
358 }
359
360 previousPrevious = previous;
361 previous = codeUnit;
362 }
363
364 // Empty paths are normalized to ".".
365 if (previous == null) return true;
366
367 // Trailing separators are removed.
368 if (style.isSeparator(previous)) return true;
369
370 // Single dots and double dots are normalized to directory traversals.
371 if (previous == chars.PERIOD &&
372 (previousPrevious == null ||
373 previousPrevious == chars.SLASH ||
374 previousPrevious == chars.PERIOD)) {
375 return true;
376 }
377
378 return false;
379 }
380
303 /// Attempts to convert [path] to an equivalent relative path relative to 381 /// Attempts to convert [path] to an equivalent relative path relative to
304 /// [root]. 382 /// [root].
305 /// 383 ///
306 /// var context = new Context(current: '/root/path'); 384 /// var context = new Context(current: '/root/path');
307 /// context.relative('/root/path/a/b.dart'); // -> 'a/b.dart' 385 /// context.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
308 /// context.relative('/root/other.dart'); // -> '../other.dart' 386 /// context.relative('/root/other.dart'); // -> '../other.dart'
309 /// 387 ///
310 /// If the [from] argument is passed, [path] is made relative to that instead. 388 /// If the [from] argument is passed, [path] is made relative to that instead.
311 /// 389 ///
312 /// context.relative('/root/path/a/b.dart', 390 /// context.relative('/root/path/a/b.dart',
(...skipping 13 matching lines...) Expand all
326 /// a context with a relative path for [current]. 404 /// a context with a relative path for [current].
327 /// 405 ///
328 /// var context = new Context(r'some/relative/path'); 406 /// var context = new Context(r'some/relative/path');
329 /// context.relative(r'/absolute/path'); // -> '/absolute/path' 407 /// context.relative(r'/absolute/path'); // -> '/absolute/path'
330 /// 408 ///
331 /// If [root] is relative, it may be impossible to determine a path from 409 /// If [root] is relative, it may be impossible to determine a path from
332 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is 410 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is
333 /// "/", no path can be determined. In this case, a [PathException] will be 411 /// "/", no path can be determined. In this case, a [PathException] will be
334 /// thrown. 412 /// thrown.
335 String relative(String path, {String from}) { 413 String relative(String path, {String from}) {
336 // Avoid calling [current] since it is slow and calling join() when 414 // Avoid expensive computation if the path is already relative.
337 // [from] is absolute does nothing. 415 if (from == null && this.isRelative(path)) return this.normalize(path);
338 if (from == null) { 416
339 from = current; 417 from = from == null ? current : absolute(from);
340 } else if (this.isRelative(from) || this.isRootRelative(from)) {
341 from = this.join(current, from);
342 }
343 418
344 // We can't determine the path from a relative path to an absolute path. 419 // We can't determine the path from a relative path to an absolute path.
345 if (this.isRelative(from) && this.isAbsolute(path)) { 420 if (this.isRelative(from) && this.isAbsolute(path)) {
346 return this.normalize(path); 421 return this.normalize(path);
347 } 422 }
348 423
349 // If the given path is relative, resolve it relative to the context's 424 // If the given path is relative, resolve it relative to the context's
350 // current directory. 425 // current directory.
351 if (this.isRelative(path) || this.isRootRelative(path)) { 426 if (this.isRelative(path) || this.isRootRelative(path)) {
352 path = this.absolute(path); 427 path = this.absolute(path);
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
418 return pathParsed.toString(); 493 return pathParsed.toString();
419 } 494 }
420 495
421 /// Returns `true` if [child] is a path beneath `parent`, and `false` 496 /// Returns `true` if [child] is a path beneath `parent`, and `false`
422 /// otherwise. 497 /// otherwise.
423 /// 498 ///
424 /// path.isWithin('/root/path', '/root/path/a'); // -> true 499 /// path.isWithin('/root/path', '/root/path/a'); // -> true
425 /// path.isWithin('/root/path', '/root/other'); // -> false 500 /// path.isWithin('/root/path', '/root/other'); // -> false
426 /// path.isWithin('/root/path', '/root/path'); // -> false 501 /// path.isWithin('/root/path', '/root/path'); // -> false
427 bool isWithin(String parent, String child) { 502 bool isWithin(String parent, String child) {
503 // 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
505 // absolute is faster than making it relative.
506 var parentIsAbsolute = isAbsolute(parent);
507 var childIsAbsolute = isAbsolute(child);
508 if (parentIsAbsolute && !childIsAbsolute) {
509 child = absolute(child);
510 if (style.isRootRelative(parent)) parent = absolute(parent);
511 } else if (childIsAbsolute && !parentIsAbsolute) {
512 parent = absolute(parent);
513 if (style.isRootRelative(child)) child = absolute(child);
514 } else if (childIsAbsolute && parentIsAbsolute) {
515 var childIsRootRelative = style.isRootRelative(child);
516 var parentIsRootRelative = style.isRootRelative(parent);
517
518 if (childIsRootRelative && !parentIsRootRelative) {
519 child = absolute(child);
520 } else if (parentIsRootRelative && !childIsRootRelative) {
521 parent = absolute(parent);
522 }
523 }
524
525 var fastResult = _isWithinFast(parent, child);
526 if (fastResult != null) return fastResult;
527
428 var relative; 528 var relative;
429 try { 529 try {
430 relative = this.relative(child, from: parent); 530 relative = this.relative(child, from: parent);
431 } on PathException catch (_) { 531 } on PathException catch (_) {
432 // If no relative path from [parent] to [child] is found, [child] 532 // If no relative path from [parent] to [child] is found, [child]
433 // definitely isn't a child of [parent]. 533 // definitely isn't a child of [parent].
434 return false; 534 return false;
435 } 535 }
436 536
437 var parts = this.split(relative); 537 var parts = this.split(relative);
438 return this.isRelative(relative) && 538 return this.isRelative(relative) &&
439 parts.first != '..' && 539 parts.first != '..' &&
440 parts.first != '.'; 540 parts.first != '.';
441 } 541 }
442 542
543 /// An optimized implementation of [isWithin] that doesn't handle a few
544 /// complex cases.
545 bool _isWithinFast(String parent, String child) {
546 // Normally we just bail when we see "." path components, but we can handle
547 // a single dot easily enough.
548 if (parent == '.') parent = '';
549
550 var parentRootLength = style.rootLength(parent);
551 var childRootLength = style.rootLength(child);
552
553 // 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
555 // different.
556 //
557 // isWithin("C:/bar", "//foo/bar/baz") //=> false
558 // isWithin("http://example.com/", "http://google.com/bar") //=> false
559 if (parentRootLength != childRootLength) return false;
560
561 var parentCodeUnits = parent.codeUnits;
562 var childCodeUnits = child.codeUnits;
563
564 // Make sure that the roots are textually the same as well.
565 //
566 // isWithin("C:/bar", "D:/bar/baz") //=> false
567 // isWithin("http://example.com/", "http://example.org/bar") //=> false
568 for (var i = 0; i < parentRootLength; i++) {
569 var parentCodeUnit = parentCodeUnits[i];
570 var childCodeUnit = childCodeUnits[i];
571 if (parentCodeUnit == childCodeUnit) continue;
572
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 }
580 }
581
582 // 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
584 // comparing relative paths.
585 var lastCodeUnit = chars.SLASH;
586
587 // Iterate through both paths as long as they're semantically identical.
588 var parentIndex = parentRootLength;
589 var childIndex = childRootLength;
590 while (parentIndex < parent.length && childIndex < child.length) {
591 var parentCodeUnit = parentCodeUnits[parentIndex];
592 var childCodeUnit = childCodeUnits[childIndex];
593 if (parentCodeUnit == childCodeUnit) {
594 lastCodeUnit = parentCodeUnit;
595 parentIndex++;
596 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;
608 }
609
610 // Ignore multiple separators in a row.
611 if (parentIsSeparator && style.isSeparator(lastCodeUnit)) {
612 parentIndex++;
613 continue;
614 } else if (childIsSeparator && style.isSeparator(lastCodeUnit)) {
615 childIndex++;
616 continue;
617 }
618
619 if (parentCodeUnit == chars.PERIOD) {
620 // If a dot comes after a separator, it may be a directory traversal
621 // operator. To check that, we need to know if it's followed by either
622 // "/" or "./". Otherwise, it's just a normal non-matching character.
623 //
624 // isWithin("foo/./bar", "foo/bar/baz") //=> true
625 // isWithin("foo/bar/../baz", "foo/bar/.foo") //=> false
626 if (style.isSeparator(lastCodeUnit)) {
627 parentIndex++;
628
629 // We've hit "/." at the end of the parent path, which we can ignore,
630 // since the paths were equivalent up to this point.
631 if (parentIndex == parent.length) break;
632 parentCodeUnit = parentCodeUnits[parentIndex];
633
634 // We've hit "/./", which we can ignore.
635 if (style.isSeparator(parentCodeUnit)) {
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 }
649 }
650
651 // If this isn't a directory traversal, fall through so we hit the
652 // normal handling for mismatched paths.
653 }
654
655 // This is the same logic as above, but for the child path instead of the
656 // parent.
657 if (childCodeUnit == chars.PERIOD) {
658 if (style.isSeparator(lastCodeUnit)) {
659 childIndex++;
660 if (childIndex == child.length) break;
661 childCodeUnit = childCodeUnits[childIndex];
662
663 if (style.isSeparator(childCodeUnit)) {
664 childIndex++;
665 continue;
666 }
667
668 if (childCodeUnit == chars.PERIOD) {
669 childIndex++;
670 if (childIndex == child.length ||
671 style.isSeparator(childCodeUnits[childIndex])) {
672 return null;
673 }
674 }
675 }
676 }
677
678 // 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
680 // ".." components, we can be confident that [child] is not within
681 // [parent].
682 var childDirection = _pathDirection(childCodeUnits, childIndex);
683 if (childDirection != _PathDirection.belowRoot) return null;
684 var parentDirection = _pathDirection(parentCodeUnits, parentIndex);
685 if (parentDirection != _PathDirection.belowRoot) return null;
686
687 return false;
688 }
689
690 // 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
692 // going on, in which case we do the slow check.
693 //
694 // isWithin("foo/bar/baz", "foo/bar") //=> false
695 // isWithin("foo/bar/baz/../..", "foo/bar") //=> true
696 if (childIndex == child.length) {
697 var direction = _pathDirection(parentCodeUnits, parentIndex);
698 return direction == _PathDirection.aboveRoot ? null : false;
699 }
700
701 // 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
703 // what that tells us.
704 var direction = _pathDirection(childCodeUnits, childIndex);
705
706 // If there are no more components in the child, then it's the same as
707 // the parent, not within it.
708 //
709 // isWithin("foo/bar", "foo/bar") //=> false
710 // isWithin("foo/bar", "foo/bar//") //=> false
711 if (direction == _PathDirection.atRoot) return false;
712
713 // 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.
715 //
716 // isWithin("foo/bar", "foo/bar/..") //=> false
717 // isWithin("foo/bar", "foo/bar/baz/bang/../../..") //=> false
718 // isWithin("foo/bar", "foo/bar/baz/bang/../../../bar/baz") //=> true
719 if (direction == _PathDirection.aboveRoot) return null;
720
721 // The child is within the parent if and only if we're on a separator
722 // boundary.
723 //
724 // isWithin("foo/bar", "foo/bar/baz") //=> true
725 // isWithin("foo/bar/", "foo/bar/baz") //=> true
726 // isWithin("foo/bar", "foo/barbaz") //=> false
727 return style.isSeparator(childCodeUnits[childIndex]) ||
728 style.isSeparator(lastCodeUnit);
729 }
730
731 // Returns a [_PathDirection] describing the path represented by [codeUnits]
732 // after [index].
733 //
734 // This ignores leading separators.
735 //
736 // pathDirection("foo") //=> below root
737 // pathDirection("foo/bar/../baz") //=> below root
738 // pathDirection("//foo/bar/baz") //=> below root
739 // pathDirection("/") //=> at root
740 // pathDirection("foo/..") //=> at root
741 // pathDirection("foo/../baz") //=> reaches root
742 // pathDirection("foo/../..") //=> above root
743 // pathDirection("foo/../../foo/bar/baz") //=> above root
744 _PathDirection _pathDirection(List<int> codeUnits, int index) {
745 var depth = 0;
746 var reachedRoot = false;
747 var i = index;
748 while (i < codeUnits.length) {
749 // Ignore initial separators or doubled separators.
750 while (i < codeUnits.length && style.isSeparator(codeUnits[i])) {
751 i++;
752 }
753
754 // If we're at the end, stop.
755 if (i == codeUnits.length) break;
756
757 // Move through the path component to the next separator.
758 var start = i;
759 while (i < codeUnits.length && !style.isSeparator(codeUnits[i])) {
760 i++;
761 }
762
763 // See if the path component is ".", "..", or a name.
764 if (i - start == 1 && codeUnits[start] == chars.PERIOD) {
765 // Don't change the depth.
766 } else if (i - start == 2 &&
767 codeUnits[start] == chars.PERIOD &&
768 codeUnits[start + 1] == chars.PERIOD) {
769 // ".." backs out a directory.
770 depth--;
771
772 // If we work back beyond the root, stop.
773 if (depth < 0) break;
774
775 // Record that we reached the root so we don't return
776 // [_PathDirection.belowRoot].
777 if (depth == 0) reachedRoot = true;
778 } else {
779 // Step inside a directory.
780 depth++;
781 }
782
783 // If we're at the end, stop.
784 if (i == codeUnits.length) break;
785
786 // Move past the separator.
787 i++;
788 }
789
790 if (depth < 0) return _PathDirection.aboveRoot;
791 if (depth == 0) return _PathDirection.atRoot;
792 if (reachedRoot) return _PathDirection.reachesRoot;
793 return _PathDirection.belowRoot;
794 }
795
443 /// Removes a trailing extension from the last part of [path]. 796 /// Removes a trailing extension from the last part of [path].
444 /// 797 ///
445 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' 798 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
446 String withoutExtension(String path) { 799 String withoutExtension(String path) {
447 var parsed = _parse(path); 800 var parsed = _parse(path);
448 801
449 for (var i = parsed.parts.length - 1; i >= 0; i--) { 802 for (var i = parsed.parts.length - 1; i >= 0; i--) {
450 if (!parsed.parts[i].isEmpty) { 803 if (!parsed.parts[i].isEmpty) {
451 parsed.parts[i] = parsed.basenameWithoutExtension; 804 parsed.parts[i] = parsed.basenameWithoutExtension;
452 break; 805 break;
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
565 var message = new StringBuffer(); 918 var message = new StringBuffer();
566 message.write("$method("); 919 message.write("$method(");
567 message.write(args 920 message.write(args
568 .take(numArgs) 921 .take(numArgs)
569 .map((arg) => arg == null ? "null" : '"$arg"') 922 .map((arg) => arg == null ? "null" : '"$arg"')
570 .join(", ")); 923 .join(", "));
571 message.write("): part ${i - 1} was null, but part $i was not."); 924 message.write("): part ${i - 1} was null, but part $i was not.");
572 throw new ArgumentError(message.toString()); 925 throw new ArgumentError(message.toString());
573 } 926 }
574 } 927 }
928
929 /// An enum of possible return values for [Context._pathDirection].
930 class _PathDirection {
931 /// The path contains enough ".." components that at some point it reaches
932 /// above its original root.
933 ///
934 /// Note that this applies even if the path ends beneath its original root. It
935 /// takes precendence over any other return values that may apple.
936 static const aboveRoot = const _PathDirection("above root");
937
938 /// The path contains enough ".." components that it ends at its original
939 /// root.
940 static const atRoot = const _PathDirection("at root");
941
942 /// The path contains enough ".." components that at some point it reaches its
943 /// original root, but it ends beneath that root.
944 static const reachesRoot = const _PathDirection("reaches root");
945
946 /// The path never reaches to or above its original root.
947 static const belowRoot = const _PathDirection("below root");
948
949 final String name;
950
951 const _PathDirection(this.name);
952
953 String toString() => name;
954 }
OLDNEW
« no previous file with comments | « packages/path/lib/path.dart ('k') | packages/path/pubspec.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698