1use std::path::{Component, Path, PathBuf};
4
5pub fn normalize<P: AsRef<Path>>(path: P) -> PathBuf {
9 let mut out = PathBuf::new();
10 for comp in path.as_ref().components() {
11 match comp {
12 Component::CurDir => {}
13 Component::ParentDir => {
14 match out.components().next_back() {
16 Some(Component::RootDir | Component::Prefix(_)) | None => {}
17 _ => {
18 out.pop();
19 }
20 }
21 }
22 other => out.push(other),
23 }
24 }
25 out
26}
27
28pub fn stem_ext<P: AsRef<Path>>(path: P) -> (String, String) {
31 let p = path.as_ref();
32 let stem = p
33 .file_stem()
34 .and_then(|s| s.to_str())
35 .unwrap_or("")
36 .to_string();
37 let ext = p
38 .extension()
39 .and_then(|s| s.to_str())
40 .unwrap_or("")
41 .to_string();
42 (stem, ext)
43}
44
45pub fn with_extension<P: AsRef<Path>>(path: P, new_ext: &str) -> PathBuf {
48 path.as_ref().with_extension(new_ext)
49}
50
51#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn normalize_removes_dots() {
59 assert_eq!(normalize("./foo/../bar/./baz"), PathBuf::from("bar/baz"));
60 }
61
62 #[test]
63 fn normalize_absolute() {
64 assert_eq!(normalize("/a/b/../c"), PathBuf::from("/a/c"));
65 }
66
67 #[test]
68 fn normalize_no_above_root() {
69 assert_eq!(normalize("/.."), PathBuf::from("/"));
70 }
71
72 #[test]
73 fn stem_ext_splits_correctly() {
74 assert_eq!(stem_ext("main.rs"), ("main".into(), "rs".into()));
75 assert_eq!(stem_ext("no_ext"), ("no_ext".into(), "".into()));
76 }
77}