use std::path::{Component, Path, PathBuf};
pub fn normalize_lexical(p: &Path) -> PathBuf {
#[cfg(windows)]
let p = &strip_verbatim_prefix(p);
let mut out = PathBuf::new();
for comp in p.components() {
match comp {
Component::ParentDir => {
out.pop();
}
Component::CurDir => {}
other => out.push(other.as_os_str()),
}
}
out
}
#[cfg(windows)]
fn strip_verbatim_prefix(p: &Path) -> PathBuf {
let text = p.to_string_lossy();
if let Some(rest) = text.strip_prefix(r"\\?\UNC\") {
return PathBuf::from(format!(r"\\{rest}"));
}
match text.strip_prefix(r"\\?\") {
Some(rest) => PathBuf::from(rest),
None => p.to_path_buf(),
}
}
pub(crate) fn canonicalize_lenient(p: &Path) -> PathBuf {
let mut tail = PathBuf::new();
let mut cur = p.to_path_buf();
loop {
if let Ok(resolved) = cur.canonicalize() {
return resolved.join(&tail);
}
match (cur.parent(), cur.file_name()) {
(Some(parent), Some(name)) => {
tail = PathBuf::from(name).join(&tail);
cur = parent.to_path_buf();
}
_ => return p.to_path_buf(),
}
}
}
pub(crate) fn path_contains(base: &Path, target: &Path) -> bool {
let base_abs = canonicalize_lenient(base);
let target_abs = canonicalize_lenient(target);
target_abs == base_abs || target_abs.starts_with(&base_abs)
}
pub(crate) fn same_path(a: &Path, b: &Path) -> bool {
match (a.canonicalize(), b.canonicalize()) {
(Ok(x), Ok(y)) => x == y,
_ => normalize_lexical(a) == normalize_lexical(b),
}
}