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

Viewed 199

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 Answers

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.

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(())))
Related