in lib/src/context.dart [467:546]
String relative(String path, {String? from}) {
// Avoid expensive computation if the path is already relative.
if (from == null && isRelative(path)) return normalize(path);
from = from == null ? current : absolute(from);
// We can't determine the path from a relative path to an absolute path.
if (isRelative(from) && isAbsolute(path)) {
return normalize(path);
}
// If the given path is relative, resolve it relative to the context's
// current directory.
if (isRelative(path) || isRootRelative(path)) {
path = absolute(path);
}
// If the path is still relative and `from` is absolute, we're unable to
// find a path from `from` to `path`.
if (isRelative(path) && isAbsolute(from)) {
throw PathException('Unable to find a path to "$path" from "$from".');
}
final fromParsed = _parse(from)..normalize();
final pathParsed = _parse(path)..normalize();
if (fromParsed.parts.isNotEmpty && 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.isNotEmpty &&
pathParsed.parts.isNotEmpty &&
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.isNotEmpty && fromParsed.parts[0] == '..') {
throw PathException('Unable to find a path to "$path" from "$from".');
}
pathParsed.parts.insertAll(0, List.filled(fromParsed.parts.length, '..'));
pathParsed.separators[0] = '';
pathParsed.separators
.insertAll(1, List.filled(fromParsed.parts.length, style.separator));
// Corner case: the paths completely collapsed.
if (pathParsed.parts.isEmpty) 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();
}