1#![deny(missing_debug_implementations, missing_docs)]
2
3use std::{
22 fmt,
23 path::{Path, PathBuf},
24};
25
26use std::borrow::Cow;
27
28pub trait PlainPathExt {
30 fn plain(&self) -> Result<Cow<'_, Path>, HomeDirNotFound>;
36}
37
38impl PlainPathExt for Path {
39 fn plain(&self) -> Result<Cow<'_, Path>, HomeDirNotFound> {
40 crate::plain(self)
41 }
42}
43
44impl PlainPathExt for PathBuf {
45 fn plain(&self) -> Result<Cow<'_, Path>, HomeDirNotFound> {
46 crate::plain(self)
47 }
48}
49
50pub fn plain(path: &Path) -> Result<Cow<'_, Path>, HomeDirNotFound> {
55 if path.starts_with("~") {
56 dirs::home_dir()
58 .map(|mut path_normalized| {
59 path_normalized.extend(path.into_iter().skip(1));
60 Cow::Owned(path_normalized)
61 })
62 .ok_or(HomeDirNotFound)
63 } else {
64 Ok(Cow::Borrowed(path))
65 }
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub struct HomeDirNotFound;
71
72impl fmt::Display for HomeDirNotFound {
73 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74 write!(f, "Failed to determine user's home directory.")
75 }
76}
77
78impl std::error::Error for HomeDirNotFound {}
79
80#[cfg(test)]
81mod tests {
82 use std::path::{Component, Path};
83
84 use super::{HomeDirNotFound, PlainPathExt};
85
86 #[test]
87 fn expands_tilde() -> Result<(), HomeDirNotFound> {
88 let path = Path::new("~/.ssh/config").plain()?;
89
90 let mut components = path.components();
91 assert_eq!(Some(Component::RootDir), components.next());
92
93 #[cfg(osx)]
94 assert_eq!(
95 Some(Component::Normal(OsStr::new("Users"))),
96 components.next()
97 );
98
99 #[cfg(linux)]
100 assert_eq!(
101 Some(Component::Normal(OsStr::new("home"))),
102 components.next()
103 );
104
105 Ok(())
106 }
107}