use std::{
collections::HashSet,
fmt::Display,
path::{Path, PathBuf},
};
use once_cell::sync::Lazy;
static HOMEDIRS: Lazy<Vec<PathBuf>> = Lazy::new(default_homedirs);
fn default_homedirs() -> Vec<PathBuf> {
if let Some(basic_home) = dirs::home_dir() {
let mut homedirs = HashSet::new();
homedirs.insert(basic_home.clone());
if let Ok(canonical) = std::fs::canonicalize(&basic_home) {
homedirs.insert(canonical);
}
if let Ok(rp) = crate::walk::ResolvePath::new(basic_home) {
let (mut p, rest) = rp.into_result();
p.extend(rest);
homedirs.insert(p);
}
homedirs.into_iter().collect()
} else {
vec![]
}
}
const HOME_SUBSTITUTION: &str = {
if cfg!(target_family = "windows") {
"%UserProfile%"
} else {
"${HOME}"
}
};
pub trait PathExt {
fn anonymize_home(&self) -> AnonHomePath<'_>;
}
impl PathExt for Path {
fn anonymize_home(&self) -> AnonHomePath<'_> {
AnonHomePath(self)
}
}
#[derive(Debug, Clone)]
pub struct AnonHomePath<'a>(&'a Path);
impl<'a> std::fmt::Display for AnonHomePath<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[allow(clippy::disallowed_methods)]
fn display_lossy(p: &Path) -> impl Display + '_ {
p.display()
}
for home in HOMEDIRS.iter() {
if let Ok(suffix) = self.0.strip_prefix(home) {
return write!(
f,
"{}{}{}",
HOME_SUBSTITUTION,
std::path::MAIN_SEPARATOR,
display_lossy(suffix),
);
}
}
display_lossy(self.0).fmt(f)
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
#[test]
fn no_change() {
let path = PathBuf::from("/completely/untoucha8le");
assert_eq!(path.anonymize_home().to_string(), path.to_string_lossy());
}
fn check_with_home(homedir: &Path) {
let arti_conf = homedir.join("here").join("is").join("a").join("path");
#[cfg(target_family = "windows")]
assert_eq!(
arti_conf.anonymize_home().to_string(),
"%UserProfile%\\here\\is\\a\\path"
);
#[cfg(not(target_family = "windows"))]
assert_eq!(
arti_conf.anonymize_home().to_string(),
"${HOME}/here/is/a/path"
);
}
#[test]
fn in_home() {
if let Some(home) = dirs::home_dir() {
check_with_home(&home);
}
}
#[test]
fn in_canonical_home() {
if let Some(canonical_home) = dirs::home_dir()
.map(std::fs::canonicalize)
.transpose()
.ok()
.flatten()
{
check_with_home(&canonical_home);
}
}
}