Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Given two absolute paths, how can I express one of the paths relative to the other?

Tags:

path

rust

I think this should be quite doable, given that there is a nice function canonicalize which normalizes paths (so I can start by normalizing my two input paths) and Path and PathBuf give us a way of iterating over the parts of paths through components. I imagine something could be worked out here to factor out a common prefix, then prepend as many .. components as remain in the anchor path to what remains of the initial input path.

My problem seems to be pretty common:

  1. How to find relative path given two absolute paths?
  2. Find a path in Windows relative to another
like image 898
Alec Avatar asked Sep 06 '16 04:09

Alec


People also ask

How do you convert an absolute path to a relative path?

The absolutePath function works by beginning at the starting folder and moving up one level for each "../" in the relative path. Then it concatenates the changed starting folder with the relative path to produce the equivalent absolute path.

Can an absolute path be a relative path?

In simple words, an absolute path refers to the same location in a file system relative to the root directory, whereas a relative path points to a specific location in a file system relative to the current directory you are working on.

How do you represent absolute path?

An absolute path is the full path to a file or directory. It is relative to the root directory ( / ). Note that it is a best practice to use absolute paths when you use file paths inside of scripts. For example, the absolute path to the ls command is: /usr/bin/ls .


2 Answers

This now exists as the pathdiff crate, using the code from kennytm's answer

You can use it as:

extern crate pathdiff;

pathdiff::diff_paths(path, base);

where base is where the relative path should be applied to obtain path

like image 171
Manishearth Avatar answered Oct 06 '22 01:10

Manishearth


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:

  1. 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)
  2. 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())
    }
}
like image 24
kennytm Avatar answered Oct 06 '22 00:10

kennytm