1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use std::env;
use std::path::{Component, Path, PathBuf};
use nix::unistd::{Uid, User};
#[cfg(test)]
mod tests;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("the user does not have entry")]
MissingEntry,
}
pub trait HomeDirExt {
fn expand_home(&self) -> Result<PathBuf, Error>;
}
impl HomeDirExt for Path {
fn expand_home(&self) -> Result<PathBuf, Error> {
let mut path = PathBuf::new();
let mut comps = self.components();
match comps.next() {
Some(Component::Normal(os)) => {
if let Some(s) = os.to_str() {
match s {
"~" => {
let p = getenv()
.ok_or(Error::MissingEntry)
.or_else(|_| getent_current())?;
path.push(p);
}
s if s.starts_with('~') => {
path.push(getent(&s[1..])?);
}
s => path.push(s),
}
} else {
path.push(os)
}
}
Some(comp) => path.push(comp),
None => return Ok(path),
};
for comp in comps {
path.push(comp);
}
Ok(path)
}
}
impl<T> HomeDirExt for T
where
T: AsRef<Path>,
{
fn expand_home(&self) -> Result<PathBuf, Error> {
self.as_ref().expand_home()
}
}
pub(crate) fn getent(name: &str) -> Result<PathBuf, Error> {
let usr = User::from_name(name).or(Err(Error::MissingEntry))?;
let usr = usr.ok_or_else(|| Error::MissingEntry)?;
Ok(usr.dir)
}
pub(crate) fn getenv() -> Option<PathBuf> {
env::var("HOME").ok().map(Into::into)
}
pub(crate) fn getent_current() -> Result<PathBuf, Error> {
let usr = User::from_uid(Uid::current()).or(Err(Error::MissingEntry))?;
let usr = usr.ok_or_else(|| Error::MissingEntry)?;
Ok(usr.dir)
}