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}