relative method

String relative (String path, { String from })

Attempts to convert path to an equivalent relative path relative to root.

var context = new Context(current: '/root/path');
context.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
context.relative('/root/other.dart'); // -> '../other.dart'

If the from argument is passed, path is made relative to that instead.

context.relative('/root/path/a/b.dart',
    from: '/root/path'); // -> 'a/b.dart'
context.relative('/root/other.dart',
    from: '/root/path'); // -> '../other.dart'

If path and/or from are relative paths, they are assumed to be relative to current.

Since there is no relative path from one drive letter to another on Windows, this will return an absolute path in that case.

context.relative(r'D:\other', from: r'C:\other'); // -> 'D:\other'

This will also return an absolute path if an absolute path is passed to a context with a relative path for current.

var context = new Context(r'some/relative/path');
context.relative(r'/absolute/path'); // -> '/absolute/path'

If root is relative, it may be impossible to determine a path from from to path. For example, if root and path are "." and from is "/", no path can be determined. In this case, a PathException will be thrown.

Implementation

String relative(String path, {String from}) {
  // Avoid expensive computation if the path is already relative.
  if (from == null && this.isRelative(path)) return this.normalize(path);

  from = from == null ? current : absolute(from);

  // We can't determine the path from a relative path to an absolute path.
  if (this.isRelative(from) && this.isAbsolute(path)) {
    return this.normalize(path);
  }

  // If the given path is relative, resolve it relative to the context's
  // current directory.
  if (this.isRelative(path) || this.isRootRelative(path)) {
    path = this.absolute(path);
  }

  // If the path is still relative and `from` is absolute, we're unable to
  // find a path from `from` to `path`.
  if (this.isRelative(path) && this.isAbsolute(from)) {
    throw new PathException('Unable to find a path to "$path" from "$from".');
  }

  var fromParsed = _parse(from)..normalize();
  var pathParsed = _parse(path)..normalize();

  if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') {
    return pathParsed.toString();
  }

  // If the root prefixes don't match (for example, different drive letters
  // on Windows), then there is no relative path, so just return the absolute
  // one. In Windows, drive letters are case-insenstive and we allow
  // calculation of relative paths, even if a path has not been normalized.
  if (fromParsed.root != pathParsed.root &&
      ((fromParsed.root == null || pathParsed.root == null) ||
          !style.pathsEqual(fromParsed.root, pathParsed.root))) {
    return pathParsed.toString();
  }

  // Strip off their common prefix.
  while (fromParsed.parts.length > 0 &&
      pathParsed.parts.length > 0 &&
      style.pathsEqual(fromParsed.parts[0], pathParsed.parts[0])) {
    fromParsed.parts.removeAt(0);
    fromParsed.separators.removeAt(1);
    pathParsed.parts.removeAt(0);
    pathParsed.separators.removeAt(1);
  }

  // If there are any directories left in the from path, we need to walk up
  // out of them. If a directory left in the from path is '..', it cannot
  // be cancelled by adding a '..'.
  if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') {
    throw new PathException('Unable to find a path to "$path" from "$from".');
  }
  pathParsed.parts
      .insertAll(0, new List.filled(fromParsed.parts.length, '..'));
  pathParsed.separators[0] = '';
  pathParsed.separators.insertAll(
      1, new List.filled(fromParsed.parts.length, style.separator));

  // Corner case: the paths completely collapsed.
  if (pathParsed.parts.length == 0) return '.';

  // Corner case: path was '.' and some '..' directories were added in front.
  // Don't add a final '/.' in that case.
  if (pathParsed.parts.length > 1 && pathParsed.parts.last == '.') {
    pathParsed.parts.removeLast();
    pathParsed.separators
      ..removeLast()
      ..removeLast()
      ..add('');
  }

  // Make it relative.
  pathParsed.root = '';
  pathParsed.removeTrailingSeparators();

  return pathParsed.toString();
}