#[cfg(windows)] use std::path::Prefix; use std::path::{Component, Path, PathBuf}; /// Normalizes file paths by handling path components, prefixes, and resolving relative paths /// Based on: https://github.com/rust-lang/cargo/blob/rust-1.67.0/crates/cargo-util/src/paths.rs#L82-L107 pub fn normalize_path(path: &Path) -> PathBuf { let mut components = path.components().peekable(); let mut ret = if let Some(c @ Component::Prefix(_prefix_component)) = components.peek().cloned() { #[cfg(windows)] // Remove only the Verbatim prefix, but keep the drive letter (e.g., C:\) match _prefix_component.kind() { Prefix::VerbatimDisk(disk) => { components.next(); // skip this prefix // Re-add the disk prefix (e.g., C:) let mut pb = PathBuf::new(); pb.push(format!("{}:", disk as char)); pb } Prefix::Verbatim(_) | Prefix::VerbatimUNC(_, _) => { components.next(); // skip this prefix PathBuf::new() } _ => { components.next(); PathBuf::from(c.as_os_str()) } } #[cfg(not(windows))] { components.next(); // skip this prefix PathBuf::from(c.as_os_str()) } } else { PathBuf::new() }; for component in components { match component { Component::Prefix(..) => unreachable!(), Component::RootDir => { ret.push(component.as_os_str()); } Component::CurDir => {} Component::ParentDir => { ret.pop(); } Component::Normal(c) => { ret.push(c); } } } ret } /// Removes file:/ and file:\ prefixes from file paths pub fn normalize_file_path(path: &str) -> String { path.replace("file:/", "").replace("file:\\", "") } /// Removes prefix from path string with proper formatting pub fn remove_prefix(path: &str, prefix: &str) -> String { if !prefix.is_empty() && path.starts_with(prefix) { let result = path[prefix.len()..].to_string(); if result.is_empty() { "/".to_string() } else if result.starts_with('/') { result } else { format!("/{}", result) } } else { path.to_string() } }