1use std::fmt;
4use std::path::PathBuf;
5
6pub fn expand_home(path: &str) -> Result<PathBuf, NoHomeFolder> {
11 let Some(rest) = path.strip_prefix('~') else {
12 return Ok(PathBuf::from(path));
13 };
14 if !rest.is_empty() && !rest.starts_with(std::path::is_separator) {
15 return Ok(PathBuf::from(path));
16 }
17 let home = std::env::home_dir().ok_or(NoHomeFolder)?;
18 Ok(home.join(rest.trim_start_matches(std::path::is_separator)))
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct NoHomeFolder;
23
24impl fmt::Display for NoHomeFolder {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 f.write_str("couldn't find your home folder for `~`")
27 }
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33
34 #[test]
35 fn a_leading_tilde_means_the_home_folder() {
36 let home = std::env::home_dir().expect("tests run with a home folder");
37 assert_eq!(expand_home("~").ok(), Some(home.clone()));
38 assert_eq!(
39 expand_home("~/Downloads").ok(),
40 Some(home.join("Downloads"))
41 );
42 assert_eq!(expand_home("~other").ok(), Some(PathBuf::from("~other")));
43 assert_eq!(expand_home("/tmp").ok(), Some(PathBuf::from("/tmp")));
44 }
45}