use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use typed_path::{Utf8WindowsComponent, Utf8WindowsPath};
pub(crate) fn simplified_components(input: &Path) -> Option<Vec<&OsStr>> {
let mut out = Vec::new();
let input_str = input.to_str()?;
for component in Utf8WindowsPath::new(input_str).components() {
match component {
Utf8WindowsComponent::Prefix(_) | Utf8WindowsComponent::RootDir => (),
Utf8WindowsComponent::ParentDir => {
out.pop()?;
}
Utf8WindowsComponent::Normal(s) => out.push(OsStr::new(s)),
Utf8WindowsComponent::CurDir => (),
}
}
Some(out)
}
pub(crate) fn file_name_sanitized(no_null_filename: &str) -> PathBuf {
Utf8WindowsPath::new(no_null_filename)
.components()
.filter_map(|component| match component {
Utf8WindowsComponent::Normal(s) => Some(s),
_ => None,
})
.collect()
}
pub(crate) fn enclosed_name(file_name: &str) -> Option<PathBuf> {
let mut depth = 0usize;
let mut out_path = PathBuf::new();
for component in Utf8WindowsPath::new(file_name).components() {
match component {
Utf8WindowsComponent::Prefix(_) | Utf8WindowsComponent::RootDir => {
if depth > 0 {
return None;
}
}
Utf8WindowsComponent::ParentDir => {
depth = depth.checked_sub(1)?;
out_path.pop();
}
Utf8WindowsComponent::Normal(s) => {
depth += 1;
out_path.push(s);
}
Utf8WindowsComponent::CurDir => (),
}
}
Some(out_path)
}
#[cfg(test)]
mod tests {
use super::simplified_components;
use std::path::Path;
#[test]
fn test_simplified_components_relative_path() {
let path = Path::new("foo/bar/baz.txt");
let components = simplified_components(path).unwrap();
assert_eq!(components.len(), 3);
assert_eq!(components[0], "foo");
assert_eq!(components[1], "bar");
assert_eq!(components[2], "baz.txt");
}
#[test]
fn test_simplified_components_absolute_unix_path() {
let path = Path::new("/foo/bar/baz.txt");
let components = simplified_components(path).unwrap();
assert_eq!(components.len(), 3);
assert_eq!(components[0], "foo");
assert_eq!(components[1], "bar");
assert_eq!(components[2], "baz.txt");
}
#[test]
fn test_simplified_components_with_parent_dirs() {
let path = Path::new("foo/../bar/baz.txt");
let components = simplified_components(path).unwrap();
assert_eq!(components.len(), 2);
assert_eq!(components[0], "bar");
assert_eq!(components[1], "baz.txt");
}
#[test]
fn test_simplified_components_too_many_parent_dirs() {
let path = Path::new("foo/../../bar");
let result = simplified_components(path);
assert!(result.is_none()); }
#[test]
fn test_simplified_components_with_current_dir() {
let path = Path::new("foo/./bar/baz.txt");
let components = simplified_components(path).unwrap();
assert_eq!(components.len(), 3);
assert_eq!(components[0], "foo");
assert_eq!(components[1], "bar");
assert_eq!(components[2], "baz.txt");
}
#[test]
fn test_simplified_components_empty_path() {
let path = Path::new("");
let components = simplified_components(path).unwrap();
assert_eq!(components.len(), 0);
}
#[test]
fn test_simplified_components_root_only() {
let path = Path::new("/");
let components = simplified_components(path).unwrap();
assert_eq!(components.len(), 0);
}
#[cfg(windows)]
#[test]
fn test_simplified_components_windows_absolute_path() {
let path = Path::new(r"C:\foo\bar\baz.txt");
let components = simplified_components(path).unwrap();
assert_eq!(components.len(), 3);
assert_eq!(components[0], "foo");
assert_eq!(components[1], "bar");
assert_eq!(components[2], "baz.txt");
}
#[cfg(windows)]
#[test]
fn test_simplified_components_windows_unc_path() {
let path = Path::new(r"\\server\share\foo\bar.txt");
let components = simplified_components(path).unwrap();
assert_eq!(components.len(), 2);
assert_eq!(components[0], "foo");
assert_eq!(components[1], "bar.txt");
}
}