#[cfg(windows)] use std::path::Prefix; use std::path::{Component, Path, PathBuf}; #[cfg(windows)] use std::os::windows::ffi::OsStrExt; #[cfg(windows)] use std::ffi::OsStr; #[cfg(windows)] use windows_sys::Win32::Storage::FileSystem::GetShortPathNameW; /// 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() } } /// Get Windows short path to avoid issues with spaces and special characters #[cfg(windows)] pub fn get_short_path>(path: P) -> Option { let wide: Vec = OsStr::new(path.as_ref()) .encode_wide() .chain(Some(0)) .collect(); let mut buffer = vec![0u16; 260]; let len = unsafe { GetShortPathNameW(wide.as_ptr(), buffer.as_mut_ptr(), buffer.len() as u32) }; if len > 0 { Some(String::from_utf16_lossy(&buffer[..len as usize])) } else { None } } #[cfg(test)] mod tests { use super::*; #[cfg(windows)] #[test] fn test_get_short_path() { // Test with a real path that should exist on Windows use std::env; if let Ok(temp_dir) = env::var("TEMP") { let result = get_short_path(&temp_dir); // Should return some short path or None (both are valid) // We can't assert the exact value as it depends on the system println!("Short path result: {:?}", result); } } }