use std::path::{Path, PathBuf};
pub(super) fn normalize_lexical_path(path: &Path) -> PathBuf {
let mut normalized = PathBuf::new();
for component in path.components() {
match component {
std::path::Component::CurDir => {}
std::path::Component::ParentDir => match normalized.components().next_back() {
Some(std::path::Component::Normal(_)) => {
normalized.pop();
}
Some(std::path::Component::ParentDir) | None => {
normalized.push(std::path::Component::ParentDir.as_os_str());
}
Some(std::path::Component::RootDir) | Some(std::path::Component::Prefix(_)) => {}
Some(std::path::Component::CurDir) => unreachable!(),
},
other => normalized.push(other.as_os_str()),
}
}
normalized
}
pub(super) fn scanned_path(path: &str) -> PathBuf {
normalize_lexical_path(Path::new(path))
}
pub(super) fn scanned_file_dir(path: &str) -> Option<PathBuf> {
Path::new(path).parent().map(normalize_lexical_path)
}
pub(super) fn strip_declared_dot_slash(pattern: &str) -> &str {
let trimmed = pattern.trim();
trimmed.strip_prefix("./").unwrap_or(trimmed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize_lexical_path_strips_curdir_keeps_unresolved_parent() {
assert_eq!(
normalize_lexical_path(Path::new(".")),
PathBuf::new(),
"`.` collapses to the empty relative root"
);
assert_eq!(
normalize_lexical_path(Path::new("./module-a")),
PathBuf::from("module-a")
);
assert_eq!(
normalize_lexical_path(Path::new("../../../module-a")),
PathBuf::from("../../../module-a"),
"unresolved `..` must be preserved so over-escaped modules do not collapse"
);
}
#[test]
fn scanned_path_matches_dot_prefixed_fileinfo() {
assert_eq!(
scanned_path("./module-a/pom.xml"),
PathBuf::from("module-a/pom.xml")
);
assert_eq!(
scanned_path("module-a/pom.xml"),
PathBuf::from("module-a/pom.xml")
);
}
#[test]
fn scanned_file_dir_handles_dot_root_and_empty_parent() {
assert_eq!(
scanned_file_dir("./pom.xml").as_deref(),
Some(Path::new("")),
"parent of `./pom.xml` is `.`, which normalizes to empty"
);
assert_eq!(
scanned_file_dir("pom.xml").as_deref(),
Some(Path::new("")),
"parent of a root-level file is already empty"
);
assert_eq!(
scanned_file_dir("./module-a/src/Foo.java").as_deref(),
Some(Path::new("module-a/src"))
);
}
#[test]
fn strip_declared_dot_slash_trims_and_strips_one_prefix() {
assert_eq!(strip_declared_dot_slash(" ./packages/* "), "packages/*");
assert_eq!(strip_declared_dot_slash("crates/foo"), "crates/foo");
assert_eq!(
strip_declared_dot_slash("././nested"),
"./nested",
"only one leading `./` is stripped"
);
}
}