vtcode_commons/paths/
normalize.rs1#![expect(
2 unused_results,
3 reason = "Path normalization intentionally ignores the boolean returned by pop after a validated component check."
4)]
5
6use std::path::{Component, Path, PathBuf};
7
8pub fn normalize_path(path: &Path) -> PathBuf {
10 let mut normalized = PathBuf::new();
11 for component in path.components() {
12 match component {
13 Component::ParentDir => {
14 normalized.pop();
15 }
16 Component::CurDir => {}
17 Component::Prefix(prefix) => normalized.push(prefix.as_os_str()),
18 Component::RootDir => normalized.push(component.as_os_str()),
19 Component::Normal(part) => normalized.push(part),
20 }
21 }
22 normalized
23}
24
25pub fn expand_tilde(path: &str) -> PathBuf {
33 if path == "~" {
34 return dirs::home_dir().unwrap_or_else(|| PathBuf::from(path));
35 }
36 if let Some(rest) = path.strip_prefix("~/")
37 && let Some(home) = dirs::home_dir()
38 {
39 return home.join(rest);
40 }
41 PathBuf::from(path)
42}
43
44pub fn normalize_ascii_identifier(value: &str) -> String {
46 let mut normalized = String::with_capacity(value.len());
47 for ch in value.chars() {
48 if ch.is_ascii_alphanumeric() {
49 normalized.push(ch.to_ascii_lowercase());
50 }
51 }
52 normalized
53}
54
55pub fn file_name_from_path(path: &str) -> String {
57 Path::new(path)
58 .file_name()
59 .and_then(|name| name.to_str())
60 .map(|s| s.to_string())
61 .unwrap_or_else(|| path.to_string())
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use std::path::PathBuf;
68
69 #[test]
70 fn expand_tilde_passes_through_absolute_paths() {
71 let absolute = "/etc/hosts";
72 assert_eq!(expand_tilde(absolute), PathBuf::from(absolute));
73 }
74
75 #[test]
76 fn expand_tilde_passes_through_relative_paths() {
77 let relative = "src/main.rs";
78 assert_eq!(expand_tilde(relative), PathBuf::from(relative));
79 }
80
81 #[test]
82 fn expand_tilde_resolves_bare_tilde_to_home() {
83 if let Some(home) = dirs::home_dir() {
84 assert_eq!(expand_tilde("~"), home);
85 }
86 }
87
88 #[test]
89 fn expand_tilde_resolves_tilde_slash_prefix() {
90 if let Some(home) = dirs::home_dir() {
91 let resolved = expand_tilde("~/projects/vtcode");
92 assert_eq!(resolved, home.join("projects/vtcode"));
93 }
94 }
95}