use std::path::Path; use std::path::PathBuf; use crate::env; pub fn normalize_for_path_comparison(path: impl AsRef) -> std::io::Result { let canonical = path.as_ref().canonicalize()?; Ok(normalize_for_wsl(canonical)) } fn normalize_for_wsl(path: PathBuf) -> PathBuf { normalize_for_wsl_with_flag(path, env::is_wsl()) } fn normalize_for_wsl_with_flag(path: PathBuf, is_wsl: bool) -> PathBuf { if !is_wsl { return path; } if !is_wsl_case_insensitive_path(&path) { return path; } lower_ascii_path(path) } fn is_wsl_case_insensitive_path(path: &Path) -> bool { #[cfg(target_os = "linux")] { use std::os::unix::ffi::OsStrExt; use std::path::Component; let mut components = path.components(); let Some(Component::RootDir) = components.next() else { return false; }; let Some(Component::Normal(mnt)) = components.next() else { return false; }; if !ascii_eq_ignore_case(mnt.as_bytes(), b"mnt") { return false; } let Some(Component::Normal(drive)) = components.next() else { return false; }; let drive_bytes = drive.as_bytes(); drive_bytes.len() == 1 && drive_bytes[0].is_ascii_alphabetic() } #[cfg(not(target_os = "linux"))] { let _ = path; false } } #[cfg(target_os = "linux")] fn ascii_eq_ignore_case(left: &[u8], right: &[u8]) -> bool { left.len() == right.len() && left .iter() .zip(right) .all(|(lhs, rhs)| lhs.to_ascii_lowercase() == *rhs) } #[cfg(target_os = "linux")] fn lower_ascii_path(path: PathBuf) -> PathBuf { use std::ffi::OsString; use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStringExt; // WSL mounts Windows drives under /mnt/, which are case-insensitive. let bytes = path.as_os_str().as_bytes(); let mut lowered = Vec::with_capacity(bytes.len()); for byte in bytes { lowered.push(byte.to_ascii_lowercase()); } PathBuf::from(OsString::from_vec(lowered)) } #[cfg(not(target_os = "linux"))] fn lower_ascii_path(path: PathBuf) -> PathBuf { path } #[cfg(test)] mod tests { #[cfg(target_os = "linux")] mod wsl { use super::super::normalize_for_wsl_with_flag; use pretty_assertions::assert_eq; use std::path::PathBuf; #[test] fn wsl_mnt_drive_paths_lowercase() { let normalized = normalize_for_wsl_with_flag(PathBuf::from("/mnt/C/Users/Dev"), true); assert_eq!(normalized, PathBuf::from("/mnt/c/users/dev")); } #[test] fn wsl_non_drive_paths_unchanged() { let path = PathBuf::from("/mnt/cc/Users/Dev"); let normalized = normalize_for_wsl_with_flag(path.clone(), true); assert_eq!(normalized, path); } #[test] fn wsl_non_mnt_paths_unchanged() { let path = PathBuf::from("/home/Dev"); let normalized = normalize_for_wsl_with_flag(path.clone(), true); assert_eq!(normalized, path); } } }