-
The most idiomatic way would be to just use an existing crate, in this case
shellexpand(github, crates.io) seems to do what you want:extern crate shellexpand; // 1.0.0 #[test] fn test_shellexpand() { let home = std::env::var("HOME").unwrap(); assert_eq!(shellexpand::tilde("~/foo"), format!("{}/foo", home)); } -
Alternatively, you could try it with
dirs(crates.io). Here is a sketch:extern crate dirs; // 1.0.4 use std::path::{Path, PathBuf}; fn expand_tilde<P: AsRef<Path>>(path_user_input: P) -> Option<PathBuf> { let p = path_user_input.as_ref(); if !p.starts_with("~") { return Some(p.to_path_buf()); } if p == Path::new("~") { return dirs::home_dir(); } dirs::home_dir().map(|mut h| { if h == Path::new("https://stackoverflow.com/") { // Corner case: `h` root directory; // don't prepend extra `/`, just drop the tilde. p.strip_prefix("~").unwrap().to_path_buf() } else { h.push(p.strip_prefix("~/").unwrap()); h } }) }Usage examples:
#[test] fn test_expand_tilde() { // Should work on your linux box during tests, would fail in stranger // environments! let home = std::env::var("HOME").unwrap(); let projects = PathBuf::from(format!("{}/Projects", home)); assert_eq!(expand_tilde("~/Projects"), Some(projects)); assert_eq!(expand_tilde("/foo/bar"), Some("/foo/bar".into())); assert_eq!( expand_tilde("~alice/projects"), Some("~alice/projects".into()) ); }Some remarks:
- The
P: AsRef<Path>input type imitates what the standard
library does. This is why the method accepts allPath-like
inputs, like&str,&OsStr, and&Path. Path::newdoesn’t allocate anything, it points to
exactly the same bytes as the&str.strip_prefix("~/").unwrap()should never fail here,
because we checked that the path starts with~and
is not just~. The only way how this can be is that
the path starts with~/(because of howstarts_with
is defined).
- The