Skip to main content

grm/
path.rs

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