relative method
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();
}