If one path is a base of another, you could use Path::strip_prefix
, but it won't calculate the ../
for you (instead returns an Err):
use std::path::*;
let base = Path::new("/foo/bar");
let child_a = Path::new("/foo/bar/a");
let child_b = Path::new("/foo/bar/b");
println!("{:?}", child_a.strip_prefix(base)); // Ok("a")
println!("{:?}", child_a.strip_prefix(child_b)); // Err(StripPrefixError(()))
The previous incarnation of strip_prefix
was path_relative_from
which used to add ../
, but this behavior was dropped due to symlinks:
- The current behavior where joining the result onto the first path unambiguously refers to the same thing the second path does, even if there's symlinks (which basically means
base
needs to be a prefix of self
)
- The old behavior where the result can start with
../
components. Symlinks mean traversing the base
path and then traversing the returned relative path may not put you in the same directory that traversing the self
path does. But this operation is useful when either you're working with a path-based system that doesn't care about symlinks, or you've already resolved symlinks in the paths you're working with.
If you need the ../
behavior, you could copy the implementation from librustc_back (the compiler backend). I didn't find any packages on crates.io providing this yet.
// This routine is adapted from the *old* Path's `path_relative_from`
// function, which works differently from the new `relative_from` function.
// In particular, this handles the case on unix where both paths are
// absolute but with only the root as the common directory.
fn path_relative_from(path: &Path, base: &Path) -> Option<PathBuf> {
use std::path::Component;
if path.is_absolute() != base.is_absolute() {
if path.is_absolute() {
Some(PathBuf::from(path))
} else {
None
}
} else {
let mut ita = path.components();
let mut itb = base.components();
let mut comps: Vec<Component> = vec![];
loop {
match (ita.next(), itb.next()) {
(None, None) => break,
(Some(a), None) => {
comps.push(a);
comps.extend(ita.by_ref());
break;
}
(None, _) => comps.push(Component::ParentDir),
(Some(a), Some(b)) if comps.is_empty() && a == b => (),
(Some(a), Some(b)) if b == Component::CurDir => comps.push(a),
(Some(_), Some(b)) if b == Component::ParentDir => return None,
(Some(a), Some(_)) => {
comps.push(Component::ParentDir);
for _ in itb {
comps.push(Component::ParentDir);
}
comps.push(a);
comps.extend(ita.by_ref());
break;
}
}
}
Some(comps.iter().map(|c| c.as_os_str()).collect())
}
}