use std::path::{Path, PathBuf};
const VERBATIM: &str = r"\\?\";
const VERBATIM_UNC: &str = r"\\?\UNC\";
pub fn display_path(path: &Path) -> String {
strip_verbatim(&path.to_string_lossy())
}
fn strip_verbatim(raw: &str) -> String {
if let Some(rest) = raw.strip_prefix(VERBATIM_UNC) {
return format!(r"\\{rest}");
}
match raw.strip_prefix(VERBATIM) {
Some(rest) => rest.to_string(),
None => raw.to_string(),
}
}
pub fn dedup_key(path: &Path) -> PathBuf {
key_with(path, cfg!(windows))
}
fn key_with(path: &Path, case_insensitive: bool) -> PathBuf {
let stripped = strip_verbatim(&path.to_string_lossy());
if case_insensitive {
PathBuf::from(stripped.to_lowercase())
} else {
PathBuf::from(stripped)
}
}
pub fn env_name_eq(a: &str, b: &str) -> bool {
if cfg!(windows) {
a.eq_ignore_ascii_case(b)
} else {
a == b
}
}
pub fn home_normalized(path: &str, home: Option<&str>) -> String {
let Some(home) = home.filter(|home| !home.is_empty() && *home != "/") else {
return path.to_string();
};
match path.strip_prefix(home) {
Some("") => "~".to_string(),
Some(rest) if rest.starts_with('/') || rest.starts_with('\\') => format!("~{rest}"),
_ => path.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_strips_the_verbatim_prefix() {
assert_eq!(
display_path(Path::new(r"\\?\C:\Users\me\.claude\settings.json")),
r"C:\Users\me\.claude\settings.json"
);
}
#[test]
fn display_rewrites_verbatim_unc_to_its_plain_form() {
assert_eq!(
display_path(Path::new(r"\\?\UNC\server\share\settings.json")),
r"\\server\share\settings.json"
);
}
#[test]
fn display_leaves_ordinary_paths_alone() {
assert_eq!(
display_path(Path::new("/home/me/.claude/settings.json")),
"/home/me/.claude/settings.json"
);
assert_eq!(
display_path(Path::new(r"C:\Users\me\settings.json")),
r"C:\Users\me\settings.json"
);
}
#[test]
fn case_insensitive_key_folds_case_and_prefix() {
let watcher = Path::new(r"C:\Users\Me\.claude\Settings.json");
let hook = Path::new(r"\\?\c:\users\me\.claude\settings.json");
assert_eq!(
key_with(watcher, true),
key_with(hook, true),
"same file reported in two casings must produce one key"
);
}
#[test]
fn case_sensitive_key_keeps_distinct_paths_distinct() {
let lower = Path::new("/home/me/settings.json");
let upper = Path::new("/home/Me/settings.json");
assert_ne!(
key_with(lower, false),
key_with(upper, false),
"POSIX casing is significant and must not be folded"
);
}
#[test]
fn dedup_key_matches_this_platform() {
let path = Path::new("Settings.json");
let expected = key_with(path, cfg!(windows));
assert_eq!(dedup_key(path), expected);
}
#[test]
fn home_is_written_as_a_tilde_and_only_at_a_path_boundary() {
assert_eq!(
home_normalized("/home/dev/.ssh/id_rsa", Some("/home/dev")),
"~/.ssh/id_rsa"
);
assert_eq!(home_normalized("/home/dev", Some("/home/dev")), "~");
assert_eq!(
home_normalized("/home/devops/x", Some("/home/dev")),
"/home/devops/x",
"a sibling directory is not the home"
);
assert_eq!(
home_normalized("/etc/passwd", Some("/home/dev")),
"/etc/passwd"
);
assert_eq!(home_normalized("/home/dev/x", None), "/home/dev/x");
assert_eq!(
home_normalized("/x", Some("/")),
"/x",
"a root home would rewrite everything"
);
}
}