From 9ec152d06d78875f436472a3787c5eaff69f5a59 Mon Sep 17 00:00:00 2001 From: "pegasus.cadence@gmail.com" Date: Mon, 18 Nov 2024 18:35:36 -0800 Subject: [PATCH] DriverPwdMap --- crates/nu-path/src/tilde.rs | 12 + .../src/engine/state_driver_pwd.rs | 292 ++++++++++++++++++ 2 files changed, 304 insertions(+) create mode 100644 crates/nu-protocol/src/engine/state_driver_pwd.rs diff --git a/crates/nu-path/src/tilde.rs b/crates/nu-path/src/tilde.rs index ac48bdfc3f..02324ea8da 100644 --- a/crates/nu-path/src/tilde.rs +++ b/crates/nu-path/src/tilde.rs @@ -21,6 +21,18 @@ fn expand_tilde_with_home(path: impl AsRef, home: Option) -> Path let path = path.as_ref(); if !path.starts_with("~") { + use nu_protocol::engine::state_driver_pwd:: { + need_expand_current_directory, + get_windows_absolute_path, + }; + if need_expand_current_directory(path) { + if let Some(current_dir) = get_windows_absolute_path(path) { + //println!("Absolute path for {} is: {}", path.display(), current_dir); + return PathBuf::from(¤t_dir) + } else { + println!("Failed to get absolute path for {}", path.display()); + } + } let string = path.to_string_lossy(); let mut path_as_string = string.as_ref().bytes(); return match path_as_string.next() { diff --git a/crates/nu-protocol/src/engine/state_driver_pwd.rs b/crates/nu-protocol/src/engine/state_driver_pwd.rs new file mode 100644 index 0000000000..dfdbe0ebc3 --- /dev/null +++ b/crates/nu-protocol/src/engine/state_driver_pwd.rs @@ -0,0 +1,292 @@ +use std::path::Path; + +pub struct DrivePwdMap { + map: [Option; 26], // Fixed-size array for A-Z +} + +impl DrivePwdMap { + /// Create a new DrivePwdMap + pub fn new() -> Self { + DrivePwdMap { + map: Default::default(), // Initialize all to `None` + } + } + + /// Set the current working directory based on the drive letter in the path + pub fn set_pwd(&mut self, path: &Path) -> Result<(), String> { + if let Some(drive_letter) = Self::extract_drive_letter(path) { + if let Some(index) = Self::drive_to_index(drive_letter) { + self.map[index] = Some(path.to_string_lossy().into_owned()); + Ok(()) + } else { + Err(format!("Invalid drive letter: {}", drive_letter)) + } + } else { + Err(format!("Invalid path: {}", path.display())) + } + } + + /// Get the current working directory for a drive letter + /// If no PWD is set, return the root of the drive (e.g., `C:\`) + pub fn get_pwd(&self, drive: char) -> Option { + Self::drive_to_index(drive).map(|index| { + self.map[index] + .clone() + .unwrap_or_else(|| format!("{}:\\", drive.to_ascii_uppercase())) + }) + } + + /// Expand a relative path using the current working directory of the drive + pub fn expand_path(&self, path: &Path) -> Option { + let path_str = path.to_str()?; + if let Some(drive_letter) = Self::extract_drive_letter(path) { + let is_absolute = path_str.contains(":\\") || path_str.starts_with("\\"); + if is_absolute { + // Already an absolute path + Some(PathBuf::from(path_str)) + } else if let Some(pwd) = self.get_pwd(drive_letter) { + // Combine current PWD with the relative path + let mut base = PathBuf::from(pwd); + base.push(path_str.split_at(2).1); // Skip the "C:" part of the relative path + Some(base) + } else { + None // Drive letter not found + } + } else { + None // Invalid or no drive letter + } + } + + /// Helper to convert a drive letter to an array index + fn drive_to_index(drive: char) -> Option { + let drive = drive.to_ascii_uppercase(); + if ('A'..='Z').contains(&drive) { + Some((drive as usize) - ('A' as usize)) + } else { + None + } + } + + /// Extract the drive letter from a path (e.g., `C:\Users` -> `C`) + fn extract_drive_letter(path: &Path) -> Option { + path.to_str() + .and_then(|s| s.chars().next()) + .filter(|c| c.is_ascii_alphabetic()) + } +} + +use once_cell::sync::Lazy; +use std::sync::Mutex; +use crate::ShellError; + +/// Global singleton instance of DrivePwdMap +static DRIVE_PWD_MAP: Lazy> = Lazy::new(|| Mutex::new(DrivePwdMap::new())); + +/// Public API to access the singleton instance +pub fn get_drive_pwd_map() -> &'static Mutex { + &DRIVE_PWD_MAP +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::Path; + + #[test] + fn test_expand_path() { + let mut drive_map = DrivePwdMap::new(); + + // Set PWD for drive C + drive_map.set_pwd(Path::new("C:\\Users\\Home")).unwrap(); + + // Expand a relative path + let expanded = drive_map.expand_path(Path::new("C:test")); + assert_eq!(expanded, Some(PathBuf::from("C:\\Users\\Home\\test"))); + + // Expand an absolute path + let expanded = drive_map.expand_path(Path::new("C:\\absolute\\path")); + assert_eq!(expanded, Some(PathBuf::from("C:\\absolute\\path"))); + + // Expand with no drive letter + let expanded = drive_map.expand_path(Path::new("\\no_drive")); + assert_eq!(expanded, None); + + // Expand with no PWD set for the drive + let expanded = drive_map.expand_path(Path::new("D:test")); + assert_eq!(expanded, Some(PathBuf::from("D:\\test"))); + } + #[test] + fn test_singleton_set_and_get_pwd() { + let drive_pwd_map = get_drive_pwd_map(); + { + let mut map = drive_pwd_map.lock().unwrap(); + + // Set PWD for drive C + assert!(map.set_pwd(Path::new("C:\\Users\\Example")).is_ok()); + } + + { + let map = drive_pwd_map.lock().unwrap(); + + // Get PWD for drive C + assert_eq!(map.get_pwd('C'), Some("C:\\Users\\Example".to_string())); + + // Get PWD for drive E (not set, should return E:\) + assert_eq!(map.get_pwd('E'), Some("E:\\".to_string())); + } + } + + #[test] + fn test_set_and_get_pwd() { + let mut drive_map = DrivePwdMap::new(); + + // Set PWD for drive C + assert!(drive_map.set_pwd(Path::new("C:\\Users\\Example")).is_ok()); + assert_eq!(drive_map.get_pwd('C'), Some("C:\\Users\\Example".to_string())); + + // Set PWD for drive D + assert!(drive_map.set_pwd(Path::new("D:\\Projects")).is_ok()); + assert_eq!(drive_map.get_pwd('D'), Some("D:\\Projects".to_string())); + + // Get PWD for drive E (not set, should return E:\) + assert_eq!(drive_map.get_pwd('E'), Some("E:\\".to_string())); + } + + #[test] + fn test_set_pwd_invalid_path() { + let mut drive_map = DrivePwdMap::new(); + + // Invalid path (no drive letter) + let result = drive_map.set_pwd(Path::new("\\InvalidPath")); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), "Invalid path: \\InvalidPath"); + } + + #[test] + fn test_get_pwd_invalid_drive() { + let drive_map = DrivePwdMap::new(); + + // Get PWD for a drive not set (e.g., Z) + assert_eq!(drive_map.get_pwd('Z'), Some("Z:\\".to_string())); + + // Invalid drive letter (non-alphabetic) + assert_eq!(drive_map.get_pwd('1'), None); + } + + #[test] + fn test_drive_to_index() { + // Valid drive letters + assert_eq!(DrivePwdMap::drive_to_index('A'), Some(0)); + assert_eq!(DrivePwdMap::drive_to_index('Z'), Some(25)); + // Valid drive letters + assert_eq!(DrivePwdMap::drive_to_index('a'), Some(0)); + assert_eq!(DrivePwdMap::drive_to_index('z'), Some(25)); + for i in 1..25 { + assert_eq!(DrivePwdMap::drive_to_index(std::char::from_u32(('A' as usize + i) as u32).unwrap()), Some(i)); + assert_eq!(DrivePwdMap::drive_to_index(std::char::from_u32(('a' as usize + i) as u32).unwrap()), Some(i)); + } + + // Invalid drive letters + assert_eq!(DrivePwdMap::drive_to_index('1'), None); + assert_eq!(DrivePwdMap::drive_to_index('$'), None); + } +} + +mod current_directory_specific { + use crate::ShellError; + use std::path::Path; + + #[cfg(target_os = "windows")] + fn need_expand_current_directory(path: &Path) -> bool { + if let Some(path_str) = path.to_str() { + let chars: Vec = path_str.chars().collect(); + if chars.len() >= 2 { + return chars[1] == ':' && (chars.len() == 2 || (chars[2] != '/' && chars[2] != '\\')); + } + } + false + } + + #[cfg(not(target_os = "windows"))] + fn need_expand_current_directory(path: &Path) -> bool { + false + } + + #[cfg(target_os = "windows")] + fn get_windows_absolute_path(path: &Path) -> Option { + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + use std::os::windows::ffi::OsStrExt; + use winapi::um::fileapi::GetFullPathNameW; + use winapi::um::winnt::WCHAR; + + const MAX_PATH : usize = 260; + let mut buffer: [WCHAR; MAX_PATH] = [0; MAX_PATH]; + + if let Some(path_str) = path.to_str() { + unsafe { + // Convert input to wide string. + let wide_path: Vec = OsString::from(path_str).encode_wide().chain(Some(0)).collect(); + let length = GetFullPathNameW( + wide_path.as_ptr(), + buffer.len() as u32, + buffer.as_mut_ptr(), + std::ptr::null_mut(), + ); + + if length > 0 { + let abs_path = OsString::from_wide(&buffer[..length as usize]); + if let Some(abs_path_str) = abs_path.to_str() { + let abs_path_string = abs_path_str.to_string(); + return Some(abs_path_string); + } + } + } + } + + None + } + + #[cfg(not(target_os = "windows"))] + fn get_windows_absolute_path(path: &Path) -> Option { + None + } + #[cfg(target_os = "windows")] + pub fn set_current_directory_windows(path: &Path) -> Result<(), ShellError> { + use std::ffi::OsString; + use std::os::windows::ffi::OsStrExt; + use windows_sys::Win32::System::Environment::SetCurrentDirectoryW; + + if let Some(path_str) = path.to_str() { + unsafe { + // Convert input to wide string. + let wide_path: Vec = OsString::from(path_str).encode_wide().chain(Some(0)).collect(); + if SetCurrentDirectoryW(wide_path.as_ptr()) != 0 { + println!("Successfully changed the current directory to {}", path_str); + return Ok(()) + } else { + return + Err(ShellError::GenericError { + error: "Failed to set current directory".into(), + msg: "SetCurrentDirectoryW() failed".into(), + span: None, + help: None, + inner: vec![], + }) + }; + } + } + Err(ShellError::GenericError { + error: "Failed to set current directory".into(), + msg: "Invalid path".into(), + span: None, + help: None, + inner: vec![], + }) + } + + #[cfg(not(target_os = "windows"))] + pub fn set_current_directory_windows(_path: &Path) -> Result<(), ShellError>{ + Ok(()) + } +} \ No newline at end of file