How do I change a &Path's first ancestor in Rust?

686 Views Asked by At

I want to change the first ancestor of a &Path, possibly by returning a new PathBuf like so.

fn change_parent(path: &Path, new_parent: &Path) -> PathBuf;

Here's how I expect it to work.

let old_path = change_root(Path::new("/hello/world.html"), Path::new("/html"));
let new_path = PathBuf::from("/html/world.html");
assert_eq!(old_path, new_path)

I tried .ancestors() but the Path::new("/hello/world.html").ancestors(); returns an iterator over ["/hello/world.html", "/hello", "/"] which is kind of the opposite of what I want. What I want is ["/hello/world.html", "hello/world.html", "/world.html"] so I can append a new path easily.

Should I switch to using split on "(/|\)" then replacing the first result instead?

2

There are 2 best solutions below

0
Ecumene On BEST ANSWER

This is using strip_prefix provided by conradludgate on The Rust Programming Language Community (Discord):

use std::path::{Path, PathBuf, StripPrefixError}; 

fn replace_prefix(p: impl AsRef<Path>, from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<PathBuf, StripPrefixError> {
  p.as_ref().strip_prefix(from).map(|p| to.as_ref().join(p))
}

(
  replace_prefix("/public/file.html", "/public", "/.html_output"),
  replace_prefix("/public/folder/nested/file.html", "/public", "/.html_output"),
  replace_prefix("/folder/nested/file.html", "/public", "/.html_output"),
)
(Ok("/.html_output/file.html"), Ok("/.html_output/folder/nested/file.html"), Err(StripPrefixError(())))
1
egerhard On

Using Path::components I was able to come up with this solution:

use std::path::{Component, Path};
use std::{ffi::OsString, path::PathBuf};

let path = Path::new("/tmp/foo.txt");
let mut b: Vec<_> = path.components().collect();

// b[0] is the root directory "/" and b[1] is the
// "tmp" directory.
let html = OsString::from("html");
b[1] = Component::Normal(&html);

let pb: PathBuf = b.iter().collect();

I couldn't find a simpler way to do this. The Path and PathBuf implementations are both based on a single OsString. So, with both types, to get access to individual components in the path, the underlying OsString would need to be split by something and components is the way to do the splitting. So, I don't think there's really another solution.