EDIT:
The expanduser
crate probably does everything you want, including expansion of ~user
. An example (adapted from the documentation):
use expanduser::expanduser;
let path = expanduser("~foolmeonce/path/to/directory")?;
assert_eq!(path.display().to_string(), "/home/foolmeonce/path/to/directory");
let path = expanduser("~/path/to/directory")?;
assert_eq!(path.display().to_string(), "/home/foolmetwice/path/to/directory");
ORIGINAL ANSWER:
Here's an implementation returning a Cow<Path>
, so that we only allocate if there's actually a tilde prefix in the path:
use std::{borrow::Cow, path::Path};
use directories::UserDirs;
use lazy_static::lazy_static;
fn expand_home_dir<'a, P: AsRef<Path> + ?Sized>(path: &'a P) -> Cow<'a, Path> {
let path = path.as_ref();
if !path.starts_with("~") {
return path.into();
}
lazy_static! {
static ref HOME_DIR: &'static Path = UserDirs::new().unwrap().home_dir();
}
HOME_DIR.join(path.strip_prefix("~").unwrap()).into()
}
Things to notice:
- The home directory is retrieved at most once.
- The only
unwrap
that could fail is the one in the lazy_static!
block, but there's no recovery from it.
- The only possible allocation happening is in
join
.
Some usage examples:
#[test]
fn test_expand_home_dir() {
lazy_static! {
static ref HOME_DIR: String = std::env::var("HOME").unwrap();
}
// Simple prefix expansion.
assert_eq!(
expand_home_dir("~/a/path/to/a/file"),
Path::new(&format!("{}/a/path/to/a/file", &*HOME_DIR))
);
// Lone tilde is user's home directory.
assert_eq!(expand_home_dir("~"), Path::new(&*HOME_DIR));
// Tilde in the middle of a path should not be expanded.
assert_eq!(
expand_home_dir("/a/~/path/to/a/file"),
Path::new("/a/~/path/to/a/file")
);
// No tilde, no expansion in absolute paths.
assert_eq!(
expand_home_dir("/a/path/to/a/file"),
Path::new("/a/path/to/a/file")
);
// No tilde, no expansion in relative paths.
assert_eq!(
expand_home_dir("another/path/to/a/file"),
Path::new("another/path/to/a/file")
);
}