use crate::error::PathError;
use crate::internal::components::is_drive_relative_path;
use crate::internal::validation::reject_nul_path;
use std::path::{Component, Path, PathBuf};
pub fn normalize(path: impl AsRef<Path>) -> Result<PathBuf, PathError> {
let path = path.as_ref();
reject_nul_path(path)?;
if path.as_os_str().is_empty() {
return Err(PathError::EmptyInput);
}
if is_drive_relative_path(path) {
return Err(PathError::drive_relative(path));
}
let mut out = PathBuf::new();
let mut absolute = false;
for component in path.components() {
match component {
Component::Prefix(prefix) => {
out.push(prefix.as_os_str());
}
Component::RootDir => {
out.push(component.as_os_str());
absolute = true;
}
Component::CurDir => {
}
Component::ParentDir => {
match out.components().next_back() {
None => {
if !absolute {
out.push("..");
}
}
Some(Component::ParentDir) => {
out.push("..");
}
Some(Component::RootDir) | Some(Component::Prefix(_)) => {
}
Some(Component::Normal(_)) => {
out.pop();
}
Some(Component::CurDir) => {
out.pop();
out.push("..");
}
}
}
Component::Normal(c) => {
out.push(c);
}
}
}
if out.as_os_str().is_empty() {
if absolute {
out.push(Component::RootDir.as_os_str());
} else {
out.push(".");
}
}
Ok(out)
}
pub fn canonicalize_existing(path: impl AsRef<Path>) -> Result<PathBuf, PathError> {
let path = path.as_ref();
reject_nul_path(path)?;
if is_drive_relative_path(path) {
return Err(PathError::drive_relative(path));
}
std::fs::canonicalize(path).map_err(|e| PathError::filesystem(path, e))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn collapses_dot_and_parent() {
assert_eq!(
normalize("foo/./bar/../baz").unwrap(),
PathBuf::from("foo/baz")
);
assert_eq!(normalize("./foo").unwrap(), PathBuf::from("foo"));
assert_eq!(normalize("foo//bar").unwrap(), PathBuf::from("foo/bar"));
}
#[test]
fn preserves_leading_parent_on_relative() {
assert_eq!(normalize("../foo").unwrap(), PathBuf::from("../foo"));
assert_eq!(normalize("foo/../../bar").unwrap(), PathBuf::from("../bar"));
}
#[test]
fn empty_rejected() {
assert!(matches!(normalize(""), Err(PathError::EmptyInput)));
}
}