use crate::types::string;
const SEP: char = std::path::MAIN_SEPARATOR;
#[allow(non_snake_case)]
pub fn Join(parts: &[impl AsRef<str>]) -> string {
let joined: Vec<&str> = parts.iter().map(|p| p.as_ref()).filter(|s| !s.is_empty()).collect();
if joined.is_empty() {
return String::new();
}
let combined = joined.join(&SEP.to_string());
Clean(combined)
}
#[allow(non_snake_case)]
pub fn Base(path: impl AsRef<str>) -> string {
let s = path.as_ref();
if s.is_empty() {
return ".".to_string();
}
let trimmed = s.trim_end_matches(SEP);
if trimmed.is_empty() {
return SEP.to_string();
}
match trimmed.rsplit_once(SEP) {
Some((_, tail)) => tail.to_string(),
None => trimmed.to_string(),
}
}
#[allow(non_snake_case)]
pub fn Dir(path: impl AsRef<str>) -> string {
let s = path.as_ref();
let trimmed = s.trim_end_matches(SEP);
match trimmed.rsplit_once(SEP) {
Some((head, _)) => {
if head.is_empty() {
SEP.to_string()
} else {
Clean(head.to_string())
}
}
None => ".".to_string(),
}
}
#[allow(non_snake_case)]
pub fn Ext(path: impl AsRef<str>) -> string {
let s = path.as_ref();
let base = Base(s);
match base.rfind('.') {
Some(i) if i > 0 => base[i..].to_string(),
_ => String::new(),
}
}
#[allow(non_snake_case)]
pub fn Clean(path: impl Into<String>) -> string {
let s: String = path.into();
if s.is_empty() {
return ".".to_string();
}
let absolute = s.starts_with(SEP);
let mut stack: Vec<&str> = Vec::new();
for part in s.split(SEP) {
match part {
"" | "." => continue,
".." => {
if stack.last().map_or(false, |t| *t != "..") && !stack.is_empty() {
stack.pop();
} else if !absolute {
stack.push("..");
}
}
other => stack.push(other),
}
}
let joined = stack.join(&SEP.to_string());
if absolute {
format!("{}{}", SEP, joined)
} else if joined.is_empty() {
".".to_string()
} else {
joined
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn join_basic() {
assert_eq!(Join(&["a", "b", "c"]), "a/b/c");
assert_eq!(Join(&["/a", "b"]), "/a/b");
assert_eq!(Join(&["a", "", "b"]), "a/b");
}
#[test]
fn base_cases() {
assert_eq!(Base("/a/b/c.txt"), "c.txt");
assert_eq!(Base("c.txt"), "c.txt");
assert_eq!(Base("/"), "/");
assert_eq!(Base(""), ".");
}
#[test]
fn dir_cases() {
assert_eq!(Dir("/a/b/c.txt"), "/a/b");
assert_eq!(Dir("c.txt"), ".");
assert_eq!(Dir("/a"), "/");
}
#[test]
fn ext_cases() {
assert_eq!(Ext("a/b.txt"), ".txt");
assert_eq!(Ext("name.tar.gz"), ".gz");
assert_eq!(Ext("noext"), "");
assert_eq!(Ext(".hidden"), ""); }
#[test]
fn clean_normalizes() {
assert_eq!(Clean("a/./b//c/../d"), "a/b/d");
assert_eq!(Clean("/a/../b"), "/b");
assert_eq!(Clean(""), ".");
assert_eq!(Clean("/"), "/");
}
}