1use std::{
9 collections::HashSet,
10 fmt::Display,
11 path::{Path, PathBuf},
12};
13
14use extend::ext;
15use std::sync::LazyLock;
16
17static HOMEDIRS: LazyLock<Vec<PathBuf>> = LazyLock::new(default_homedirs);
19
20fn default_homedirs() -> Vec<PathBuf> {
22 if let Some(basic_home) = dirs::home_dir() {
23 let mut homedirs = HashSet::new();
25
26 homedirs.insert(basic_home.clone());
28 if let Ok(canonical) = std::fs::canonicalize(&basic_home) {
30 homedirs.insert(canonical);
31 }
32 if let Ok(rp) = crate::walk::ResolvePath::new(basic_home) {
34 let (mut p, rest) = rp.into_result();
35 p.extend(rest);
36 homedirs.insert(p);
37 }
38
39 homedirs.into_iter().collect()
40 } else {
41 vec![]
42 }
43}
44
45const HOME_SUBSTITUTION: &str = {
47 if cfg!(target_family = "windows") {
48 "%UserProfile%"
49 } else {
50 "${HOME}"
51 }
52};
53
54#[ext]
56pub impl Path {
57 fn anonymize_home(&self) -> AnonHomePath<'_> {
75 AnonHomePath(self)
76 }
77}
78
79#[derive(Debug, Clone)]
82pub struct AnonHomePath<'a>(&'a Path);
83
84impl<'a> std::fmt::Display for AnonHomePath<'a> {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 #[allow(clippy::disallowed_methods)]
88 fn display_lossy(p: &Path) -> impl Display + '_ {
89 p.display()
90 }
91
92 for home in HOMEDIRS.iter() {
96 if let Ok(suffix) = self.0.strip_prefix(home) {
97 return write!(
98 f,
99 "{}{}{}",
100 HOME_SUBSTITUTION,
101 std::path::MAIN_SEPARATOR,
102 display_lossy(suffix),
103 );
104 }
105 }
106
107 display_lossy(self.0).fmt(f)
110 }
111}
112
113#[cfg(test)]
114mod test {
115 #![allow(clippy::bool_assert_comparison)]
117 #![allow(clippy::clone_on_copy)]
118 #![allow(clippy::dbg_macro)]
119 #![allow(clippy::mixed_attributes_style)]
120 #![allow(clippy::print_stderr)]
121 #![allow(clippy::print_stdout)]
122 #![allow(clippy::single_char_pattern)]
123 #![allow(clippy::unwrap_used)]
124 #![allow(clippy::unchecked_time_subtraction)]
125 #![allow(clippy::useless_vec)]
126 #![allow(clippy::needless_pass_by_value)]
127 #![allow(clippy::string_slice)] use super::*;
130
131 #[test]
132 fn no_change() {
133 let path = PathBuf::from("/completely/untoucha8le");
135 assert_eq!(path.anonymize_home().to_string(), path.to_string_lossy());
136 }
137
138 fn check_with_home(homedir: &Path) {
139 let arti_conf = homedir.join("here").join("is").join("a").join("path");
140
141 #[cfg(target_family = "windows")]
142 assert_eq!(
143 arti_conf.anonymize_home().to_string(),
144 "%UserProfile%\\here\\is\\a\\path"
145 );
146
147 #[cfg(not(target_family = "windows"))]
148 assert_eq!(
149 arti_conf.anonymize_home().to_string(),
150 "${HOME}/here/is/a/path"
151 );
152 }
153
154 #[test]
155 fn in_home() {
156 if let Some(home) = dirs::home_dir() {
157 check_with_home(&home);
158 }
159 }
160
161 #[test]
162 fn in_canonical_home() {
163 if let Some(canonical_home) = dirs::home_dir()
164 .map(std::fs::canonicalize)
165 .transpose()
166 .ok()
167 .flatten()
168 {
169 check_with_home(&canonical_home);
170 }
171 }
172}