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}