use std::path::{Path, PathBuf};
pub fn rel_path(fname: &Path, root: &Path) -> String {
pathdiff::diff_paths(fname, root)
.map(|p| normalize_slashes(&p))
.unwrap_or_else(|| fname.to_string_lossy().into_owned())
}
fn normalize_slashes(path: &Path) -> String {
path.to_string_lossy().replace('\\', "/")
}
pub fn normalize_path(path: &Path) -> PathBuf {
std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
}
pub fn file_stem(path: &Path) -> Option<&str> {
path.file_stem().and_then(|s| s.to_str())
}
pub fn file_name(path: &Path) -> Option<&str> {
path.file_name().and_then(|s| s.to_str())
}
pub fn extension(path: &Path) -> Option<&str> {
path.extension().and_then(|s| s.to_str())
}
pub fn path_components(rel_fname: &str) -> impl Iterator<Item = &str> {
Path::new(rel_fname)
.components()
.filter_map(|c| c.as_os_str().to_str())
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn rel_path_basic() {
let root = Path::new("/home/user/project");
let fname = Path::new("/home/user/project/src/main.rs");
assert_eq!(rel_path(fname, root), "src/main.rs");
}
#[test]
fn rel_path_same_dir() {
let root = Path::new("/home/user/project");
let fname = Path::new("/home/user/project/file.rs");
assert_eq!(rel_path(fname, root), "file.rs");
}
#[test]
fn rel_path_fallback_to_absolute() {
let root = Path::new("/root");
let fname = Path::new("/other/path/file.rs");
let result = rel_path(fname, root);
assert!(!result.is_empty());
}
#[test]
fn path_components_basic() {
let components: Vec<_> = path_components("src/lib/mod.rs").collect();
assert_eq!(components, vec!["src", "lib", "mod.rs"]);
}
#[test]
fn file_stem_basic() {
assert_eq!(file_stem(Path::new("main.rs")), Some("main"));
assert_eq!(file_stem(Path::new("lib.tar.gz")), Some("lib.tar"));
}
#[test]
fn extension_basic() {
assert_eq!(extension(Path::new("main.rs")), Some("rs"));
assert_eq!(extension(Path::new("Makefile")), None);
}
}