Refactor to clear role of code segments

This commit is contained in:
Zhenping Zhao 2024-12-08 17:12:06 -08:00
parent 32ba2e0cc4
commit b0aeab9c99
6 changed files with 268 additions and 199 deletions

View file

@ -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();

View file

@ -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};

View file

@ -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<char> = 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<char> {
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;

View file

@ -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::*;

View file

@ -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<String> = 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<PathBuf> {
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<PathBuf> {
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<String> = 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<char> = 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<char> {
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::<P, Q>(path, relative_to, expand_tilde)
// Facilitates file system commands to easily gain the ability to expand PWD-per-drive
pub fn expand_path_with<P, Q>(
_stack: &Stack,
_engine_state: &EngineState,
path: P,
relative_to: Q,
expand_tilde: bool,
) -> PathBuf
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
#[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::<P, Q>(path, relative_to, expand_tilde)
// Helper stub/proxy for nu_path::expand_path_with::<P, Q>(path, relative_to, expand_tilde)
// Facilitates file system commands to easily gain the ability to expand PWD-per-drive
pub fn expand_path_with<P, Q>(
_stack: &Stack,
_engine_state: &EngineState,
path: P,
relative_to: Q,
expand_tilde: bool,
) -> PathBuf
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
#[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::<P, Q>(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'));
}
}
}
}

View file

@ -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(())
}
}