use std::{
io,
path::{Component, Path, PathBuf},
};
use anyhow::bail;
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(".")
}
}
pub fn normalize<P>(path: P) -> io::Result<PathBuf>
where
P: AsRef<Path>,
{
let path = path.as_ref();
Ok(if path.is_absolute() {
clean(path)
} else {
clean(std::env::current_dir()?.join(path))
})
}
pub fn diff_normalized_paths<P, B>(to_path: P, from_path: B) -> anyhow::Result<PathBuf>
where
P: AsRef<Path>,
B: AsRef<Path>,
{
let to_path = to_path.as_ref();
let from_path = from_path.as_ref();
assert!(to_path.is_absolute() && from_path.is_absolute());
let mut ita = to_path.components().peekable();
let mut itb = from_path.components().peekable();
while let (Some(a), Some(b)) = (ita.peek(), itb.peek()) {
match (a, b) {
(Component::Prefix(pa), Component::Prefix(pb)) if pa != pb => {
bail!("Path prefix doesn't match")
}
_ if a == b => {
ita.next();
itb.next();
}
_ => break,
}
}
let mut result = Vec::new();
for comp in itb {
assert!(matches!(comp, Component::Normal(_)));
result.push(Component::ParentDir);
}
for comp in ita {
result.push(comp);
}
if result.is_empty() {
result.push(Component::CurDir);
}
Ok(result.iter().collect())
}
#[cfg(test)]
mod test {
use std::path::PathBuf;
use crate::path_utils::{diff_normalized_paths, normalize};
#[test]
fn test_diff_normalized_paths() {
assert_eq!(
diff_normalized_paths(
normalize("/some/path").unwrap(),
normalize("/some/foo/baz/path").unwrap()
)
.unwrap(),
PathBuf::from("../../../path")
);
assert_eq!(
diff_normalized_paths(
normalize("/some/foo/baz/path").unwrap(),
normalize("/some/path").unwrap(),
)
.unwrap(),
PathBuf::from("../foo/baz/path")
);
assert_eq!(
diff_normalized_paths(
normalize("some/path").unwrap(),
normalize("some/foo/baz/path").unwrap()
)
.unwrap(),
PathBuf::from("../../../path")
);
assert_eq!(
diff_normalized_paths(
normalize("some/foo/baz/path").unwrap(),
normalize("some/path").unwrap(),
)
.unwrap(),
PathBuf::from("../foo/baz/path")
);
}
}