#![forbid(unsafe_code)]
use std::path::{Component, Path, PathBuf};
pub trait PathClean {
fn clean(&self) -> PathBuf;
}
impl PathClean for Path {
fn clean(&self) -> PathBuf {
clean(self)
}
}
impl PathClean for PathBuf {
fn clean(&self) -> PathBuf {
clean(self)
}
}
pub fn clean<P>(path: P) -> PathBuf
where
P: AsRef<Path>,
{
let mut out = Vec::new();
for comp in path.as_ref().components() {
match comp {
Component::CurDir => (),
Component::ParentDir => match out.last() {
Some(Component::RootDir) => (),
Some(Component::Normal(_)) => {
out.pop();
}
None
| Some(Component::CurDir)
| Some(Component::ParentDir)
| Some(Component::Prefix(_)) => out.push(comp),
},
comp => out.push(comp),
}
}
if !out.is_empty() {
out.iter().collect()
} else {
PathBuf::from(".")
}
}
#[cfg(test)]
mod tests {
use super::{clean, PathClean};
use std::path::{Path, PathBuf};
#[test]
fn test_empty_path_is_current_dir() {
assert_eq!(clean(""), PathBuf::from("."));
}
#[test]
fn test_clean_paths_dont_change() {
let tests = vec![(".", "."), ("..", ".."), ("/", "/")];
for test in tests {
assert_eq!(clean(test.0), PathBuf::from(test.1));
}
}
#[test]
fn test_replace_multiple_slashes() {
let tests = vec![
("/", "/"),
("//", "/"),
("///", "/"),
(".//", "."),
("//..", "/"),
("..//", ".."),
("/..//", "/"),
("/.//./", "/"),
("././/./", "."),
("path//to///thing", "path/to/thing"),
("/path//to///thing", "/path/to/thing"),
];
for test in tests {
assert_eq!(clean(test.0), PathBuf::from(test.1));
}
}
#[test]
fn test_eliminate_current_dir() {
let tests = vec![
("./", "."),
("/./", "/"),
("./test", "test"),
("./test/./path", "test/path"),
("/test/./path/", "/test/path"),
("test/path/.", "test/path"),
];
for test in tests {
assert_eq!(clean(test.0), PathBuf::from(test.1));
}
}
#[test]
fn test_eliminate_parent_dir() {
let tests = vec![
("/..", "/"),
("/../test", "/test"),
("test/..", "."),
("test/path/..", "test"),
("test/../path", "path"),
("/test/../path", "/path"),
("test/path/../../", "."),
("test/path/../../..", ".."),
("/test/path/../../..", "/"),
("/test/path/../../../..", "/"),
("test/path/../../../..", "../.."),
("test/path/../../another/path", "another/path"),
("test/path/../../another/path/..", "another"),
("../test", "../test"),
("../test/", "../test"),
("../test/path", "../test/path"),
("../test/..", ".."),
];
for test in tests {
assert_eq!(clean(test.0), PathBuf::from(test.1));
}
}
#[test]
fn test_pathbuf_trait() {
assert_eq!(
PathBuf::from("/test/../path/").clean(),
PathBuf::from("/path")
);
}
#[test]
fn test_path_trait() {
assert_eq!(Path::new("/test/../path/").clean(), PathBuf::from("/path"));
}
#[test]
#[cfg(target_os = "windows")]
fn test_windows_paths() {
let tests = vec![
("\\..", "\\"),
("\\..\\test", "\\test"),
("test\\..", "."),
("test\\path\\..\\..\\..", ".."),
("test\\path/..\\../another\\path", "another\\path"), ("test\\path\\my/path", "test\\path\\my\\path"), ("/dir\\../otherDir/test.json", "/otherDir/test.json"), ("c:\\test\\..", "c:\\"), ("c:/test/..", "c:/"), ];
for test in tests {
assert_eq!(clean(test.0), PathBuf::from(test.1));
}
}
}