diff --git a/crates/nu-cli/src/completions/completion_common.rs b/crates/nu-cli/src/completions/completion_common.rs index 64f8acdab6..c0852ea640 100644 --- a/crates/nu-cli/src/completions/completion_common.rs +++ b/crates/nu-cli/src/completions/completion_common.rs @@ -5,7 +5,7 @@ use nu_engine::env_to_string; use nu_path::dots::expand_ndots; use nu_path::{expand_to_real_path, home_dir}; #[cfg(windows)] -use nu_protocol::engine::expand_pwd; +use nu_protocol::engine::pwd_per_drive_helper::os_windows::*; //fs_client::expand_pwd; use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, Span, @@ -177,16 +177,17 @@ pub fn complete_item( let cleaned_partial = surround_remove(partial); let isdir = cleaned_partial.ends_with(is_separator); #[cfg(windows)] - let cleaned_partial = - if let Some(absolute_path) = expand_pwd(stack, engine_state, Path::new(&cleaned_partial)) { - if let Some(abs_path_string) = absolute_path.as_path().to_str() { - abs_path_string.to_string() - } else { - absolute_path.display().to_string() - } + let cleaned_partial = if let Some(absolute_path) = + fs_client::expand_pwd(stack, engine_state, Path::new(&cleaned_partial)) + { + if let Some(abs_path_string) = absolute_path.as_path().to_str() { + abs_path_string.to_string() } else { - cleaned_partial - }; + absolute_path.display().to_string() + } + } else { + cleaned_partial + }; let expanded_partial = expand_ndots(Path::new(&cleaned_partial)); let should_collapse_dots = expanded_partial != Path::new(&cleaned_partial); let mut partial = expanded_partial.to_string_lossy().to_string(); diff --git a/crates/nu-path/src/lib.rs b/crates/nu-path/src/lib.rs index 290985995b..f61c61b954 100644 --- a/crates/nu-path/src/lib.rs +++ b/crates/nu-path/src/lib.rs @@ -7,7 +7,7 @@ pub mod form; mod helpers; mod path; #[cfg(windows)] -pub mod pwd_per_drive; +mod pwd_per_drive; mod tilde; mod trailing_slash; @@ -16,9 +16,10 @@ pub use expansions::{canonicalize_with, expand_path_with, expand_to_real_path, l pub use helpers::{cache_dir, data_dir, home_dir, nu_config_dir}; pub use path::*; #[cfg(windows)] -pub use pwd_per_drive::{ - bash_strip_redundant_quotes, cmd_strip_all_double_quotes, ensure_trailing_delimiter, - env_var_for_drive, extract_drive_letter, get_full_path_name_w, need_expand, -}; +pub use pwd_per_drive::*; +// { +// bash_strip_redundant_quotes, cmd_strip_all_double_quotes, ensure_trailing_delimiter, +// env_var_for_drive, extract_drive_letter, get_full_path_name_w, need_expand, +// }; pub use tilde::expand_tilde; pub use trailing_slash::{has_trailing_slash, strip_trailing_slash}; diff --git a/crates/nu-path/src/pwd_per_drive.rs b/crates/nu-path/src/pwd_per_drive.rs index 235998664a..5c5345eb02 100644 --- a/crates/nu-path/src/pwd_per_drive.rs +++ b/crates/nu-path/src/pwd_per_drive.rs @@ -4,62 +4,6 @@ /// use std::path::Path; -/// Helper to check if input path is relative path -/// with drive letter, it can be expanded with PWD-per-drive. -/// ``` -/// use nu_path::need_expand; -/// assert!(need_expand(r"c:nushell\src")); -/// assert!(need_expand("C:src/")); -/// assert!(need_expand("a:")); -/// assert!(need_expand("z:")); -/// // Absolute path does not need expand -/// assert!(!need_expand(r"c:\")); -/// // Unix path does not need expand -/// assert!(!need_expand("/usr/bin")); -/// ``` -pub fn need_expand(path: &str) -> bool { - let chars: Vec = path.chars().collect(); - if chars.len() >= 2 { - chars[1] == ':' && (chars.len() == 2 || (chars[2] != '/' && chars[2] != '\\')) - } else { - false - } -} - -/// Get windows env var for drive -/// ``` -/// use nu_path::env_var_for_drive; -/// -/// for drive_letter in 'A'..='Z' { -/// assert_eq!(env_var_for_drive(drive_letter), format!("={drive_letter}:")); -/// } -/// for drive_letter in 'a'..='z' { -/// assert_eq!( -/// env_var_for_drive(drive_letter), -/// format!("={}:", drive_letter.to_ascii_uppercase()) -/// ); -/// } -/// -/// ``` -pub fn env_var_for_drive(drive_letter: char) -> String { - let drive_letter = drive_letter.to_ascii_uppercase(); - format!("={}:", drive_letter) -} - -/// Helper to extract the drive letter from a path, keep case -/// ``` -/// use nu_path::extract_drive_letter; -/// use std::path::Path; -/// -/// assert_eq!(extract_drive_letter(Path::new("C:test")), Some('C')); -/// assert_eq!(extract_drive_letter(Path::new(r"d:\temp")), Some('d')); -/// ``` -pub fn extract_drive_letter(path: &Path) -> Option { - path.to_str() - .and_then(|s| s.chars().next()) - .filter(|c| c.is_ascii_alphabetic()) -} - /// Ensure a path has a trailing `\\` or '/' /// ``` /// use nu_path::ensure_trailing_delimiter; diff --git a/crates/nu-protocol/src/engine/mod.rs b/crates/nu-protocol/src/engine/mod.rs index a8a92082fd..400999cf71 100644 --- a/crates/nu-protocol/src/engine/mod.rs +++ b/crates/nu-protocol/src/engine/mod.rs @@ -29,9 +29,9 @@ pub use engine_state::*; pub use error_handler::*; pub use overlay::*; pub use pattern_match::*; -pub use pwd_per_drive_helper::expand_path_with; +pub use pwd_per_drive_helper::fs_client::*; //expand_path_with #[cfg(windows)] -pub use pwd_per_drive_helper::{expand_pwd, set_pwd}; +pub use pwd_per_drive_helper::os_windows::*; //{expand_pwd, set_pwd}; pub use sequence::*; pub use stack::*; pub use stack_out_dest::*; diff --git a/crates/nu-protocol/src/engine/pwd_per_drive_helper.rs b/crates/nu-protocol/src/engine/pwd_per_drive_helper.rs index 1b6ff3ee5e..b3593af669 100644 --- a/crates/nu-protocol/src/engine/pwd_per_drive_helper.rs +++ b/crates/nu-protocol/src/engine/pwd_per_drive_helper.rs @@ -2,156 +2,279 @@ use crate::engine::{EngineState, Stack}; #[cfg(windows)] use crate::{Span, Value}; #[cfg(windows)] -use nu_path::{ - bash_strip_redundant_quotes, ensure_trailing_delimiter, env_var_for_drive, - extract_drive_letter, get_full_path_name_w, need_expand, -}; +use nu_path::{bash_strip_redundant_quotes, ensure_trailing_delimiter, get_full_path_name_w}; use std::path::{Path, PathBuf}; #[cfg(windows)] -pub fn set_pwd(stack: &mut Stack, path: &Path) { - if let Some(drive) = extract_drive_letter(path) { - let value = Value::string(path.to_string_lossy(), Span::unknown()); - stack.add_env_var(env_var_for_drive(drive), value.clone()); - } -} +pub mod os_windows { + use super::*; -// get pwd for drive: -// 1. From env_var, if no, -// 2. From sys_absolute, if no, -// 3. Construct root path to drives -#[cfg(windows)] -fn get_pwd_on_drive(stack: &Stack, engine_state: &EngineState, drive_letter: char) -> String { - let env_var_for_drive = env_var_for_drive(drive_letter); - let mut abs_pwd: Option = None; - if let Some(pwd) = stack.get_env_var(engine_state, &env_var_for_drive) { - if let Ok(pwd_string) = pwd.clone().into_string() { - abs_pwd = Some(pwd_string); + // For maintainer to notify current pwd + pub mod maintainer { + use super::*; + + /// when user change current directory, maintainer nofity + /// PWD-per-drive by calling set_pwd() with current stack and path; + pub fn set_pwd(stack: &mut Stack, path: &Path) { + use implementation::{env_var_for_drive, extract_drive_letter}; + + if let Some(drive) = extract_drive_letter(path) { + let value = Value::string(path.to_string_lossy(), Span::unknown()); + stack.add_env_var(env_var_for_drive(drive), value.clone()); + } } } - if abs_pwd.is_none() { - if let Some(sys_pwd) = get_full_path_name_w(&format!("{}:", drive_letter)) { - abs_pwd = Some(sys_pwd); - } - } - if let Some(pwd) = abs_pwd { - ensure_trailing_delimiter(&pwd) - } else { - format!(r"{}:\", drive_letter) - } -} -#[cfg(windows)] -pub fn expand_pwd(stack: &Stack, engine_state: &EngineState, path: &Path) -> Option { - if let Some(path_str) = path.to_str() { - if let Some(path_string) = bash_strip_redundant_quotes(path_str) { - if need_expand(&path_string) { - if let Some(drive_letter) = extract_drive_letter(Path::new(&path_string)) { - let mut base = - PathBuf::from(get_pwd_on_drive(stack, engine_state, drive_letter)); - // Combine PWD with the relative path - // need_expand() and extract_drive_letter() all ensure path_str.len() >= 2 - base.push(&path_string[2..]); // Join PWD with path parts after "C:" - return Some(base); + // For file system command usage + pub mod fs_client { + use super::*; + + /// file system command implementation can use expand_pwd to expand relate path for a drive + /// and strip redundant double or single quote like bash + /// expand_pwd(stack, engine_state, Path::new("''C:''nushell''") -> + /// Some(PathBuf("C:\\User\\nushell"); + pub fn expand_pwd( + stack: &Stack, + engine_state: &EngineState, + path: &Path, + ) -> Option { + use implementation::{extract_drive_letter, get_pwd_on_drive, need_expand}; + + if let Some(path_str) = path.to_str() { + if let Some(path_string) = bash_strip_redundant_quotes(path_str) { + if need_expand(&path_string) { + if let Some(drive_letter) = extract_drive_letter(Path::new(&path_string)) { + let mut base = + PathBuf::from(get_pwd_on_drive(stack, engine_state, drive_letter)); + // Combine PWD with the relative path + // need_expand() and extract_drive_letter() all ensure path_str.len() >= 2 + base.push(&path_string[2..]); // Join PWD with path parts after "C:" + return Some(base); + } + } + if path_string != path_str { + return Some(PathBuf::from(&path_string)); + } } } - if path_string != path_str { - return Some(PathBuf::from(&path_string)); - } + None + } + } + + // Implementation for maintainer and fs_client + pub(in crate::engine::pwd_per_drive_helper) mod implementation { + use super::*; + + // get pwd for drive: + // 1. From env_var, if no, + // 2. From sys_absolute, if no, + // 3. Construct root path to drives + pub fn get_pwd_on_drive( + stack: &Stack, + engine_state: &EngineState, + drive_letter: char, + ) -> String { + let env_var_for_drive = env_var_for_drive(drive_letter); + let mut abs_pwd: Option = None; + if let Some(pwd) = stack.get_env_var(engine_state, &env_var_for_drive) { + if let Ok(pwd_string) = pwd.clone().into_string() { + abs_pwd = Some(pwd_string); + } + } + if abs_pwd.is_none() { + if let Some(sys_pwd) = get_full_path_name_w(&format!("{}:", drive_letter)) { + abs_pwd = Some(sys_pwd); + } + } + if let Some(pwd) = abs_pwd { + ensure_trailing_delimiter(&pwd) + } else { + format!(r"{}:\", drive_letter) + } + } + + /// Helper to check if input path is relative path + /// with drive letter, it can be expanded with PWD-per-drive. + pub fn need_expand(path: &str) -> bool { + let chars: Vec = path.chars().collect(); + if chars.len() >= 2 { + chars[1] == ':' && (chars.len() == 2 || (chars[2] != '/' && chars[2] != '\\')) + } else { + false + } + } + + /// Get windows env var for drive + pub fn env_var_for_drive(drive_letter: char) -> String { + let drive_letter = drive_letter.to_ascii_uppercase(); + format!("={}:", drive_letter) + } + + /// Helper to extract the drive letter from a path, keep case + pub fn extract_drive_letter(path: &Path) -> Option { + path.to_str() + .and_then(|s| s.chars().next()) + .filter(|c| c.is_ascii_alphabetic()) } } - None } -// Helper stub/proxy for nu_path::expand_path_with::(path, relative_to, expand_tilde) -// Facilitates file system commands to easily gain the ability to expand PWD-per-drive -pub fn expand_path_with( - _stack: &Stack, - _engine_state: &EngineState, - path: P, - relative_to: Q, - expand_tilde: bool, -) -> PathBuf -where - P: AsRef, - Q: AsRef, -{ - #[cfg(windows)] - if let Some(abs_path) = expand_pwd(_stack, _engine_state, path.as_ref()) { - return abs_path; - } +// For file system command usage +pub mod fs_client { + use super::*; - nu_path::expand_path_with::(path, relative_to, expand_tilde) + // Helper stub/proxy for nu_path::expand_path_with::(path, relative_to, expand_tilde) + // Facilitates file system commands to easily gain the ability to expand PWD-per-drive + pub fn expand_path_with( + _stack: &Stack, + _engine_state: &EngineState, + path: P, + relative_to: Q, + expand_tilde: bool, + ) -> PathBuf + where + P: AsRef, + Q: AsRef, + { + #[cfg(windows)] + if let Some(abs_path) = + os_windows::fs_client::expand_pwd(_stack, _engine_state, path.as_ref()) + { + return abs_path; + } + + nu_path::expand_path_with::(path, relative_to, expand_tilde) + } } #[cfg(windows)] -#[cfg(test)] +#[cfg(test)] // test only for windows mod tests { use super::*; - #[test] - fn test_set_pwd() { - let mut stack = Stack::new(); - let path_str = r"c:\uesrs\nushell"; - let path = Path::new(path_str); - set_pwd(&mut stack, path); - let engine_state = EngineState::new(); - assert_eq!( - stack - .get_env_var(&engine_state, &env_var_for_drive('c')) - .unwrap() - .clone() - .into_string() - .unwrap(), - path_str.to_string() - ); - } + mod fs_client_test { + use super::*; - #[test] - fn test_get_pwd_on_drive() { - let mut stack = Stack::new(); - let path_str = r"c:\users\nushell"; - let path = Path::new(path_str); - set_pwd(&mut stack, path); - let engine_state = EngineState::new(); - let result = format!(r"{path_str}\"); - assert_eq!(result, get_pwd_on_drive(&stack, &engine_state, 'c')); - } + #[test] + fn test_fs_client_expand_path_with() { + let mut stack = Stack::new(); + let path_str = r"c:\users\nushell"; + let path = Path::new(path_str); + os_windows::maintainer::set_pwd(&mut stack, path); + let engine_state = EngineState::new(); - #[test] - fn test_expand_pwd() { - let mut stack = Stack::new(); - let path_str = r"c:\users\nushell"; - let path = Path::new(path_str); - set_pwd(&mut stack, path); - let engine_state = EngineState::new(); - - let rel_path = Path::new("c:.config"); - let result = format!(r"{path_str}\.config"); - assert_eq!( - Some(result.as_str()), - expand_pwd(&stack, &engine_state, rel_path) - .unwrap() + let rel_path = Path::new("c:.config"); + let result = format!(r"{path_str}\.config"); + assert_eq!( + Some(result.as_str()), + fs_client::expand_path_with( + &stack, + &engine_state, + rel_path, + Path::new(path_str), + false + ) .as_path() .to_str() - ); + ); + } } - #[test] - fn test_expand_path_with() { - let mut stack = Stack::new(); - let path_str = r"c:\users\nushell"; - let path = Path::new(path_str); - set_pwd(&mut stack, path); - let engine_state = EngineState::new(); + mod os_windows_tests { + use super::*; - let rel_path = Path::new("c:.config"); - let result = format!(r"{path_str}\.config"); - assert_eq!( - Some(result.as_str()), - expand_path_with(&stack, &engine_state, rel_path, Path::new(path_str), false) - .as_path() - .to_str() - ); + #[test] + fn test_os_windows_maintainer_set_pwd() { + let mut stack = Stack::new(); + let path_str = r"c:\uesrs\nushell"; + let path = Path::new(path_str); + os_windows::maintainer::set_pwd(&mut stack, path); + let engine_state = EngineState::new(); + assert_eq!( + stack + .get_env_var( + &engine_state, + &os_windows::implementation::env_var_for_drive('c') + ) + .unwrap() + .clone() + .into_string() + .unwrap(), + path_str.to_string() + ); + } + + #[test] + fn test_os_windows_fs_client_expand_pwd() { + let mut stack = Stack::new(); + let path_str = r"c:\users\nushell"; + let path = Path::new(path_str); + os_windows::maintainer::set_pwd(&mut stack, path); + let engine_state = EngineState::new(); + + let rel_path = Path::new("c:.config"); + let result = format!(r"{path_str}\.config"); + assert_eq!( + Some(result.as_str()), + os_windows::fs_client::expand_pwd(&stack, &engine_state, rel_path) + .unwrap() + .as_path() + .to_str() + ); + } + + mod implementation_test { + use super::*; + #[test] + fn test_os_windows_implementation_get_pwd_on_drive() { + let mut stack = Stack::new(); + let path_str = r"c:\users\nushell"; + let path = Path::new(path_str); + os_windows::maintainer::set_pwd(&mut stack, path); + let engine_state = EngineState::new(); + let result = format!(r"{path_str}\"); + assert_eq!( + result, + os_windows::implementation::get_pwd_on_drive(&stack, &engine_state, 'c') + ); + } + + #[test] + fn test_os_windows_implementation_need_expand() { + use os_windows::implementation::need_expand; + + assert!(need_expand(r"c:nushell\src")); + assert!(need_expand("C:src/")); + assert!(need_expand("a:")); + assert!(need_expand("z:")); + // Absolute path does not need expand + assert!(!need_expand(r"c:\")); + // Unix path does not need expand + assert!(!need_expand("/usr/bin")); + } + + #[test] + fn test_os_windows_implementation_env_var_for_drive() { + use os_windows::implementation::env_var_for_drive; + + for drive_letter in 'A'..='Z' { + assert_eq!(env_var_for_drive(drive_letter), format!("={drive_letter}:")); + } + for drive_letter in 'a'..='z' { + assert_eq!( + env_var_for_drive(drive_letter), + format!("={}:", drive_letter.to_ascii_uppercase()) + ); + } + } + + #[test] + fn test_os_windows_implementation_extract_drive_letter() { + use os_windows::implementation::extract_drive_letter; + + assert_eq!(extract_drive_letter(Path::new("C:test")), Some('C')); + assert_eq!(extract_drive_letter(Path::new(r"d:\temp")), Some('d')); + } + } } } diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index fe4ab34cc1..4905474e35 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -1,5 +1,5 @@ #[cfg(windows)] -use crate::engine::set_pwd; +use crate::engine::pwd_per_drive_helper::*; use crate::{ engine::{ ArgumentStack, EngineState, ErrorHandlerStack, Redirection, StackCallArgGuard, @@ -764,7 +764,7 @@ impl Stack { let value = Value::string(path.to_string_lossy(), Span::unknown()); self.add_env_var("PWD".into(), value); #[cfg(windows)] // Sync with PWD-per-drive - set_pwd(self, &path); + os_windows::maintainer::set_pwd(self, &path); Ok(()) } }