mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 11:45:50 +02:00
Migrate to a new PWD API (#12603)
This is the first PR towards migrating to a new `$env.PWD` API that returns potentially un-canonicalized paths. Refer to PR #12515 for motivations. ## New API: `EngineState::cwd()` The goal of the new API is to cover both parse-time and runtime use case, and avoid unintentional misuse. It takes an `Option<Stack>` as argument, which if supplied, will search for `$env.PWD` on the stack in additional to the engine state. I think with this design, there's less confusion over parse-time and runtime environments. If you have access to a stack, just supply it; otherwise supply `None`. ## Deprecation of other PWD-related APIs Other APIs are re-implemented using `EngineState::cwd()` and properly documented. They're marked deprecated, but their behavior is unchanged. Unused APIs are deleted, and code that accesses `$env.PWD` directly without using an API is rewritten. Deprecated APIs: * `EngineState::current_work_dir()` * `StateWorkingSet::get_cwd()` * `env::current_dir()` * `env::current_dir_str()` * `env::current_dir_const()` * `env::current_dir_str_const()` Other changes: * `EngineState::get_cwd()` (deleted) * `StateWorkingSet::list_env()` (deleted) * `repl::do_run_cmd()` (rewritten with `env::current_dir_str()`) ## `cd` and `pwd` now use logical paths by default This pulls the changes from PR #12515. It's currently somewhat broken because using non-canonicalized paths exposed a bug in our path normalization logic (Issue #12602). Once that is fixed, this should work. ## Future plans This PR needs some tests. Which test helpers should I use, and where should I put those tests? I noticed that unquoted paths are expanded within `eval_filepath()` and `eval_directory()` before they even reach the `cd` command. This means every paths is expanded twice. Is this intended? Once this PR lands, the plan is to review all usages of the deprecated APIs and migrate them to `EngineState::cwd()`. In the meantime, these usages are annotated with `#[allow(deprecated)]` to avoid breaking CI. --------- Co-authored-by: Jakub Žádník <kubouch@gmail.com>
This commit is contained in:
@ -2,7 +2,7 @@ use crate::ClosureEvalOnce;
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::{
|
||||
ast::{Call, Expr},
|
||||
engine::{EngineState, Stack, StateWorkingSet, PWD_ENV},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Config, ShellError, Span, Value, VarId,
|
||||
};
|
||||
use std::{
|
||||
@ -156,85 +156,56 @@ pub fn env_to_strings(
|
||||
Ok(env_vars_str)
|
||||
}
|
||||
|
||||
/// Shorthand for env_to_string() for PWD with custom error
|
||||
/// Returns the current working directory as a String, which is guaranteed to be canonicalized.
|
||||
/// Unlike `current_dir_str_const()`, this also considers modifications to the current working directory made on the stack.
|
||||
///
|
||||
/// Returns an error if $env.PWD doesn't exist, is not a String, or is not an absolute path.
|
||||
#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
|
||||
pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result<String, ShellError> {
|
||||
if let Some(pwd) = stack.get_env_var(engine_state, PWD_ENV) {
|
||||
// TODO: PWD should be string by default, we don't need to run ENV_CONVERSIONS on it
|
||||
match env_to_string(PWD_ENV, &pwd, engine_state, stack) {
|
||||
Ok(cwd) => {
|
||||
if Path::new(&cwd).is_absolute() {
|
||||
Ok(cwd)
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: "Invalid current directory".into(),
|
||||
msg: format!("The 'PWD' environment variable must be set to an absolute path. Found: '{cwd}'"),
|
||||
span: Some(pwd.span()),
|
||||
help: None,
|
||||
inner: vec![]
|
||||
})
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: "Current directory not found".into(),
|
||||
msg: "".into(),
|
||||
span: None,
|
||||
help: Some("The environment variable 'PWD' was not found. It is required to define the current directory.".into()),
|
||||
inner: vec![],
|
||||
})
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
current_dir(engine_state, stack).map(|path| path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
/// Simplified version of current_dir_str() for constant evaluation
|
||||
/// Returns the current working directory as a String, which is guaranteed to be canonicalized.
|
||||
///
|
||||
/// Returns an error if $env.PWD doesn't exist, is not a String, or is not an absolute path.
|
||||
#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
|
||||
pub fn current_dir_str_const(working_set: &StateWorkingSet) -> Result<String, ShellError> {
|
||||
if let Some(pwd) = working_set.get_env_var(PWD_ENV) {
|
||||
let span = pwd.span();
|
||||
match pwd {
|
||||
Value::String { val, .. } => {
|
||||
if Path::new(val).is_absolute() {
|
||||
Ok(val.clone())
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: "Invalid current directory".into(),
|
||||
msg: format!("The 'PWD' environment variable must be set to an absolute path. Found: '{val}'"),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![]
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::GenericError {
|
||||
error: "PWD is not a string".into(),
|
||||
msg: "".into(),
|
||||
span: None,
|
||||
help: Some(
|
||||
"Cusrrent working directory environment variable 'PWD' must be a string."
|
||||
.into(),
|
||||
),
|
||||
inner: vec![],
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::GenericError{
|
||||
error: "Current directory not found".into(),
|
||||
msg: "".into(),
|
||||
span: None,
|
||||
help: Some("The environment variable 'PWD' was not found. It is required to define the current directory.".into()),
|
||||
inner: vec![],
|
||||
})
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
current_dir_const(working_set).map(|path| path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
/// Calls current_dir_str() and returns the current directory as a PathBuf
|
||||
/// Returns the current working directory, which is guaranteed to be canonicalized.
|
||||
/// Unlike `current_dir_const()`, this also considers modifications to the current working directory made on the stack.
|
||||
///
|
||||
/// Returns an error if $env.PWD doesn't exist, is not a String, or is not an absolute path.
|
||||
#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
|
||||
pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result<PathBuf, ShellError> {
|
||||
current_dir_str(engine_state, stack).map(PathBuf::from)
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
// `EngineState::cwd()` always returns absolute path.
|
||||
// We're using `canonicalize_with` instead of `fs::canonicalize()` because
|
||||
// we still need to simplify Windows paths. "." is safe because `cwd` should
|
||||
// be an absolute path already.
|
||||
canonicalize_with(&cwd, ".").map_err(|_| ShellError::DirectoryNotFound {
|
||||
dir: cwd.to_string_lossy().to_string(),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Version of current_dir() for constant evaluation
|
||||
/// Returns the current working directory, which is guaranteed to be canonicalized.
|
||||
///
|
||||
/// Returns an error if $env.PWD doesn't exist, is not a String, or is not an absolute path.
|
||||
#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
|
||||
pub fn current_dir_const(working_set: &StateWorkingSet) -> Result<PathBuf, ShellError> {
|
||||
current_dir_str_const(working_set).map(PathBuf::from)
|
||||
let cwd = working_set.permanent_state.cwd(None)?;
|
||||
// `EngineState::cwd()` always returns absolute path.
|
||||
// We're using `canonicalize_with` instead of `fs::canonicalize()` because
|
||||
// we still need to simplify Windows paths. "." is safe because `cwd` should
|
||||
// be an absolute path already.
|
||||
canonicalize_with(&cwd, ".").map_err(|_| ShellError::DirectoryNotFound {
|
||||
dir: cwd.to_string_lossy().to_string(),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the contents of path environment variable as a list of strings
|
||||
@ -315,6 +286,7 @@ pub fn find_in_dirs_env(
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
current_dir_str(engine_state, stack)?
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::{current_dir, current_dir_str, get_config, get_full_help};
|
||||
#[allow(deprecated)]
|
||||
use crate::{current_dir, get_config, get_full_help};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::{
|
||||
ast::{
|
||||
@ -325,6 +326,7 @@ fn eval_redirection<D: DebugContext>(
|
||||
) -> Result<Redirection, ShellError> {
|
||||
match target {
|
||||
RedirectionTarget::File { expr, append, .. } => {
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let value = eval_expression::<D>(engine_state, stack, expr)?;
|
||||
let path = Spanned::<PathBuf>::from_value(value)?.item;
|
||||
@ -633,7 +635,7 @@ impl Eval for EvalRuntime {
|
||||
if quoted {
|
||||
Ok(Value::string(path, span))
|
||||
} else {
|
||||
let cwd = current_dir_str(engine_state, stack)?;
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
let path = expand_path_with(path, cwd, true);
|
||||
|
||||
Ok(Value::string(path.to_string_lossy(), span))
|
||||
@ -652,7 +654,7 @@ impl Eval for EvalRuntime {
|
||||
} else if quoted {
|
||||
Ok(Value::string(path, span))
|
||||
} else {
|
||||
let cwd = current_dir_str(engine_state, stack)?;
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
let path = expand_path_with(path, cwd, true);
|
||||
|
||||
Ok(Value::string(path.to_string_lossy(), span))
|
||||
|
Reference in New Issue
Block a user