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}