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