xtask_todo_lib/devshell/vfs/
path.rs1#[must_use]
5pub fn resolve_path_with_cwd(cwd: &str, path: &str) -> String {
6 let path = path.trim();
7 let path_normalized = normalize_path(path);
8 if path_normalized.starts_with('/') {
10 return path_normalized;
11 }
12 if path_normalized == "/" {
13 return cwd.to_string();
14 }
15 let base = cwd.trim_end_matches('/');
17 let p = path.trim_start_matches('/');
18 let combined = if base.is_empty() {
19 format!("/{p}")
20 } else {
21 format!("{base}/{p}")
22 };
23 let result = normalize_path(&combined);
24 if result.is_empty() || result == "." {
25 "/".to_string()
26 } else if result.starts_with('/') {
27 result
28 } else {
29 format!("/{result}")
30 }
31}
32
33#[must_use]
36pub fn normalize_path(input: &str) -> String {
37 let s = input.replace('\\', "/");
38
39 let (rest, absolute) = if s.len() >= 2
41 && s.chars().next().is_some_and(|c| c.is_ascii_alphabetic())
42 && s.chars().nth(1) == Some(':')
43 {
44 (&s[2..], true)
45 } else {
46 (s.as_str(), s.starts_with('/'))
47 };
48
49 let rest = rest.trim_start_matches('/');
50 let mut out: Vec<&str> = Vec::new();
51 for p in rest.split('/') {
52 match p {
53 "" | "." => {}
54 ".." => {
55 out.pop();
56 }
57 _ => out.push(p),
58 }
59 }
60
61 if absolute {
62 "/".to_string() + &out.join("/")
63 } else if out.is_empty() {
64 ".".to_string()
65 } else {
66 out.join("/")
67 }
68}