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>
2024-05-03 13:33:09 +02:00
|
|
|
#[allow(deprecated)]
|
|
|
|
use nu_engine::{command_prelude::*, current_dir};
|
|
|
|
use nu_protocol::{engine::StateWorkingSet, PluginRegistryFile};
|
2024-04-24 13:28:45 +02:00
|
|
|
use std::{
|
|
|
|
fs::{self, File},
|
|
|
|
path::PathBuf,
|
|
|
|
};
|
2024-04-21 14:36:26 +02:00
|
|
|
|
|
|
|
pub(crate) fn modify_plugin_file(
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
|
|
|
span: Span,
|
|
|
|
custom_path: Option<Spanned<String>>,
|
2024-04-25 00:40:39 +02:00
|
|
|
operate: impl FnOnce(&mut PluginRegistryFile) -> Result<(), ShellError>,
|
2024-04-21 14:36:26 +02:00
|
|
|
) -> Result<(), ShellError> {
|
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>
2024-05-03 13:33:09 +02:00
|
|
|
#[allow(deprecated)]
|
2024-04-21 14:36:26 +02:00
|
|
|
let cwd = current_dir(engine_state, stack)?;
|
|
|
|
|
2024-04-25 00:40:39 +02:00
|
|
|
let plugin_registry_file_path = if let Some(ref custom_path) = custom_path {
|
2024-04-21 14:36:26 +02:00
|
|
|
nu_path::expand_path_with(&custom_path.item, cwd, true)
|
|
|
|
} else {
|
|
|
|
engine_state
|
|
|
|
.plugin_path
|
|
|
|
.clone()
|
|
|
|
.ok_or_else(|| ShellError::GenericError {
|
2024-04-25 00:40:39 +02:00
|
|
|
error: "Plugin registry file not set".into(),
|
2024-04-21 14:36:26 +02:00
|
|
|
msg: "pass --plugin-config explicitly here".into(),
|
|
|
|
span: Some(span),
|
|
|
|
help: Some("you may be running `nu` with --no-config-file".into()),
|
|
|
|
inner: vec![],
|
|
|
|
})?
|
|
|
|
};
|
|
|
|
|
2024-06-28 08:49:06 +02:00
|
|
|
let file_span = custom_path.as_ref().map(|p| p.span).unwrap_or(span);
|
|
|
|
|
2024-04-21 14:36:26 +02:00
|
|
|
// Try to read the plugin file if it exists
|
2024-04-25 00:40:39 +02:00
|
|
|
let mut contents = if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) {
|
|
|
|
PluginRegistryFile::read_from(
|
2024-06-28 08:49:06 +02:00
|
|
|
File::open(&plugin_registry_file_path).map_err(|err| ShellError::IOErrorSpanned {
|
|
|
|
msg: format!(
|
|
|
|
"failed to read `{}`: {}",
|
|
|
|
plugin_registry_file_path.display(),
|
|
|
|
err
|
|
|
|
),
|
|
|
|
span: file_span,
|
|
|
|
})?,
|
|
|
|
Some(file_span),
|
2024-04-21 14:36:26 +02:00
|
|
|
)?
|
|
|
|
} else {
|
2024-04-25 00:40:39 +02:00
|
|
|
PluginRegistryFile::default()
|
2024-04-21 14:36:26 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// Do the operation
|
|
|
|
operate(&mut contents)?;
|
|
|
|
|
|
|
|
// Save the modified file on success
|
|
|
|
contents.write_to(
|
2024-06-28 08:49:06 +02:00
|
|
|
File::create(&plugin_registry_file_path).map_err(|err| ShellError::IOErrorSpanned {
|
|
|
|
msg: format!(
|
|
|
|
"failed to create `{}`: {}",
|
|
|
|
plugin_registry_file_path.display(),
|
|
|
|
err
|
|
|
|
),
|
|
|
|
span: file_span,
|
|
|
|
})?,
|
2024-04-21 14:36:26 +02:00
|
|
|
Some(span),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-04-24 13:28:45 +02:00
|
|
|
|
|
|
|
pub(crate) fn canonicalize_possible_filename_arg(
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &Stack,
|
|
|
|
arg: &str,
|
|
|
|
) -> PathBuf {
|
|
|
|
// This results in the best possible chance of a match with the plugin item
|
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>
2024-05-03 13:33:09 +02:00
|
|
|
#[allow(deprecated)]
|
2024-04-24 13:28:45 +02:00
|
|
|
if let Ok(cwd) = nu_engine::current_dir(engine_state, stack) {
|
|
|
|
let path = nu_path::expand_path_with(arg, &cwd, true);
|
|
|
|
// Try to canonicalize
|
|
|
|
nu_path::locate_in_dirs(&path, &cwd, || get_plugin_dirs(engine_state, stack))
|
|
|
|
// If we couldn't locate it, return the expanded path alone
|
|
|
|
.unwrap_or(path)
|
|
|
|
} else {
|
|
|
|
arg.into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn get_plugin_dirs(
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &Stack,
|
|
|
|
) -> impl Iterator<Item = String> {
|
|
|
|
// Get the NU_PLUGIN_DIRS constant or env var
|
|
|
|
let working_set = StateWorkingSet::new(engine_state);
|
|
|
|
let value = working_set
|
|
|
|
.find_variable(b"$NU_PLUGIN_DIRS")
|
|
|
|
.and_then(|var_id| working_set.get_constant(var_id).ok().cloned())
|
|
|
|
.or_else(|| stack.get_env_var(engine_state, "NU_PLUGIN_DIRS"));
|
|
|
|
|
|
|
|
// Get all of the strings in the list, if possible
|
|
|
|
value
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(|value| value.into_list().ok())
|
|
|
|
.flatten()
|
|
|
|
.flat_map(|list_item| list_item.coerce_into_string().ok())
|
|
|
|
}
|