grm/
path.rs

1use std::{
2    fmt,
3    path::{Path, PathBuf},
4};
5
6use thiserror::Error;
7
8#[derive(Debug)]
9pub struct EnvVariableName(String);
10
11impl fmt::Display for EnvVariableName {
12    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13        write!(f, "{}", self.0)
14    }
15}
16
17#[derive(Debug, Error)]
18pub enum Error {
19    #[error("found non-utf8 path: {:?}", .path)]
20    NonUtf8 { path: PathBuf },
21    #[error("failed getting env variable `{}`: {}", .variable, .error)]
22    Env {
23        variable: EnvVariableName,
24        error: String,
25    },
26    #[error("failed expanding path: {}", .error)]
27    Expand { error: String },
28}
29
30pub fn path_as_string(path: &Path) -> Result<String, Error> {
31    path.to_path_buf()
32        .into_os_string()
33        .into_string()
34        .map_err(|_s| Error::NonUtf8 {
35            path: path.to_path_buf(),
36        })
37}
38
39pub fn env_home() -> Result<PathBuf, Error> {
40    Ok(PathBuf::from(std::env::var("HOME").map_err(|e| {
41        Error::Env {
42            variable: EnvVariableName("HOME".to_owned()),
43            error: e.to_string(),
44        }
45    })?))
46}
47
48pub fn expand_path(path: &Path) -> Result<PathBuf, Error> {
49    let home = path_as_string(&env_home()?)?;
50    let expanded_path = match shellexpand::full_with_context(
51        &path_as_string(path)?,
52        || Some(home.clone()),
53        |name| -> Result<Option<String>, Error> {
54            match name {
55                "HOME" => Ok(Some(home.clone())),
56                _ => Ok(None),
57            }
58        },
59    ) {
60        Ok(std::borrow::Cow::Borrowed(path)) => path.to_owned(),
61        Ok(std::borrow::Cow::Owned(path)) => path,
62        Err(e) => {
63            return Err(Error::Expand {
64                error: e.cause.to_string(),
65            });
66        }
67    };
68
69    Ok(Path::new(&expanded_path).to_path_buf())
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn check_expand_tilde() -> Result<(), Error> {
78        temp_env::with_var("HOME", Some("/home/test"), || {
79            assert_eq!(
80                expand_path(Path::new("~/file"))?,
81                Path::new("/home/test/file")
82            );
83            Ok(())
84        })
85    }
86
87    #[test]
88    fn check_expand_invalid_tilde() -> Result<(), Error> {
89        temp_env::with_var("HOME", Some("/home/test"), || {
90            assert_eq!(
91                expand_path(Path::new("/home/~/file"))?,
92                Path::new("/home/~/file")
93            );
94            Ok(())
95        })
96    }
97
98    #[test]
99    fn check_expand_home() -> Result<(), Error> {
100        temp_env::with_var("HOME", Some("/home/test"), || {
101            assert_eq!(
102                expand_path(Path::new("$HOME/file"))?,
103                Path::new("/home/test/file")
104            );
105            assert_eq!(
106                expand_path(Path::new("${HOME}/file"))?,
107                Path::new("/home/test/file")
108            );
109            Ok(())
110        })
111    }
112}