1

The reason I'm interested in doing this is because there's a part of my path which will remain constant but which I wish to remove along with all its parent pieces.

So if we have say,

some/unknown/path/foo/bar/baz

I would like to return

bar/baz

But with the expectation that I only know foo/... directly proceeds the part of the path I care about.

Perhaps strip_prefix is the wrong approach so if there's a better way of doing this I would certainly appreciate being pointed in that direction.

maxcountryman
  • 1,562
  • 1
  • 24
  • 51

2 Answers2

6

strip_prefix won't do what you want, because it requires that you know the prefix to strip. However, you can use iter to get an iterator over path components, and then use standard Iterator methods to build a new PathBuf out of only the part you want.

Here's an example (try it):

let p = path::Path::new("some/unknown/path/foo/bar/baz");
let q: path::PathBuf = p.iter()   // iterate over path components
    .skip_while(|s| *s != "foo")  // skip everything before "foo"
    .skip(1)                      // skip "foo" itself
    .collect();                   // collect the rest into a PathBuf
println!("{:?}", q); // prints "bar/baz"

(This will allocate a new PathBuf. Shepmaster's answer shows how to get a &Path referencing the original without allocating.)

You can then use to_str, to_string_lossy, or into_os_string plus OsString::into_string to get something that can be turned into a String.

See also: How to convert the PathBuf to String

trent
  • 25,033
  • 7
  • 51
  • 90
5

While I think that trentcl's answer is cleaner, it's worth showing some non-allocating versions.

Using Path::strip_prefix

To use Path::strip_prefix, you need to know the prefix. We can generate it by walking up the parents of the original path until we find one that ends_with "foo".

use std::path::Path;

fn thing1<P>(path: &P) -> Result<&Path, ()>
where
    P: AsRef<Path> + ?Sized,
{
    let original = path.as_ref();
    let mut prefix = original;

    while !prefix.ends_with("foo") {
        prefix = match prefix.parent() {
            Some(p) => p,
            None => return Err(()),
        };
    }

    original.strip_prefix(prefix).map_err(|_| ())
}

fn main() {
    let x = thing1("some/unknown/path/foo/bar/baz");
    println!("{:?}", x);
}

With an iterator

We can iterate over the pieces of the path, taking values while it's not "foo". Once we've advanced the iterator enough, we can get the remainder as a path.

use std::path::Path;

fn thing2<P>(path: &P) -> &Path
where
    P: AsRef<Path> + ?Sized,
{
    let path = path.as_ref();
    let mut i = path.iter();

    for _ in i.by_ref().take_while(|c| *c != "foo") {}

    i.as_path()
}

fn main() {
    let x = thing2("some/unknown/path/foo/bar/baz");
    println!("{:?}", x);
}

This returns an empty path when "foo" doesn't exist.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366