2023-04-02 18:28:36 +02:00
|
|
|
use nu_protocol::{
|
|
|
|
engine::{EngineState, Stack, StateWorkingSet},
|
Add `command_prelude` module (#12291)
# Description
When implementing a `Command`, one must also import all the types
present in the function signatures for `Command`. This makes it so that
we often import the same set of types in each command implementation
file. E.g., something like this:
```rust
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, Type, Value,
};
```
This PR adds the `nu_engine::command_prelude` module which contains the
necessary and commonly used types to implement a `Command`:
```rust
// command_prelude.rs
pub use crate::CallExt;
pub use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, IntoSpanned,
PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
};
```
This should reduce the boilerplate needed to implement a command and
also gives us a place to track the breadth of the `Command` API. I tried
to be conservative with what went into the prelude modules, since it
might be hard/annoying to remove items from the prelude in the future.
Let me know if something should be included or excluded.
2024-03-26 22:17:30 +01:00
|
|
|
report_error, Range, ShellError, Span, Value,
|
2023-04-02 18:28:36 +02:00
|
|
|
};
|
2024-04-06 16:04:56 +02:00
|
|
|
use std::{ops::Bound, path::PathBuf};
|
2023-03-20 05:05:22 +01:00
|
|
|
|
|
|
|
pub fn get_init_cwd() -> PathBuf {
|
|
|
|
std::env::current_dir().unwrap_or_else(|_| {
|
|
|
|
std::env::var("PWD")
|
|
|
|
.map(Into::into)
|
|
|
|
.unwrap_or_else(|_| nu_path::home_dir().unwrap_or_default())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
|
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)]
|
2023-03-20 05:05:22 +01:00
|
|
|
nu_engine::env::current_dir(engine_state, stack).unwrap_or_else(|e| {
|
|
|
|
let working_set = StateWorkingSet::new(engine_state);
|
|
|
|
report_error(&working_set, &e);
|
|
|
|
crate::util::get_init_cwd()
|
|
|
|
})
|
|
|
|
}
|
2023-04-02 18:28:36 +02:00
|
|
|
|
|
|
|
type MakeRangeError = fn(&str, Span) -> ShellError;
|
|
|
|
|
|
|
|
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
|
2024-04-06 16:04:56 +02:00
|
|
|
match range {
|
|
|
|
Range::IntRange(range) => {
|
|
|
|
let start = range.start().try_into().unwrap_or(0);
|
|
|
|
let end = match range.end() {
|
|
|
|
Bound::Included(v) => v as isize,
|
|
|
|
Bound::Excluded(v) => (v - 1) as isize,
|
|
|
|
Bound::Unbounded => isize::MAX,
|
|
|
|
};
|
|
|
|
Ok((start, end))
|
2023-04-02 18:28:36 +02:00
|
|
|
}
|
2024-04-06 16:04:56 +02:00
|
|
|
Range::FloatRange(_) => Err(|msg, span| ShellError::TypeMismatch {
|
|
|
|
err_message: msg.to_string(),
|
|
|
|
span,
|
|
|
|
}),
|
|
|
|
}
|
2023-04-02 18:28:36 +02:00
|
|
|
}
|
2023-09-29 16:36:03 +02:00
|
|
|
|
|
|
|
const HELP_MSG: &str = "Nushell's config file can be found with the command: $nu.config-path. \
|
|
|
|
For more help: (https://nushell.sh/book/configuration.html#configurations-with-built-in-commands)";
|
|
|
|
|
|
|
|
fn get_editor_commandline(
|
|
|
|
value: &Value,
|
|
|
|
var_name: &str,
|
|
|
|
) -> Result<(String, Vec<String>), ShellError> {
|
|
|
|
match value {
|
|
|
|
Value::String { val, .. } if !val.is_empty() => Ok((val.to_string(), Vec::new())),
|
|
|
|
Value::List { vals, .. } if !vals.is_empty() => {
|
2024-02-17 19:14:16 +01:00
|
|
|
let mut editor_cmd = vals.iter().map(|l| l.coerce_string());
|
2023-09-29 16:36:03 +02:00
|
|
|
match editor_cmd.next().transpose()? {
|
|
|
|
Some(editor) if !editor.is_empty() => {
|
|
|
|
let params = editor_cmd.collect::<Result<_, ShellError>>()?;
|
|
|
|
Ok((editor, params))
|
|
|
|
}
|
2023-12-07 00:40:03 +01:00
|
|
|
_ => Err(ShellError::GenericError {
|
|
|
|
error: "Editor executable is missing".into(),
|
|
|
|
msg: "Set the first element to an executable".into(),
|
|
|
|
span: Some(value.span()),
|
|
|
|
help: Some(HELP_MSG.into()),
|
|
|
|
inner: vec![],
|
|
|
|
}),
|
2023-09-29 16:36:03 +02:00
|
|
|
}
|
|
|
|
}
|
2023-12-07 00:40:03 +01:00
|
|
|
Value::String { .. } | Value::List { .. } => Err(ShellError::GenericError {
|
|
|
|
error: format!("{var_name} should be a non-empty string or list<String>"),
|
|
|
|
msg: "Specify an executable here".into(),
|
|
|
|
span: Some(value.span()),
|
|
|
|
help: Some(HELP_MSG.into()),
|
|
|
|
inner: vec![],
|
|
|
|
}),
|
2023-09-29 16:36:03 +02:00
|
|
|
x => Err(ShellError::CantConvert {
|
|
|
|
to_type: "string or list<string>".into(),
|
|
|
|
from_type: x.get_type().to_string(),
|
|
|
|
span: value.span(),
|
|
|
|
help: None,
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_editor(
|
|
|
|
engine_state: &EngineState,
|
2024-03-09 17:55:39 +01:00
|
|
|
stack: &Stack,
|
2023-09-29 16:36:03 +02:00
|
|
|
span: Span,
|
|
|
|
) -> Result<(String, Vec<String>), ShellError> {
|
|
|
|
let config = engine_state.get_config();
|
|
|
|
let env_vars = stack.get_env_vars(engine_state);
|
|
|
|
|
|
|
|
if let Ok(buff_editor) =
|
|
|
|
get_editor_commandline(&config.buffer_editor, "$env.config.buffer_editor")
|
|
|
|
{
|
|
|
|
Ok(buff_editor)
|
|
|
|
} else if let Some(value) = env_vars.get("EDITOR") {
|
|
|
|
get_editor_commandline(value, "$env.EDITOR")
|
|
|
|
} else if let Some(value) = env_vars.get("VISUAL") {
|
|
|
|
get_editor_commandline(value, "$env.VISUAL")
|
|
|
|
} else {
|
2023-12-07 00:40:03 +01:00
|
|
|
Err(ShellError::GenericError {
|
|
|
|
error: "No editor configured".into(),
|
|
|
|
msg:
|
|
|
|
"Please specify one via `$env.config.buffer_editor` or `$env.EDITOR`/`$env.VISUAL`"
|
|
|
|
.into(),
|
|
|
|
span: Some(span),
|
|
|
|
help: Some(HELP_MSG.into()),
|
|
|
|
inner: vec![],
|
|
|
|
})
|
2023-09-29 16:36:03 +02:00
|
|
|
}
|
|
|
|
}
|