mirror of
https://github.com/nushell/nushell.git
synced 2025-07-02 23:51:49 +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:
@ -1,4 +1,4 @@
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_utils::filesystem::{have_permission, PermissionResult};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -20,6 +20,7 @@ impl Command for Cd {
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("cd")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.switch("physical", "use the physical directory structure; resolve symbolic links before processing instances of ..", Some('P'))
|
||||
.optional("path", SyntaxShape::Directory, "The path to change to.")
|
||||
.input_output_types(vec![
|
||||
(Type::Nothing, Type::Nothing),
|
||||
@ -36,8 +37,9 @@ impl Command for Cd {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let physical = call.has_flag(engine_state, stack, "physical")?;
|
||||
let path_val: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
|
||||
let path_val = {
|
||||
if let Some(path) = path_val {
|
||||
@ -53,54 +55,53 @@ impl Command for Cd {
|
||||
let (path, span) = match path_val {
|
||||
Some(v) => {
|
||||
if v.item == "-" {
|
||||
let oldpwd = stack.get_env_var(engine_state, "OLDPWD");
|
||||
|
||||
if let Some(oldpwd) = oldpwd {
|
||||
let path = oldpwd.to_path()?;
|
||||
let path = match nu_path::canonicalize_with(path.clone(), &cwd) {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
return Err(ShellError::DirectoryNotFound {
|
||||
dir: path.to_string_lossy().to_string(),
|
||||
span: v.span,
|
||||
});
|
||||
}
|
||||
};
|
||||
(path.to_string_lossy().to_string(), v.span)
|
||||
if let Some(oldpwd) = stack.get_env_var(engine_state, "OLDPWD") {
|
||||
(oldpwd.to_path()?, v.span)
|
||||
} else {
|
||||
(cwd.to_string_lossy().to_string(), v.span)
|
||||
(cwd, v.span)
|
||||
}
|
||||
} else {
|
||||
// Trim whitespace from the end of path.
|
||||
let path_no_whitespace =
|
||||
&v.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
|
||||
|
||||
let path = match nu_path::canonicalize_with(path_no_whitespace, &cwd) {
|
||||
Ok(p) => {
|
||||
if !p.is_dir() {
|
||||
// If `--physical` is specified, canonicalize the path; otherwise expand the path.
|
||||
let path = if physical {
|
||||
if let Ok(path) = nu_path::canonicalize_with(path_no_whitespace, &cwd) {
|
||||
if !path.is_dir() {
|
||||
return Err(ShellError::NotADirectory { span: v.span });
|
||||
};
|
||||
p
|
||||
}
|
||||
|
||||
// if canonicalize failed, let's check to see if it's abbreviated
|
||||
Err(_) => {
|
||||
path
|
||||
} else {
|
||||
return Err(ShellError::DirectoryNotFound {
|
||||
dir: path_no_whitespace.to_string(),
|
||||
span: v.span,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let path = nu_path::expand_path_with(path_no_whitespace, &cwd, true);
|
||||
if !path.exists() {
|
||||
return Err(ShellError::DirectoryNotFound {
|
||||
dir: path_no_whitespace.to_string(),
|
||||
span: v.span,
|
||||
});
|
||||
};
|
||||
if !path.is_dir() {
|
||||
return Err(ShellError::NotADirectory { span: v.span });
|
||||
};
|
||||
path
|
||||
};
|
||||
(path.to_string_lossy().to_string(), v.span)
|
||||
(path, v.span)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let path = nu_path::expand_tilde("~");
|
||||
(path.to_string_lossy().to_string(), call.head)
|
||||
(path, call.head)
|
||||
}
|
||||
};
|
||||
|
||||
let path_value = Value::string(path.clone(), span);
|
||||
|
||||
// Set OLDPWD.
|
||||
// We're using `Stack::get_env_var()` instead of `EngineState::cwd()` to avoid a conversion roundtrip.
|
||||
if let Some(oldpwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
stack.add_env_var("OLDPWD".into(), oldpwd)
|
||||
}
|
||||
@ -109,11 +110,15 @@ impl Command for Cd {
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
PermissionResult::PermissionOk => {
|
||||
stack.add_env_var("PWD".into(), path_value);
|
||||
stack.add_env_var("PWD".into(), Value::string(path.to_string_lossy(), span));
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
PermissionResult::PermissionDenied(reason) => Err(ShellError::IOError {
|
||||
msg: format!("Cannot change directory to {path}: {reason}"),
|
||||
msg: format!(
|
||||
"Cannot change directory to {}: {}",
|
||||
path.to_string_lossy(),
|
||||
reason
|
||||
),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
use crate::{DirBuilder, DirInfo, FileInfo};
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_glob::Pattern;
|
||||
use nu_protocol::NuGlob;
|
||||
@ -98,6 +99,7 @@ impl Command for Du {
|
||||
let all = call.has_flag(engine_state, stack, "all")?;
|
||||
let deref = call.has_flag(engine_state, stack, "deref")?;
|
||||
let exclude = call.get_flag(engine_state, stack, "exclude")?;
|
||||
#[allow(deprecated)]
|
||||
let current_dir = current_dir(engine_state, stack)?;
|
||||
|
||||
let paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
|
@ -1,3 +1,4 @@
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, env::current_dir};
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
use wax::{Glob as WaxGlob, WalkBehavior, WalkEntry};
|
||||
@ -178,6 +179,7 @@ impl Command for Glob {
|
||||
}
|
||||
};
|
||||
|
||||
#[allow(deprecated)]
|
||||
let path = current_dir(engine_state, stack)?;
|
||||
let path = match nu_path::canonicalize_with(prefix, path) {
|
||||
Ok(path) => path,
|
||||
|
@ -1,6 +1,7 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
use crate::{DirBuilder, DirInfo};
|
||||
use chrono::{DateTime, Local, LocalResult, TimeZone, Utc};
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, env::current_dir};
|
||||
use nu_glob::{MatchOptions, Pattern};
|
||||
use nu_path::expand_to_real_path;
|
||||
@ -91,6 +92,7 @@ impl Command for Ls {
|
||||
let use_mime_type = call.has_flag(engine_state, stack, "mime-type")?;
|
||||
let ctrl_c = engine_state.ctrlc.clone();
|
||||
let call_span = call.head;
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
let args = Args {
|
||||
|
@ -1,3 +1,4 @@
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, env::current_dir};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -90,6 +91,7 @@ impl Command for Mktemp {
|
||||
} else if directory || tmpdir {
|
||||
Some(std::env::temp_dir())
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
Some(current_dir(engine_state, stack)?)
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir, get_eval_block};
|
||||
use nu_protocol::{BufferedReader, DataSource, NuGlob, PipelineMetadata, RawStream};
|
||||
use std::{io::BufReader, path::Path};
|
||||
@ -51,6 +52,7 @@ impl Command for Open {
|
||||
let raw = call.has_flag(engine_state, stack, "raw")?;
|
||||
let call_span = call.head;
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
let eval_block = get_eval_block(engine_state);
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::util::{get_rest_for_glob_pattern, try_interaction};
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, env::current_dir};
|
||||
use nu_glob::MatchOptions;
|
||||
use nu_path::expand_path_with;
|
||||
@ -130,6 +131,7 @@ fn rm(
|
||||
|
||||
let mut unique_argument_check = None;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let currentdir_path = current_dir(engine_state, stack)?;
|
||||
|
||||
let home: Option<String> = nu_path::home_dir().map(|path| {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::progress_bar;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::{
|
||||
@ -85,6 +86,7 @@ impl Command for Save {
|
||||
};
|
||||
|
||||
let span = call.head;
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
let path_arg = call.req::<Spanned<PathBuf>>(engine_state, stack, 0)?;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use filetime::FileTime;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::NuGlob;
|
||||
@ -113,6 +114,7 @@ impl Command for Touch {
|
||||
})?;
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
for (index, glob) in files.into_iter().enumerate() {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use std::path::PathBuf;
|
||||
use uu_cp::{BackupMode, CopyMode, UpdateMode};
|
||||
@ -177,6 +178,7 @@ impl Command for UCp {
|
||||
let target_path = PathBuf::from(&nu_utils::strip_ansi_string_unlikely(
|
||||
target.item.to_string(),
|
||||
));
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let target_path = nu_path::expand_path_with(target_path, &cwd, target.item.is_expand());
|
||||
if target.item.as_ref().ends_with(PATH_SEPARATOR) && !target_path.is_dir() {
|
||||
|
@ -1,3 +1,4 @@
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
|
||||
use uu_mkdir::mkdir;
|
||||
@ -58,6 +59,7 @@ impl Command for UMkdir {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let mut directories = get_rest_for_glob_pattern(engine_state, stack, call, 0)?
|
||||
.into_iter()
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::NuGlob;
|
||||
@ -77,6 +78,7 @@ impl Command for UMv {
|
||||
uu_mv::OverwriteMode::Force
|
||||
};
|
||||
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
if paths.is_empty() {
|
||||
|
@ -5,6 +5,7 @@ use notify_debouncer_full::{
|
||||
EventKind, RecursiveMode, Watcher,
|
||||
},
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir, ClosureEval};
|
||||
use nu_protocol::{
|
||||
engine::{Closure, StateWorkingSet},
|
||||
@ -73,6 +74,7 @@ impl Command for Watch {
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let path_arg: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
|
||||
|
Reference in New Issue
Block a user