LCOV - code coverage report
Current view: top level - path-1.4.2/lib/src - context.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 60 275 21.8 %
Date: 2017-10-10 20:17:03 Functions: 0 0 -

          Line data    Source code
       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
       3             : // BSD-style license that can be found in the LICENSE file.
       4             : 
       5             : import 'dart:math' as math;
       6             : 
       7             : import 'characters.dart' as chars;
       8             : import 'internal_style.dart';
       9             : import 'style.dart';
      10             : import 'parsed_path.dart';
      11             : import 'path_exception.dart';
      12             : import '../path.dart' as p;
      13             : 
      14           5 : Context createInternal() => new Context._internal();
      15             : 
      16             : /// An instantiable class for manipulating paths. Unlike the top-level
      17             : /// functions, this lets you explicitly select what platform the paths will use.
      18             : class Context {
      19             :   /// Creates a new path context for the given style and current directory.
      20             :   ///
      21             :   /// If [style] is omitted, it uses the host operating system's path style. If
      22             :   /// only [current] is omitted, it defaults ".". If *both* [style] and
      23             :   /// [current] are omitted, [current] defaults to the real current working
      24             :   /// directory.
      25             :   ///
      26             :   /// On the browser, [style] defaults to [Style.url] and [current] defaults to
      27             :   /// the current URL.
      28             :   factory Context({Style style, String current}) {
      29             :     if (current == null) {
      30             :       if (style == null) {
      31           0 :         current = p.current;
      32             :       } else {
      33             :         current = ".";
      34             :       }
      35             :     }
      36             : 
      37             :     if (style == null) {
      38           0 :       style = Style.platform;
      39           0 :     } else if (style is! InternalStyle) {
      40           0 :       throw new ArgumentError("Only styles defined by the path package are "
      41             :           "allowed.");
      42             :     }
      43             : 
      44           0 :     return new Context._(style as InternalStyle, current);
      45             :   }
      46             : 
      47             :   /// Create a [Context] to be used internally within path.
      48             :   Context._internal()
      49          10 :       : style = Style.platform as InternalStyle,
      50           5 :         _current = null;
      51             : 
      52           0 :   Context._(this.style, this._current);
      53             : 
      54             :   /// The style of path that this context works with.
      55             :   final InternalStyle style;
      56             : 
      57             :   /// The current directory given when Context was created. If null, current
      58             :   /// directory is evaluated from 'p.current'.
      59             :   final String _current;
      60             : 
      61             :   /// The current directory that relative paths are relative to.
      62          10 :   String get current => _current != null ? _current : p.current;
      63             : 
      64             :   /// Gets the path separator for the context's [style]. On Mac and Linux,
      65             :   /// this is `/`. On Windows, it's `\`.
      66           0 :   String get separator => style.separator;
      67             : 
      68             :   /// Creates a new path by appending the given path parts to [current].
      69             :   /// Equivalent to [join()] with [current] as the first argument. Example:
      70             :   ///
      71             :   ///     var context = new Context(current: '/root');
      72             :   ///     context.absolute('path', 'to', 'foo'); // -> '/root/path/to/foo'
      73             :   ///
      74             :   /// If [current] isn't absolute, this won't return an absolute path.
      75             :   String absolute(String part1, [String part2, String part3, String part4,
      76             :       String part5, String part6, String part7]) {
      77           0 :     _validateArgList(
      78           0 :         "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           0 :     if (part2 == null && isAbsolute(part1) && !isRootRelative(part1)) {
      83             :       return part1;
      84             :     }
      85             : 
      86           0 :     return join(current, part1, part2, part3, part4, part5, part6, part7);
      87             :   }
      88             : 
      89             :   /// Gets the part of [path] after the last separator on the context's
      90             :   /// platform.
      91             :   ///
      92             :   ///     context.basename('path/to/foo.dart'); // -> 'foo.dart'
      93             :   ///     context.basename('path/to');          // -> 'to'
      94             :   ///
      95             :   /// Trailing separators are ignored.
      96             :   ///
      97             :   ///     context.basename('path/to/'); // -> 'to'
      98           0 :   String basename(String path) => _parse(path).basename;
      99             : 
     100             :   /// Gets the part of [path] after the last separator on the context's
     101             :   /// platform, and without any trailing file extension.
     102             :   ///
     103             :   ///     context.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo'
     104             :   ///
     105             :   /// Trailing separators are ignored.
     106             :   ///
     107             :   ///     context.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo'
     108             :   String basenameWithoutExtension(String path) =>
     109           0 :       _parse(path).basenameWithoutExtension;
     110             : 
     111             :   /// Gets the part of [path] before the last separator.
     112             :   ///
     113             :   ///     context.dirname('path/to/foo.dart'); // -> 'path/to'
     114             :   ///     context.dirname('path/to');          // -> 'path'
     115             :   ///
     116             :   /// Trailing separators are ignored.
     117             :   ///
     118             :   ///     context.dirname('path/to/'); // -> 'path'
     119             :   String dirname(String path) {
     120           0 :     var parsed = _parse(path);
     121           0 :     parsed.removeTrailingSeparators();
     122           0 :     if (parsed.parts.isEmpty) return parsed.root == null ? '.' : parsed.root;
     123           0 :     if (parsed.parts.length == 1) {
     124           0 :       return parsed.root == null ? '.' : parsed.root;
     125             :     }
     126           0 :     parsed.parts.removeLast();
     127           0 :     parsed.separators.removeLast();
     128           0 :     parsed.removeTrailingSeparators();
     129           0 :     return parsed.toString();
     130             :   }
     131             : 
     132             :   /// Gets the file extension of [path]: the portion of [basename] from the last
     133             :   /// `.` to the end (including the `.` itself).
     134             :   ///
     135             :   ///     context.extension('path/to/foo.dart'); // -> '.dart'
     136             :   ///     context.extension('path/to/foo'); // -> ''
     137             :   ///     context.extension('path.to/foo'); // -> ''
     138             :   ///     context.extension('path/to/foo.dart.js'); // -> '.js'
     139             :   ///
     140             :   /// If the file name starts with a `.`, then it is not considered an
     141             :   /// extension:
     142             :   ///
     143             :   ///     context.extension('~/.bashrc');    // -> ''
     144             :   ///     context.extension('~/.notes.txt'); // -> '.txt'
     145           0 :   String extension(String path) => _parse(path).extension;
     146             : 
     147             :   // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
     148             :   /// Returns the root of [path] if it's absolute, or an empty string if it's
     149             :   /// relative.
     150             :   ///
     151             :   ///     // Unix
     152             :   ///     context.rootPrefix('path/to/foo'); // -> ''
     153             :   ///     context.rootPrefix('/path/to/foo'); // -> '/'
     154             :   ///
     155             :   ///     // Windows
     156             :   ///     context.rootPrefix(r'path\to\foo'); // -> ''
     157             :   ///     context.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
     158             :   ///
     159             :   ///     // URL
     160             :   ///     context.rootPrefix('path/to/foo'); // -> ''
     161             :   ///     context.rootPrefix('http://dartlang.org/path/to/foo');
     162             :   ///       // -> 'http://dartlang.org'
     163           0 :   String rootPrefix(String path) => path.substring(0, style.rootLength(path));
     164             : 
     165             :   /// Returns `true` if [path] is an absolute path and `false` if it is a
     166             :   /// relative path.
     167             :   ///
     168             :   /// On POSIX systems, absolute paths start with a `/` (forward slash). On
     169             :   /// Windows, an absolute path starts with `\\`, or a drive letter followed by
     170             :   /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and
     171             :   /// optional hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`.
     172             :   ///
     173             :   /// URLs that start with `/` are known as "root-relative", since they're
     174             :   /// relative to the root of the current URL. Since root-relative paths are
     175             :   /// still absolute in every other sense, [isAbsolute] will return true for
     176             :   /// them. They can be detected using [isRootRelative].
     177          15 :   bool isAbsolute(String path) => style.rootLength(path) > 0;
     178             : 
     179             :   /// Returns `true` if [path] is a relative path and `false` if it is absolute.
     180             :   /// On POSIX systems, absolute paths start with a `/` (forward slash). On
     181             :   /// Windows, an absolute path starts with `\\`, or a drive letter followed by
     182             :   /// `:/` or `:\`.
     183           5 :   bool isRelative(String path) => !this.isAbsolute(path);
     184             : 
     185             :   /// Returns `true` if [path] is a root-relative path and `false` if it's not.
     186             :   ///
     187             :   /// URLs that start with `/` are known as "root-relative", since they're
     188             :   /// relative to the root of the current URL. Since root-relative paths are
     189             :   /// still absolute in every other sense, [isAbsolute] will return true for
     190             :   /// them. They can be detected using [isRootRelative].
     191             :   ///
     192             :   /// No POSIX and Windows paths are root-relative.
     193          10 :   bool isRootRelative(String path) => style.isRootRelative(path);
     194             : 
     195             :   /// Joins the given path parts into a single path. Example:
     196             :   ///
     197             :   ///     context.join('path', 'to', 'foo'); // -> 'path/to/foo'
     198             :   ///
     199             :   /// If any part ends in a path separator, then a redundant separator will not
     200             :   /// be added:
     201             :   ///
     202             :   ///     context.join('path/', 'to', 'foo'); // -> 'path/to/foo
     203             :   ///
     204             :   /// If a part is an absolute path, then anything before that will be ignored:
     205             :   ///
     206             :   ///     context.join('path', '/to', 'foo'); // -> '/to/foo'
     207             :   ///
     208             :   String join(String part1, [String part2, String part3, String part4,
     209             :       String part5, String part6, String part7, String part8]) {
     210           0 :     var parts = <String>[
     211             :       part1,
     212             :       part2,
     213             :       part3,
     214             :       part4,
     215             :       part5,
     216             :       part6,
     217             :       part7,
     218             :       part8
     219             :     ];
     220           0 :     _validateArgList("join", parts);
     221           0 :     return joinAll(parts.where((part) => part != null));
     222             :   }
     223             : 
     224             :   /// Joins the given path parts into a single path. Example:
     225             :   ///
     226             :   ///     context.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo'
     227             :   ///
     228             :   /// If any part ends in a path separator, then a redundant separator will not
     229             :   /// be added:
     230             :   ///
     231             :   ///     context.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo
     232             :   ///
     233             :   /// If a part is an absolute path, then anything before that will be ignored:
     234             :   ///
     235             :   ///     context.joinAll(['path', '/to', 'foo']); // -> '/to/foo'
     236             :   ///
     237             :   /// For a fixed number of parts, [join] is usually terser.
     238             :   String joinAll(Iterable<String> parts) {
     239           0 :     var buffer = new StringBuffer();
     240             :     var needsSeparator = false;
     241             :     var isAbsoluteAndNotRootRelative = false;
     242             : 
     243           0 :     for (var part in parts.where((part) => part != '')) {
     244           0 :       if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) {
     245             :         // If the new part is root-relative, it preserves the previous root but
     246             :         // replaces the path after it.
     247           0 :         var parsed = _parse(part);
     248           0 :         var path = buffer.toString();
     249           0 :         parsed.root = path.substring(
     250           0 :             0, style.rootLength(path, withDrive: true));
     251           0 :         if (style.needsSeparator(parsed.root)) {
     252           0 :           parsed.separators[0] = style.separator;
     253             :         }
     254           0 :         buffer.clear();
     255           0 :         buffer.write(parsed.toString());
     256           0 :       } else if (this.isAbsolute(part)) {
     257           0 :         isAbsoluteAndNotRootRelative = !this.isRootRelative(part);
     258             :         // An absolute path discards everything before it.
     259           0 :         buffer.clear();
     260           0 :         buffer.write(part);
     261             :       } else {
     262           0 :         if (part.length > 0 && style.containsSeparator(part[0])) {
     263             :           // The part starts with a separator, so we don't need to add one.
     264             :         } else if (needsSeparator) {
     265           0 :           buffer.write(separator);
     266             :         }
     267             : 
     268           0 :         buffer.write(part);
     269             :       }
     270             : 
     271             :       // Unless this part ends with a separator, we'll need to add one before
     272             :       // the next part.
     273           0 :       needsSeparator = style.needsSeparator(part);
     274             :     }
     275             : 
     276           0 :     return buffer.toString();
     277             :   }
     278             : 
     279             :   // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
     280             :   /// Splits [path] into its components using the current platform's
     281             :   /// [separator]. Example:
     282             :   ///
     283             :   ///     context.split('path/to/foo'); // -> ['path', 'to', 'foo']
     284             :   ///
     285             :   /// The path will *not* be normalized before splitting.
     286             :   ///
     287             :   ///     context.split('path/../foo'); // -> ['path', '..', 'foo']
     288             :   ///
     289             :   /// If [path] is absolute, the root directory will be the first element in the
     290             :   /// array. Example:
     291             :   ///
     292             :   ///     // Unix
     293             :   ///     context.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo']
     294             :   ///
     295             :   ///     // Windows
     296             :   ///     context.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
     297             :   List<String> split(String path) {
     298           5 :     var parsed = _parse(path);
     299             :     // Filter out empty parts that exist due to multiple separators in a row.
     300          25 :     parsed.parts = parsed.parts.where((part) => !part.isEmpty).toList();
     301          20 :     if (parsed.root != null) parsed.parts.insert(0, parsed.root);
     302           5 :     return parsed.parts;
     303             :   }
     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           0 :     path = absolute(path);
     319           0 :     if (style != Style.windows && !_needsNormalization(path)) return path;
     320             : 
     321           0 :     var parsed = _parse(path);
     322           0 :     parsed.normalize(canonicalize: true);
     323           0 :     return parsed.toString();
     324             :   }
     325             : 
     326             :   /// Normalizes [path], simplifying it by handling `..`, and `.`, and
     327             :   /// removing redundant path separators whenever possible.
     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             :   ///
     333             :   ///     context.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
     334             :   String normalize(String path) {
     335           5 :     if (!_needsNormalization(path)) return path;
     336             : 
     337           5 :     var parsed = _parse(path);
     338           5 :     parsed.normalize();
     339           5 :     return parsed.toString();
     340             :   }
     341             : 
     342             :   /// Returns whether [path] needs to be normalized.
     343             :   bool _needsNormalization(String path) {
     344             :     var start = 0;
     345           5 :     var codeUnits = path.codeUnits;
     346             :     var previousPrevious;
     347             :     var previous;
     348             : 
     349             :     // Skip past the root before we start looking for snippets that need
     350             :     // normalization. We want to normalize "//", but not when it's part of
     351             :     // "http://".
     352          10 :     var root = style.rootLength(path);
     353           5 :     if (root != 0) {
     354             :       start = root;
     355             :       previous = chars.SLASH;
     356             : 
     357             :       // On Windows, the root still needs to be normalized if it contains a
     358             :       // forward slash.
     359          15 :       if (style == Style.windows) {
     360           0 :         for (var i = 0; i < root; i++) {
     361           0 :           if (codeUnits[i] == chars.SLASH) return true;
     362             :         }
     363             :       }
     364             :     }
     365             : 
     366          15 :     for (var i = start; i < codeUnits.length; i++) {
     367           5 :       var codeUnit = codeUnits[i];
     368          10 :       if (style.isSeparator(codeUnit)) {
     369             :         // Forward slashes in Windows paths are normalized to backslashes.
     370          15 :         if (style == Style.windows && codeUnit == chars.SLASH) return true;
     371             : 
     372             :         // Multiple separators are normalized to single separators.
     373          10 :         if (previous != null && style.isSeparator(previous)) return true;
     374             : 
     375             :         // Single dots and double dots are normalized to directory traversals.
     376             :         //
     377             :         // This can return false positives for ".../", but that's unlikely
     378             :         // enough that it's probably not going to cause performance issues.
     379           5 :         if (previous == chars.PERIOD &&
     380             :             (previousPrevious == null ||
     381           0 :              previousPrevious == chars.PERIOD ||
     382           0 :              style.isSeparator(previousPrevious))) {
     383             :           return true;
     384             :         }
     385             :       }
     386             : 
     387             :       previousPrevious = previous;
     388             :       previous = codeUnit;
     389             :     }
     390             : 
     391             :     // Empty paths are normalized to ".".
     392             :     if (previous == null) return true;
     393             : 
     394             :     // Trailing separators are removed.
     395          10 :     if (style.isSeparator(previous)) return true;
     396             : 
     397             :     // Single dots and double dots are normalized to directory traversals.
     398           0 :     if (previous == chars.PERIOD &&
     399             :         (previousPrevious == null ||
     400           0 :          style.isSeparator(previousPrevious) ||
     401           0 :          previousPrevious == chars.PERIOD)) {
     402             :       return true;
     403             :     }
     404             : 
     405             :     return false;
     406             :   }
     407             : 
     408             :   /// Attempts to convert [path] to an equivalent relative path relative to
     409             :   /// [root].
     410             :   ///
     411             :   ///     var context = new Context(current: '/root/path');
     412             :   ///     context.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
     413             :   ///     context.relative('/root/other.dart'); // -> '../other.dart'
     414             :   ///
     415             :   /// If the [from] argument is passed, [path] is made relative to that instead.
     416             :   ///
     417             :   ///     context.relative('/root/path/a/b.dart',
     418             :   ///         from: '/root/path'); // -> 'a/b.dart'
     419             :   ///     context.relative('/root/other.dart',
     420             :   ///         from: '/root/path'); // -> '../other.dart'
     421             :   ///
     422             :   /// If [path] and/or [from] are relative paths, they are assumed to be
     423             :   /// relative to [current].
     424             :   ///
     425             :   /// Since there is no relative path from one drive letter to another on
     426             :   /// Windows, this will return an absolute path in that case.
     427             :   ///
     428             :   ///     context.relative(r'D:\other', from: r'C:\other'); // -> 'D:\other'
     429             :   ///
     430             :   /// This will also return an absolute path if an absolute [path] is passed to
     431             :   /// a context with a relative path for [current].
     432             :   ///
     433             :   ///     var context = new Context(r'some/relative/path');
     434             :   ///     context.relative(r'/absolute/path'); // -> '/absolute/path'
     435             :   ///
     436             :   /// If [root] is relative, it may be impossible to determine a path from
     437             :   /// [from] to [path]. For example, if [root] and [path] are "." and [from] is
     438             :   /// "/", no path can be determined. In this case, a [PathException] will be
     439             :   /// thrown.
     440             :   String relative(String path, {String from}) {
     441             :     // Avoid expensive computation if the path is already relative.
     442           5 :     if (from == null && this.isRelative(path)) return this.normalize(path);
     443             : 
     444           5 :     from = from == null ? current : absolute(from);
     445             : 
     446             :     // We can't determine the path from a relative path to an absolute path.
     447           5 :     if (this.isRelative(from) && this.isAbsolute(path)) {
     448           0 :       return this.normalize(path);
     449             :     }
     450             : 
     451             :     // If the given path is relative, resolve it relative to the context's
     452             :     // current directory.
     453          10 :     if (this.isRelative(path) || this.isRootRelative(path)) {
     454           0 :       path = this.absolute(path);
     455             :     }
     456             : 
     457             :     // If the path is still relative and `from` is absolute, we're unable to
     458             :     // find a path from `from` to `path`.
     459           5 :     if (this.isRelative(path) && this.isAbsolute(from)) {
     460           0 :       throw new PathException('Unable to find a path to "$path" from "$from".');
     461             :     }
     462             : 
     463          10 :     var fromParsed = _parse(from)..normalize();
     464          10 :     var pathParsed = _parse(path)..normalize();
     465             : 
     466          30 :     if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') {
     467           0 :       return pathParsed.toString();
     468             :     }
     469             : 
     470             :     // If the root prefixes don't match (for example, different drive letters
     471             :     // on Windows), then there is no relative path, so just return the absolute
     472             :     // one. In Windows, drive letters are case-insenstive and we allow
     473             :     // calculation of relative paths, even if a path has not been normalized.
     474          15 :     if (fromParsed.root != pathParsed.root &&
     475           0 :         ((fromParsed.root == null || pathParsed.root == null) ||
     476           0 :             !style.pathsEqual(fromParsed.root, pathParsed.root))) {
     477           0 :       return pathParsed.toString();
     478             :     }
     479             : 
     480             :     // Strip off their common prefix.
     481          15 :     while (fromParsed.parts.length > 0 &&
     482          15 :         pathParsed.parts.length > 0 &&
     483          30 :         style.pathsEqual(fromParsed.parts[0], pathParsed.parts[0])) {
     484          10 :       fromParsed.parts.removeAt(0);
     485          10 :       fromParsed.separators.removeAt(1);
     486          10 :       pathParsed.parts.removeAt(0);
     487          10 :       pathParsed.separators.removeAt(1);
     488             :     }
     489             : 
     490             :     // If there are any directories left in the from path, we need to walk up
     491             :     // out of them. If a directory left in the from path is '..', it cannot
     492             :     // be cancelled by adding a '..'.
     493          15 :     if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') {
     494           0 :       throw new PathException('Unable to find a path to "$path" from "$from".');
     495             :     }
     496          10 :     pathParsed.parts.insertAll(
     497          15 :         0, new List.filled(fromParsed.parts.length, '..'));
     498          10 :     pathParsed.separators[0] = '';
     499          10 :     pathParsed.separators.insertAll(
     500          25 :         1, new List.filled(fromParsed.parts.length, style.separator));
     501             : 
     502             :     // Corner case: the paths completely collapsed.
     503          15 :     if (pathParsed.parts.length == 0) return '.';
     504             : 
     505             :     // Corner case: path was '.' and some '..' directories were added in front.
     506             :     // Don't add a final '/.' in that case.
     507           0 :     if (pathParsed.parts.length > 1 && pathParsed.parts.last == '.') {
     508           0 :       pathParsed.parts.removeLast();
     509           0 :       pathParsed.separators
     510           0 :         ..removeLast()
     511           0 :         ..removeLast()
     512           0 :         ..add('');
     513             :     }
     514             : 
     515             :     // Make it relative.
     516           0 :     pathParsed.root = '';
     517           0 :     pathParsed.removeTrailingSeparators();
     518             : 
     519           0 :     return pathParsed.toString();
     520             :   }
     521             : 
     522             :   /// Returns `true` if [child] is a path beneath `parent`, and `false`
     523             :   /// otherwise.
     524             :   ///
     525             :   ///     path.isWithin('/root/path', '/root/path/a'); // -> true
     526             :   ///     path.isWithin('/root/path', '/root/other'); // -> false
     527             :   ///     path.isWithin('/root/path', '/root/path'); // -> false
     528             :   bool isWithin(String parent, String child) =>
     529           0 :       _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           0 :       _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) {
     544             :     // Make both paths the same level of relative. We're only able to do the
     545             :     // quick comparison if both paths are in the same format, and making a path
     546             :     // absolute is faster than making it relative.
     547           0 :     var parentIsAbsolute = isAbsolute(parent);
     548           0 :     var childIsAbsolute = isAbsolute(child);
     549             :     if (parentIsAbsolute && !childIsAbsolute) {
     550           0 :       child = absolute(child);
     551           0 :       if (style.isRootRelative(parent)) parent = absolute(parent);
     552             :     } else if (childIsAbsolute && !parentIsAbsolute) {
     553           0 :       parent = absolute(parent);
     554           0 :       if (style.isRootRelative(child)) child = absolute(child);
     555             :     } else if (childIsAbsolute && parentIsAbsolute) {
     556           0 :       var childIsRootRelative = style.isRootRelative(child);
     557           0 :       var parentIsRootRelative = style.isRootRelative(parent);
     558             : 
     559             :       if (childIsRootRelative && !parentIsRootRelative) {
     560           0 :         child = absolute(child);
     561             :       } else if (parentIsRootRelative && !childIsRootRelative) {
     562           0 :         parent = absolute(parent);
     563             :       }
     564             :     }
     565             : 
     566           0 :     var result = _isWithinOrEqualsFast(parent, child);
     567           0 :     if (result != _PathRelation.inconclusive) return result;
     568             : 
     569             :     var relative;
     570             :     try {
     571           0 :       relative = this.relative(child, from: parent);
     572           0 :     } on PathException catch (_) {
     573             :       // If no relative path from [parent] to [child] is found, [child]
     574             :       // definitely isn't a child of [parent].
     575             :       return _PathRelation.different;
     576             :     }
     577             : 
     578           0 :     if (!this.isRelative(relative)) return _PathRelation.different;
     579           0 :     if (relative == '.') return _PathRelation.equal;
     580           0 :     if (relative == '..') return _PathRelation.different;
     581           0 :     return (relative.length >= 3 &&
     582           0 :             relative.startsWith('..') &&
     583           0 :              style.isSeparator(relative.codeUnitAt(2)))
     584             :         ? _PathRelation.different
     585             :         : _PathRelation.within;
     586             :   }
     587             : 
     588             :   /// An optimized implementation of [_isWithinOrEquals] that doesn't handle a
     589             :   /// few complex cases.
     590             :   _PathRelation _isWithinOrEqualsFast(String parent, String child) {
     591             :     // Normally we just bail when we see "." path components, but we can handle
     592             :     // a single dot easily enough.
     593           0 :     if (parent == '.') parent = '';
     594             : 
     595           0 :     var parentRootLength = style.rootLength(parent);
     596           0 :     var childRootLength = style.rootLength(child);
     597             : 
     598             :     // If the roots aren't the same length, we know both paths are absolute or
     599             :     // both are root-relative, and thus that the roots are meaningfully
     600             :     // different.
     601             :     //
     602             :     //     isWithin("C:/bar", "//foo/bar/baz") //=> false
     603             :     //     isWithin("http://example.com/", "http://google.com/bar") //=> false
     604           0 :     if (parentRootLength != childRootLength) return _PathRelation.different;
     605             : 
     606             :     // Make sure that the roots are textually the same as well.
     607             :     //
     608             :     //     isWithin("C:/bar", "D:/bar/baz") //=> false
     609             :     //     isWithin("http://example.com/", "http://example.org/bar") //=> false
     610           0 :     for (var i = 0; i < parentRootLength; i++) {
     611           0 :       var parentCodeUnit = parent.codeUnitAt(i);
     612           0 :       var childCodeUnit = child.codeUnitAt(i);
     613           0 :       if (!style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) {
     614             :         return _PathRelation.different;
     615             :       }
     616             :     }
     617             : 
     618             :     // Start by considering the last code unit as a separator, since
     619             :     // semantically we're starting at a new path component even if we're
     620             :     // comparing relative paths.
     621             :     var lastCodeUnit = chars.SLASH;
     622             : 
     623             :     /// The index of the last separator in [parent].
     624             :     int lastParentSeparator;
     625             : 
     626             :     // Iterate through both paths as long as they're semantically identical.
     627             :     var parentIndex = parentRootLength;
     628             :     var childIndex = childRootLength;
     629           0 :     while (parentIndex < parent.length && childIndex < child.length) {
     630           0 :       var parentCodeUnit = parent.codeUnitAt(parentIndex);
     631           0 :       var childCodeUnit = child.codeUnitAt(childIndex);
     632           0 :       if (style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) {
     633           0 :         if (style.isSeparator(parentCodeUnit)) {
     634             :           lastParentSeparator = parentIndex;
     635             :         }
     636             : 
     637             :         lastCodeUnit = parentCodeUnit;
     638           0 :         parentIndex++;
     639           0 :         childIndex++;
     640             :         continue;
     641             :       }
     642             : 
     643             :       // Ignore multiple separators in a row.
     644           0 :       if (style.isSeparator(parentCodeUnit) &&
     645           0 :           style.isSeparator(lastCodeUnit)) {
     646             :         lastParentSeparator = parentIndex;
     647           0 :         parentIndex++;
     648             :         continue;
     649           0 :       } else if (style.isSeparator(childCodeUnit) &&
     650           0 :           style.isSeparator(lastCodeUnit)) {
     651           0 :         childIndex++;
     652             :         continue;
     653             :       }
     654             : 
     655             :       // 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
     657             :       // "/" or "./". Otherwise, it's just a normal non-matching character.
     658             :       //
     659             :       //     isWithin("foo/./bar", "foo/bar/baz") //=> true
     660             :       //     isWithin("foo/bar/../baz", "foo/bar/.foo") //=> false
     661           0 :       if (parentCodeUnit == chars.PERIOD && style.isSeparator(lastCodeUnit)) {
     662           0 :         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           0 :         if (parentIndex == parent.length) break;
     667           0 :         parentCodeUnit = parent.codeUnitAt(parentIndex);
     668             : 
     669             :         // We've hit "/./", which we can ignore.
     670           0 :         if (style.isSeparator(parentCodeUnit)) {
     671             :           lastParentSeparator = parentIndex;
     672           0 :           parentIndex++;
     673             :           continue;
     674             :         }
     675             : 
     676             :         // We've hit "/..", which may be a directory traversal operator that
     677             :         // we can't handle on the fast track.
     678           0 :         if (parentCodeUnit == chars.PERIOD) {
     679           0 :           parentIndex++;
     680           0 :           if (parentIndex == parent.length ||
     681           0 :               style.isSeparator(parent.codeUnitAt(parentIndex))) {
     682             :             return _PathRelation.inconclusive;
     683             :           }
     684             :         }
     685             : 
     686             :         // If this isn't a directory traversal, fall through so we hit the
     687             :         // normal handling for mismatched paths.
     688             :       }
     689             : 
     690             :       // This is the same logic as above, but for the child path instead of the
     691             :       // parent.
     692           0 :       if (childCodeUnit == chars.PERIOD && style.isSeparator(lastCodeUnit)) {
     693           0 :         childIndex++;
     694           0 :         if (childIndex == child.length) break;
     695           0 :         childCodeUnit = child.codeUnitAt(childIndex);
     696             : 
     697           0 :         if (style.isSeparator(childCodeUnit)) {
     698           0 :           childIndex++;
     699             :           continue;
     700             :         }
     701             : 
     702           0 :         if (childCodeUnit == chars.PERIOD) {
     703           0 :           childIndex++;
     704           0 :           if (childIndex == child.length ||
     705           0 :               style.isSeparator(child.codeUnitAt(childIndex))) {
     706             :             return _PathRelation.inconclusive;
     707             :           }
     708             :         }
     709             :       }
     710             : 
     711             :       // If we're here, we've hit two non-matching, non-significant characters.
     712             :       // As long as the remainders of the two paths don't have any unresolved
     713             :       // ".." components, we can be confident that [child] is not within
     714             :       // [parent].
     715           0 :       var childDirection = _pathDirection(child, childIndex);
     716           0 :       if (childDirection != _PathDirection.belowRoot) {
     717             :         return _PathRelation.inconclusive;
     718             :       }
     719             : 
     720           0 :       var parentDirection = _pathDirection(parent, parentIndex);
     721           0 :       if (parentDirection != _PathDirection.belowRoot) {
     722             :         return _PathRelation.inconclusive;
     723             :       }
     724             : 
     725             :       return _PathRelation.different;
     726             :     }
     727             : 
     728             :     // If the child is shorter than the parent, it's probably not within the
     729             :     // parent. The only exception is if the parent has some weird ".." stuff
     730             :     // going on, in which case we do the slow check.
     731             :     //
     732             :     //     isWithin("foo/bar/baz", "foo/bar") //=> false
     733             :     //     isWithin("foo/bar/baz/../..", "foo/bar") //=> true
     734           0 :     if (childIndex == child.length) {
     735           0 :       if (parentIndex == parent.length ||
     736           0 :           style.isSeparator(parent.codeUnitAt(parentIndex))) {
     737             :         lastParentSeparator = parentIndex;
     738             :       } else {
     739           5 :         lastParentSeparator ??= math.max(0, parentRootLength - 1);
     740             :       }
     741             : 
     742           0 :       var direction = _pathDirection(parent,
     743           0 :           lastParentSeparator ?? parentRootLength - 1);
     744           0 :       if (direction == _PathDirection.atRoot) return _PathRelation.equal;
     745           0 :       return direction == _PathDirection.aboveRoot
     746             :           ? _PathRelation.inconclusive
     747             :           : _PathRelation.different;
     748             :     }
     749             : 
     750             :     // We've reached the end of the parent path, which means it's time to make a
     751             :     // decision. Before we do, though, we'll check the rest of the child to see
     752             :     // what that tells us.
     753           0 :     var direction = _pathDirection(child, childIndex);
     754             : 
     755             :     // If there are no more components in the child, then it's the same as
     756             :     // the parent.
     757             :     //
     758             :     //     isWithin("foo/bar", "foo/bar") //=> false
     759             :     //     isWithin("foo/bar", "foo/bar//") //=> false
     760             :     //     equals("foo/bar", "foo/bar") //=> true
     761             :     //     equals("foo/bar", "foo/bar//") //=> true
     762           0 :     if (direction == _PathDirection.atRoot) return _PathRelation.equal;
     763             : 
     764             :     // If there are unresolved ".." components in the child, no decision we make
     765             :     // will be valid. We'll abort and do the slow check instead.
     766             :     //
     767             :     //     isWithin("foo/bar", "foo/bar/..") //=> false
     768             :     //     isWithin("foo/bar", "foo/bar/baz/bang/../../..") //=> false
     769             :     //     isWithin("foo/bar", "foo/bar/baz/bang/../../../bar/baz") //=> true
     770           0 :     if (direction == _PathDirection.aboveRoot) {
     771             :       return _PathRelation.inconclusive;
     772             :     }
     773             : 
     774             :     // The child is within the parent if and only if we're on a separator
     775             :     // boundary.
     776             :     //
     777             :     //     isWithin("foo/bar", "foo/bar/baz") //=> true
     778             :     //     isWithin("foo/bar/", "foo/bar/baz") //=> true
     779             :     //     isWithin("foo/bar", "foo/barbaz") //=> false
     780           0 :     return (style.isSeparator(child.codeUnitAt(childIndex)) ||
     781           0 :             style.isSeparator(lastCodeUnit))
     782             :         ? _PathRelation.within
     783             :         : _PathRelation.different;
     784             :   }
     785             : 
     786             :   // Returns a [_PathDirection] describing the path represented by [codeUnits]
     787             :   // starting at [index].
     788             :   //
     789             :   // This ignores leading separators.
     790             :   //
     791             :   //     pathDirection("foo") //=> below root
     792             :   //     pathDirection("foo/bar/../baz") //=> below root
     793             :   //     pathDirection("//foo/bar/baz") //=> below root
     794             :   //     pathDirection("/") //=> at root
     795             :   //     pathDirection("foo/..") //=> at root
     796             :   //     pathDirection("foo/../baz") //=> reaches root
     797             :   //     pathDirection("foo/../..") //=> above root
     798             :   //     pathDirection("foo/../../foo/bar/baz") //=> above root
     799             :   _PathDirection _pathDirection(String path, int index) {
     800             :     var depth = 0;
     801             :     var reachedRoot = false;
     802             :     var i = index;
     803           0 :     while (i < path.length) {
     804             :       // Ignore initial separators or doubled separators.
     805           0 :       while (i < path.length && style.isSeparator(path.codeUnitAt(i))) {
     806           0 :         i++;
     807             :       }
     808             : 
     809             :       // If we're at the end, stop.
     810           0 :       if (i == path.length) break;
     811             : 
     812             :       // Move through the path component to the next separator.
     813             :       var start = i;
     814           0 :       while (i < path.length && !style.isSeparator(path.codeUnitAt(i))) {
     815           0 :         i++;
     816             :       }
     817             : 
     818             :       // See if the path component is ".", "..", or a name.
     819           0 :       if (i - start == 1 && path.codeUnitAt(start) == chars.PERIOD) {
     820             :         // Don't change the depth.
     821           0 :       } else if (i - start == 2 &&
     822           0 :           path.codeUnitAt(start) == chars.PERIOD &&
     823           0 :           path.codeUnitAt(start + 1) == chars.PERIOD) {
     824             :         // ".." backs out a directory.
     825           0 :         depth--;
     826             : 
     827             :         // If we work back beyond the root, stop.
     828           0 :         if (depth < 0) break;
     829             : 
     830             :         // Record that we reached the root so we don't return
     831             :         // [_PathDirection.belowRoot].
     832           0 :         if (depth == 0) reachedRoot = true;
     833             :       } else {
     834             :         // Step inside a directory.
     835           0 :         depth++;
     836             :       }
     837             : 
     838             :       // If we're at the end, stop.
     839           0 :       if (i == path.length) break;
     840             : 
     841             :       // Move past the separator.
     842           0 :       i++;
     843             :     }
     844             : 
     845           0 :     if (depth < 0) return _PathDirection.aboveRoot;
     846           0 :     if (depth == 0) return _PathDirection.atRoot;
     847             :     if (reachedRoot) return _PathDirection.reachesRoot;
     848             :     return _PathDirection.belowRoot;
     849             :   }
     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           0 :     path = absolute(path);
     859             : 
     860           0 :     var result = _hashFast(path);
     861             :     if (result != null) return result;
     862             : 
     863           0 :     var parsed = _parse(path);
     864           0 :     parsed.normalize();
     865           0 :     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           0 :     for (var i = 0; i < path.length; i++) {
     877           0 :       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           0 :       if (style.isSeparator(codeUnit)) {
     883             :         wasSeparator = true;
     884             :         continue;
     885             :       }
     886             : 
     887           0 :       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           0 :         if (i + 1 == path.length) break;
     896             : 
     897           0 :         var next = path.codeUnitAt(i + 1);
     898             : 
     899             :         // We can just ignore "/./", since they don't affect the semantics of
     900             :         // the path.
     901           0 :         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           0 :             next == chars.PERIOD &&
     909           0 :             (i + 2 == path.length ||
     910           0 :              style.isSeparator(path.codeUnitAt(i + 2)))) {
     911             :           return null;
     912             :         }
     913             :       }
     914             : 
     915             :       // Make sure [hash] stays under 32 bits even after multiplication.
     916           0 :       hash &= 0x3FFFFFF;
     917           0 :       hash *= 33;
     918           0 :       hash ^= codeUnit;
     919             :       wasSeparator = false;
     920             :       beginning = false;
     921             :     }
     922             :     return hash;
     923             :   }
     924             : 
     925             :   /// Removes a trailing extension from the last part of [path].
     926             :   ///
     927             :   ///     context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
     928             :   String withoutExtension(String path) {
     929           0 :     var parsed = _parse(path);
     930             : 
     931           0 :     for (var i = parsed.parts.length - 1; i >= 0; i--) {
     932           0 :       if (!parsed.parts[i].isEmpty) {
     933           0 :         parsed.parts[i] = parsed.basenameWithoutExtension;
     934             :         break;
     935             :       }
     936             :     }
     937             : 
     938           0 :     return parsed.toString();
     939             :   }
     940             : 
     941             :   /// Returns the path represented by [uri], which may be a [String] or a [Uri].
     942             :   ///
     943             :   /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL
     944             :   /// style, this will just convert [uri] to a string.
     945             :   ///
     946             :   ///     // POSIX
     947             :   ///     context.fromUri('file:///path/to/foo')
     948             :   ///       // -> '/path/to/foo'
     949             :   ///
     950             :   ///     // Windows
     951             :   ///     context.fromUri('file:///C:/path/to/foo')
     952             :   ///       // -> r'C:\path\to\foo'
     953             :   ///
     954             :   ///     // URL
     955             :   ///     context.fromUri('http://dartlang.org/path/to/foo')
     956             :   ///       // -> 'http://dartlang.org/path/to/foo'
     957             :   ///
     958             :   /// If [uri] is relative, a relative path will be returned.
     959             :   ///
     960             :   ///     path.fromUri('path/to/foo'); // -> 'path/to/foo'
     961             :   String fromUri(uri) {
     962           5 :     if (uri is String) uri = Uri.parse(uri);
     963          10 :     return style.pathFromUri(uri);
     964             :   }
     965             : 
     966             :   /// Returns the URI that represents [path].
     967             :   ///
     968             :   /// For POSIX and Windows styles, this will return a `file:` URI. For the URL
     969             :   /// style, this will just convert [path] to a [Uri].
     970             :   ///
     971             :   ///     // POSIX
     972             :   ///     context.toUri('/path/to/foo')
     973             :   ///       // -> Uri.parse('file:///path/to/foo')
     974             :   ///
     975             :   ///     // Windows
     976             :   ///     context.toUri(r'C:\path\to\foo')
     977             :   ///       // -> Uri.parse('file:///C:/path/to/foo')
     978             :   ///
     979             :   ///     // URL
     980             :   ///     context.toUri('http://dartlang.org/path/to/foo')
     981             :   ///       // -> Uri.parse('http://dartlang.org/path/to/foo')
     982             :   Uri toUri(String path) {
     983           0 :     if (isRelative(path)) {
     984           0 :       return style.relativePathToUri(path);
     985             :     } else {
     986           0 :       return style.absolutePathToUri(join(current, path));
     987             :     }
     988             :   }
     989             : 
     990             :   /// Returns a terse, human-readable representation of [uri].
     991             :   ///
     992             :   /// [uri] can be a [String] or a [Uri]. If it can be made relative to the
     993             :   /// current working directory, that's done. Otherwise, it's returned as-is.
     994             :   /// This gracefully handles non-`file:` URIs for [Style.posix] and
     995             :   /// [Style.windows].
     996             :   ///
     997             :   /// The returned value is meant for human consumption, and may be either URI-
     998             :   /// or path-formatted.
     999             :   ///
    1000             :   ///     // POSIX
    1001             :   ///     var context = new Context(current: '/root/path');
    1002             :   ///     context.prettyUri('file:///root/path/a/b.dart'); // -> 'a/b.dart'
    1003             :   ///     context.prettyUri('http://dartlang.org/'); // -> 'http://dartlang.org'
    1004             :   ///
    1005             :   ///     // Windows
    1006             :   ///     var context = new Context(current: r'C:\root\path');
    1007             :   ///     context.prettyUri('file:///C:/root/path/a/b.dart'); // -> r'a\b.dart'
    1008             :   ///     context.prettyUri('http://dartlang.org/'); // -> 'http://dartlang.org'
    1009             :   ///
    1010             :   ///     // URL
    1011             :   ///     var context = new Context(current: 'http://dartlang.org/root/path');
    1012             :   ///     context.prettyUri('http://dartlang.org/root/path/a/b.dart');
    1013             :   ///         // -> r'a/b.dart'
    1014             :   ///     context.prettyUri('file:///root/path'); // -> 'file:///root/path'
    1015             :   String prettyUri(uri) {
    1016           5 :     if (uri is String) uri = Uri.parse(uri);
    1017          25 :     if (uri.scheme == 'file' && style == Style.url) return uri.toString();
    1018          10 :     if (uri.scheme != 'file' && uri.scheme != '' && style != Style.url) {
    1019           0 :       return uri.toString();
    1020             :     }
    1021             : 
    1022          10 :     var path = normalize(fromUri(uri));
    1023           5 :     var rel = relative(path);
    1024             : 
    1025             :     // Only return a relative path if it's actually shorter than the absolute
    1026             :     // path. This avoids ugly things like long "../" chains to get to the root
    1027             :     // and then go back down.
    1028          25 :     return split(rel).length > split(path).length ? path : rel;
    1029             :   }
    1030             : 
    1031          10 :   ParsedPath _parse(String path) => new ParsedPath.parse(path, style);
    1032             : }
    1033             : 
    1034             : /// Validates that there are no non-null arguments following a null one and
    1035             : /// throws an appropriate [ArgumentError] on failure.
    1036             : _validateArgList(String method, List<String> args) {
    1037           0 :   for (var i = 1; i < args.length; i++) {
    1038             :     // Ignore nulls hanging off the end.
    1039           0 :     if (args[i] == null || args[i - 1] != null) continue;
    1040             : 
    1041             :     var numArgs;
    1042           0 :     for (numArgs = args.length; numArgs >= 1; numArgs--) {
    1043           0 :       if (args[numArgs - 1] != null) break;
    1044             :     }
    1045             : 
    1046             :     // Show the arguments.
    1047           0 :     var message = new StringBuffer();
    1048           0 :     message.write("$method(");
    1049           0 :     message.write(args
    1050           0 :         .take(numArgs)
    1051           0 :         .map((arg) => arg == null ? "null" : '"$arg"')
    1052           0 :         .join(", "));
    1053           0 :     message.write("): part ${i - 1} was null, but part $i was not.");
    1054           0 :     throw new ArgumentError(message.toString());
    1055             :   }
    1056             : }
    1057             : 
    1058             : /// An enum of possible return values for [Context._pathDirection].
    1059             : class _PathDirection {
    1060             :   /// The path contains enough ".." components that at some point it reaches
    1061             :   /// above its original root.
    1062             :   ///
    1063             :   /// Note that this applies even if the path ends beneath its original root. It
    1064             :   /// takes precendence over any other return values that may apple.
    1065             :   static const aboveRoot = const _PathDirection("above root");
    1066             : 
    1067             :   /// The path contains enough ".." components that it ends at its original
    1068             :   /// root.
    1069             :   static const atRoot = const _PathDirection("at root");
    1070             : 
    1071             :   /// The path contains enough ".." components that at some point it reaches its
    1072             :   /// original root, but it ends beneath that root.
    1073             :   static const reachesRoot = const _PathDirection("reaches root");
    1074             : 
    1075             :   /// The path never reaches to or above its original root.
    1076             :   static const belowRoot = const _PathDirection("below root");
    1077             : 
    1078             :   final String name;
    1079             : 
    1080           0 :   const _PathDirection(this.name);
    1081             : 
    1082           0 :   String toString() => name;
    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           5 :   const _PathRelation(this.name);
    1109             : 
    1110           0 :   String toString() => name;
    1111             : }
    1112             : 

Generated by: LCOV version 1.13