mirror of
https://github.com/nushell/nushell
synced 2024-11-10 15:14:14 +00:00
feat: add a command_not_found
hook (#8314)
# Description Add a `command_not_found` function to `$env.config.hooks`. If this function outputs a string, then it's included in the `help`. An example hook on *Arch Linux*, to find packages that contain the binary, looks like: ```nushell let-env config = { # ... hooks: { command_not_found: { |cmd_name| ( try { let pkgs = (pkgfile --binaries --verbose $cmd_name) ( $"(ansi $env.config.color_config.shape_external)($cmd_name)(ansi reset) " + $"may be found in the following packages:\n($pkgs)" ) } catch { null } ) } # ... ``` # User-Facing Changes - Add a `command_not_found` function to `$env.config.hooks`. # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
parent
e36a2947b9
commit
1d3f6105f5
20 changed files with 447 additions and 394 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2798,6 +2798,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"lscolors",
|
"lscolors",
|
||||||
"md-5",
|
"md-5",
|
||||||
|
"miette",
|
||||||
"mime",
|
"mime",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"mockito",
|
"mockito",
|
||||||
|
|
|
@ -12,10 +12,10 @@ bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.77.2" }
|
nu-test-support = { path = "../nu-test-support", version = "0.77.2" }
|
||||||
nu-command = { path = "../nu-command", version = "0.77.2" }
|
|
||||||
rstest = { version = "0.16.0", default-features = false }
|
rstest = { version = "0.16.0", default-features = false }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
nu-command = { path = "../nu-command", version = "0.77.2" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.77.2" }
|
nu-engine = { path = "../nu-engine", version = "0.77.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.77.2" }
|
nu-path = { path = "../nu-path", version = "0.77.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.77.2" }
|
nu-parser = { path = "../nu-parser", version = "0.77.2" }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::util::report_error;
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use miette::Result;
|
use miette::Result;
|
||||||
|
use nu_command::util::report_error;
|
||||||
use nu_engine::{convert_env_values, eval_block};
|
use nu_engine::{convert_env_values, eval_block};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::engine::Stack;
|
use nu_protocol::engine::Stack;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::util::{eval_source, report_error};
|
use crate::util::eval_source;
|
||||||
|
use nu_command::util::report_error;
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_parser::ParseError;
|
use nu_parser::ParseError;
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::util::{eval_source, report_error};
|
use crate::util::eval_source;
|
||||||
use log::info;
|
use log::info;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
|
use nu_command::util::report_error;
|
||||||
use nu_engine::{convert_env_values, current_dir};
|
use nu_engine::{convert_env_values, current_dir};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
|
|
|
@ -18,13 +18,13 @@ pub use completions::{FileCompletion, NuCompleter};
|
||||||
pub use config_files::eval_config_contents;
|
pub use config_files::eval_config_contents;
|
||||||
pub use eval_file::evaluate_file;
|
pub use eval_file::evaluate_file;
|
||||||
pub use menus::{DescriptionMenu, NuHelpCompleter};
|
pub use menus::{DescriptionMenu, NuHelpCompleter};
|
||||||
|
pub use nu_command::util::{get_init_cwd, report_error, report_error_new};
|
||||||
pub use nu_highlight::NuHighlight;
|
pub use nu_highlight::NuHighlight;
|
||||||
pub use print::Print;
|
pub use print::Print;
|
||||||
pub use prompt::NushellPrompt;
|
pub use prompt::NushellPrompt;
|
||||||
pub use repl::evaluate_repl;
|
pub use repl::evaluate_repl;
|
||||||
pub use repl::{eval_env_change_hook, eval_hook};
|
|
||||||
pub use syntax_highlight::NuHighlighter;
|
pub use syntax_highlight::NuHighlighter;
|
||||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
|
pub use util::{eval_source, gather_parent_env_vars};
|
||||||
pub use validation::NuValidator;
|
pub use validation::NuValidator;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::util::report_error;
|
|
||||||
use crate::NushellPrompt;
|
use crate::NushellPrompt;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
use nu_command::util::report_error;
|
||||||
use nu_engine::eval_subexpression;
|
use nu_engine::eval_subexpression;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
|
|
|
@ -2,21 +2,21 @@ use crate::{
|
||||||
completions::NuCompleter,
|
completions::NuCompleter,
|
||||||
prompt_update,
|
prompt_update,
|
||||||
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
|
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
|
||||||
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
|
util::eval_source,
|
||||||
NuHighlighter, NuValidator, NushellPrompt,
|
NuHighlighter, NuValidator, NushellPrompt,
|
||||||
};
|
};
|
||||||
use crossterm::cursor::CursorShape;
|
use crossterm::cursor::CursorShape;
|
||||||
use log::{trace, warn};
|
use log::{trace, warn};
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
use nu_engine::{convert_env_values, eval_block, eval_block_with_early_return};
|
use nu_command::hook::eval_hook;
|
||||||
|
use nu_command::util::{get_guaranteed_cwd, report_error, report_error_new};
|
||||||
|
use nu_engine::{convert_env_values, eval_block};
|
||||||
use nu_parser::{lex, parse, trim_quotes_str};
|
use nu_parser::{lex, parse, trim_quotes_str};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::PathMember,
|
|
||||||
config::NuCursorShape,
|
config::NuCursorShape,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
format_duration, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, Value,
|
||||||
Spanned, Type, Value, VarId,
|
|
||||||
};
|
};
|
||||||
use nu_utils::utils::perf;
|
use nu_utils::utils::perf;
|
||||||
use reedline::{CursorConfig, DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
use reedline::{CursorConfig, DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
||||||
|
@ -44,6 +44,7 @@ pub fn evaluate_repl(
|
||||||
prerun_command: Option<Spanned<String>>,
|
prerun_command: Option<Spanned<String>>,
|
||||||
entire_start_time: Instant,
|
entire_start_time: Instant,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
use nu_command::hook;
|
||||||
use reedline::{FileBackedHistory, Reedline, Signal};
|
use reedline::{FileBackedHistory, Reedline, Signal};
|
||||||
let use_color = engine_state.get_config().use_ansi_coloring;
|
let use_color = engine_state.get_config().use_ansi_coloring;
|
||||||
|
|
||||||
|
@ -400,7 +401,7 @@ pub fn evaluate_repl(
|
||||||
// fire the "env_change" hook
|
// fire the "env_change" hook
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
if let Err(error) =
|
if let Err(error) =
|
||||||
eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
|
hook::eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
|
||||||
{
|
{
|
||||||
report_error_new(engine_state, &error)
|
report_error_new(engine_state, &error)
|
||||||
}
|
}
|
||||||
|
@ -822,340 +823,6 @@ pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) ->
|
||||||
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
|
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_env_change_hook(
|
|
||||||
env_change_hook: Option<Value>,
|
|
||||||
engine_state: &mut EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
) -> Result<(), ShellError> {
|
|
||||||
if let Some(hook) = env_change_hook {
|
|
||||||
match hook {
|
|
||||||
Value::Record {
|
|
||||||
cols: env_names,
|
|
||||||
vals: hook_values,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
for (env_name, hook_value) in env_names.iter().zip(hook_values.iter()) {
|
|
||||||
let before = engine_state
|
|
||||||
.previous_env_vars
|
|
||||||
.get(env_name)
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let after = stack
|
|
||||||
.get_env_var(engine_state, env_name)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if before != after {
|
|
||||||
eval_hook(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
None,
|
|
||||||
vec![("$before".into(), before), ("$after".into(), after.clone())],
|
|
||||||
hook_value,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
engine_state
|
|
||||||
.previous_env_vars
|
|
||||||
.insert(env_name.to_string(), after);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
x => {
|
|
||||||
return Err(ShellError::TypeMismatch {
|
|
||||||
err_message: "record for the 'env_change' hook".to_string(),
|
|
||||||
span: x.span()?,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval_hook(
|
|
||||||
engine_state: &mut EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
input: Option<PipelineData>,
|
|
||||||
arguments: Vec<(String, Value)>,
|
|
||||||
value: &Value,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let value_span = value.span()?;
|
|
||||||
|
|
||||||
// Hooks can optionally be a record in this form:
|
|
||||||
// {
|
|
||||||
// condition: {|before, after| ... } # block that evaluates to true/false
|
|
||||||
// code: # block or a string
|
|
||||||
// }
|
|
||||||
// The condition block will be run to check whether the main hook (in `code`) should be run.
|
|
||||||
// If it returns true (the default if a condition block is not specified), the hook should be run.
|
|
||||||
let condition_path = PathMember::String {
|
|
||||||
val: "condition".to_string(),
|
|
||||||
span: value_span,
|
|
||||||
optional: false,
|
|
||||||
};
|
|
||||||
let mut output = PipelineData::empty();
|
|
||||||
|
|
||||||
let code_path = PathMember::String {
|
|
||||||
val: "code".to_string(),
|
|
||||||
span: value_span,
|
|
||||||
optional: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
match value {
|
|
||||||
Value::List { vals, .. } => {
|
|
||||||
for val in vals {
|
|
||||||
eval_hook(engine_state, stack, None, arguments.clone(), val)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Record { .. } => {
|
|
||||||
let do_run_hook =
|
|
||||||
if let Ok(condition) = value.clone().follow_cell_path(&[condition_path], false) {
|
|
||||||
match condition {
|
|
||||||
Value::Block {
|
|
||||||
val: block_id,
|
|
||||||
span: block_span,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| Value::Closure {
|
|
||||||
val: block_id,
|
|
||||||
span: block_span,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
match run_hook_block(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
block_id,
|
|
||||||
None,
|
|
||||||
arguments.clone(),
|
|
||||||
block_span,
|
|
||||||
) {
|
|
||||||
Ok(pipeline_data) => {
|
|
||||||
if let PipelineData::Value(Value::Bool { val, .. }, ..) =
|
|
||||||
pipeline_data
|
|
||||||
{
|
|
||||||
val
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
|
||||||
"boolean output".to_string(),
|
|
||||||
"other PipelineData variant".to_string(),
|
|
||||||
block_span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
|
||||||
"block".to_string(),
|
|
||||||
format!("{}", other.get_type()),
|
|
||||||
other.span()?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// always run the hook
|
|
||||||
true
|
|
||||||
};
|
|
||||||
|
|
||||||
if do_run_hook {
|
|
||||||
match value.clone().follow_cell_path(&[code_path], false)? {
|
|
||||||
Value::String {
|
|
||||||
val,
|
|
||||||
span: source_span,
|
|
||||||
} => {
|
|
||||||
let (block, delta, vars) = {
|
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
let mut vars: Vec<(VarId, Value)> = vec![];
|
|
||||||
|
|
||||||
for (name, val) in arguments {
|
|
||||||
let var_id = working_set.add_variable(
|
|
||||||
name.as_bytes().to_vec(),
|
|
||||||
val.span()?,
|
|
||||||
Type::Any,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
vars.push((var_id, val));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (output, err) =
|
|
||||||
parse(&mut working_set, Some("hook"), val.as_bytes(), false, &[]);
|
|
||||||
if let Some(err) = err {
|
|
||||||
report_error(&working_set, &err);
|
|
||||||
|
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
|
||||||
"valid source code".into(),
|
|
||||||
"source code with syntax errors".into(),
|
|
||||||
source_span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
(output, working_set.render(), vars)
|
|
||||||
};
|
|
||||||
|
|
||||||
engine_state.merge_delta(delta)?;
|
|
||||||
let input = PipelineData::empty();
|
|
||||||
|
|
||||||
let var_ids: Vec<VarId> = vars
|
|
||||||
.into_iter()
|
|
||||||
.map(|(var_id, val)| {
|
|
||||||
stack.add_var(var_id, val);
|
|
||||||
var_id
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
|
||||||
Ok(pipeline_data) => {
|
|
||||||
output = pipeline_data;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
report_error_new(engine_state, &err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for var_id in var_ids.iter() {
|
|
||||||
stack.vars.remove(var_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Block {
|
|
||||||
val: block_id,
|
|
||||||
span: block_span,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
run_hook_block(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
block_id,
|
|
||||||
input,
|
|
||||||
arguments,
|
|
||||||
block_span,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Value::Closure {
|
|
||||||
val: block_id,
|
|
||||||
span: block_span,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
run_hook_block(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
block_id,
|
|
||||||
input,
|
|
||||||
arguments,
|
|
||||||
block_span,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
|
||||||
"block or string".to_string(),
|
|
||||||
format!("{}", other.get_type()),
|
|
||||||
other.span()?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Block {
|
|
||||||
val: block_id,
|
|
||||||
span: block_span,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
output = run_hook_block(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
*block_id,
|
|
||||||
input,
|
|
||||||
arguments,
|
|
||||||
*block_span,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Value::Closure {
|
|
||||||
val: block_id,
|
|
||||||
span: block_span,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
output = run_hook_block(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
*block_id,
|
|
||||||
input,
|
|
||||||
arguments,
|
|
||||||
*block_span,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
|
||||||
"block, record, or list of records".into(),
|
|
||||||
format!("{}", other.get_type()),
|
|
||||||
other.span()?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
|
||||||
engine_state.merge_env(stack, cwd)?;
|
|
||||||
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_hook_block(
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
block_id: BlockId,
|
|
||||||
optional_input: Option<PipelineData>,
|
|
||||||
arguments: Vec<(String, Value)>,
|
|
||||||
span: Span,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let block = engine_state.get_block(block_id);
|
|
||||||
|
|
||||||
let input = optional_input.unwrap_or_else(PipelineData::empty);
|
|
||||||
|
|
||||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
|
||||||
|
|
||||||
for (idx, PositionalArg { var_id, .. }) in
|
|
||||||
block.signature.required_positional.iter().enumerate()
|
|
||||||
{
|
|
||||||
if let Some(var_id) = var_id {
|
|
||||||
if let Some(arg) = arguments.get(idx) {
|
|
||||||
callee_stack.add_var(*var_id, arg.1.clone())
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::IncompatibleParametersSingle {
|
|
||||||
msg: "This hook block has too many parameters".into(),
|
|
||||||
span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let pipeline_data =
|
|
||||||
eval_block_with_early_return(engine_state, &mut callee_stack, block, input, false, false)?;
|
|
||||||
|
|
||||||
if let PipelineData::Value(Value::Error { error }, _) = pipeline_data {
|
|
||||||
return Err(*error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all went fine, preserve the environment of the called block
|
|
||||||
let caller_env_vars = stack.get_env_var_names(engine_state);
|
|
||||||
|
|
||||||
// remove env vars that are present in the caller but not in the callee
|
|
||||||
// (the callee hid them)
|
|
||||||
for var in caller_env_vars.iter() {
|
|
||||||
if !callee_stack.has_env_var(engine_state, var) {
|
|
||||||
stack.remove_env_var(engine_state, var);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new env vars from callee to caller
|
|
||||||
for (var, value) in callee_stack.get_stack_env_vars() {
|
|
||||||
stack.add_env_var(var, value);
|
|
||||||
}
|
|
||||||
Ok(pipeline_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
||||||
io::stdout().write_all(seq.as_bytes()).map_err(|e| {
|
io::stdout().write_all(seq.as_bytes()).map_err(|e| {
|
||||||
ShellError::GenericError(
|
ShellError::GenericError(
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::repl::eval_hook;
|
use nu_command::hook::eval_hook;
|
||||||
|
use nu_command::util::{report_error, report_error_new};
|
||||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||||
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use nu_protocol::CliError;
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
print_if_stream, PipelineData, ShellError, Span, Value,
|
print_if_stream, PipelineData, ShellError, Span, Value,
|
||||||
|
@ -10,7 +10,7 @@ use nu_protocol::{
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use nu_utils::enable_vt_processing;
|
use nu_utils::enable_vt_processing;
|
||||||
use nu_utils::utils::perf;
|
use nu_utils::utils::perf;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
|
|
||||||
// This will collect environment variables from std::env and adds them to a stack.
|
// This will collect environment variables from std::env and adds them to a stack.
|
||||||
//
|
//
|
||||||
|
@ -310,43 +310,6 @@ fn set_last_exit_code(stack: &mut Stack, exit_code: i64) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_error(
|
|
||||||
working_set: &StateWorkingSet,
|
|
||||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
|
||||||
) {
|
|
||||||
eprintln!("Error: {:?}", CliError(error, working_set));
|
|
||||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
let _ = nu_utils::enable_vt_processing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_error_new(
|
|
||||||
engine_state: &EngineState,
|
|
||||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
|
||||||
) {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
report_error(&working_set, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
nu_engine::env::current_dir(engine_state, stack).unwrap_or_else(|e| {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &e);
|
|
||||||
get_init_cwd()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -60,6 +60,7 @@ itertools = "0.10.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false }
|
lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false }
|
||||||
md5 = { package = "md-5", version = "0.10.0" }
|
md5 = { package = "md-5", version = "0.10.0" }
|
||||||
|
miette = { version = "5.5.0", features = ["fancy-no-backtrace"] }
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
mime_guess = "2.0.4"
|
mime_guess = "2.0.4"
|
||||||
notify = "4.0.17"
|
notify = "4.0.17"
|
||||||
|
|
341
crates/nu-command/src/hook.rs
Normal file
341
crates/nu-command/src/hook.rs
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
use crate::util::{get_guaranteed_cwd, report_error, report_error_new};
|
||||||
|
use miette::Result;
|
||||||
|
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||||
|
use nu_parser::parse;
|
||||||
|
use nu_protocol::ast::PathMember;
|
||||||
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
|
use nu_protocol::{BlockId, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId};
|
||||||
|
|
||||||
|
pub fn eval_env_change_hook(
|
||||||
|
env_change_hook: Option<Value>,
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
) -> Result<(), ShellError> {
|
||||||
|
if let Some(hook) = env_change_hook {
|
||||||
|
match hook {
|
||||||
|
Value::Record {
|
||||||
|
cols: env_names,
|
||||||
|
vals: hook_values,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
for (env_name, hook_value) in env_names.iter().zip(hook_values.iter()) {
|
||||||
|
let before = engine_state
|
||||||
|
.previous_env_vars
|
||||||
|
.get(env_name)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let after = stack
|
||||||
|
.get_env_var(engine_state, env_name)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if before != after {
|
||||||
|
eval_hook(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
None,
|
||||||
|
vec![("$before".into(), before), ("$after".into(), after.clone())],
|
||||||
|
hook_value,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
engine_state
|
||||||
|
.previous_env_vars
|
||||||
|
.insert(env_name.to_string(), after);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x => {
|
||||||
|
return Err(ShellError::TypeMismatch {
|
||||||
|
err_message: "record for the 'env_change' hook".to_string(),
|
||||||
|
span: x.span()?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_hook(
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
input: Option<PipelineData>,
|
||||||
|
arguments: Vec<(String, Value)>,
|
||||||
|
value: &Value,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let value_span = value.span()?;
|
||||||
|
|
||||||
|
// Hooks can optionally be a record in this form:
|
||||||
|
// {
|
||||||
|
// condition: {|before, after| ... } # block that evaluates to true/false
|
||||||
|
// code: # block or a string
|
||||||
|
// }
|
||||||
|
// The condition block will be run to check whether the main hook (in `code`) should be run.
|
||||||
|
// If it returns true (the default if a condition block is not specified), the hook should be run.
|
||||||
|
let condition_path = PathMember::String {
|
||||||
|
val: "condition".to_string(),
|
||||||
|
span: value_span,
|
||||||
|
optional: false,
|
||||||
|
};
|
||||||
|
let mut output = PipelineData::empty();
|
||||||
|
|
||||||
|
let code_path = PathMember::String {
|
||||||
|
val: "code".to_string(),
|
||||||
|
span: value_span,
|
||||||
|
optional: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Value::List { vals, .. } => {
|
||||||
|
for val in vals {
|
||||||
|
eval_hook(engine_state, stack, None, arguments.clone(), val)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Record { .. } => {
|
||||||
|
let do_run_hook =
|
||||||
|
if let Ok(condition) = value.clone().follow_cell_path(&[condition_path], false) {
|
||||||
|
match condition {
|
||||||
|
Value::Block {
|
||||||
|
val: block_id,
|
||||||
|
span: block_span,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Value::Closure {
|
||||||
|
val: block_id,
|
||||||
|
span: block_span,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
match run_hook_block(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
block_id,
|
||||||
|
None,
|
||||||
|
arguments.clone(),
|
||||||
|
block_span,
|
||||||
|
) {
|
||||||
|
Ok(pipeline_data) => {
|
||||||
|
if let PipelineData::Value(Value::Bool { val, .. }, ..) =
|
||||||
|
pipeline_data
|
||||||
|
{
|
||||||
|
val
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
|
"boolean output".to_string(),
|
||||||
|
"other PipelineData variant".to_string(),
|
||||||
|
block_span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
|
"block".to_string(),
|
||||||
|
format!("{}", other.get_type()),
|
||||||
|
other.span()?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// always run the hook
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
if do_run_hook {
|
||||||
|
match value.clone().follow_cell_path(&[code_path], false)? {
|
||||||
|
Value::String {
|
||||||
|
val,
|
||||||
|
span: source_span,
|
||||||
|
} => {
|
||||||
|
let (block, delta, vars) = {
|
||||||
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
let mut vars: Vec<(VarId, Value)> = vec![];
|
||||||
|
|
||||||
|
for (name, val) in arguments {
|
||||||
|
let var_id = working_set.add_variable(
|
||||||
|
name.as_bytes().to_vec(),
|
||||||
|
val.span()?,
|
||||||
|
Type::Any,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
vars.push((var_id, val));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (output, err) =
|
||||||
|
parse(&mut working_set, Some("hook"), val.as_bytes(), false, &[]);
|
||||||
|
if let Some(err) = err {
|
||||||
|
report_error(&working_set, &err);
|
||||||
|
|
||||||
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
|
"valid source code".into(),
|
||||||
|
"source code with syntax errors".into(),
|
||||||
|
source_span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
(output, working_set.render(), vars)
|
||||||
|
};
|
||||||
|
|
||||||
|
engine_state.merge_delta(delta)?;
|
||||||
|
let input = PipelineData::empty();
|
||||||
|
|
||||||
|
let var_ids: Vec<VarId> = vars
|
||||||
|
.into_iter()
|
||||||
|
.map(|(var_id, val)| {
|
||||||
|
stack.add_var(var_id, val);
|
||||||
|
var_id
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||||
|
Ok(pipeline_data) => {
|
||||||
|
output = pipeline_data;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
report_error_new(engine_state, &err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for var_id in var_ids.iter() {
|
||||||
|
stack.vars.remove(var_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Block {
|
||||||
|
val: block_id,
|
||||||
|
span: block_span,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
run_hook_block(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
block_id,
|
||||||
|
input,
|
||||||
|
arguments,
|
||||||
|
block_span,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Value::Closure {
|
||||||
|
val: block_id,
|
||||||
|
span: block_span,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
run_hook_block(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
block_id,
|
||||||
|
input,
|
||||||
|
arguments,
|
||||||
|
block_span,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
|
"block or string".to_string(),
|
||||||
|
format!("{}", other.get_type()),
|
||||||
|
other.span()?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Block {
|
||||||
|
val: block_id,
|
||||||
|
span: block_span,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
output = run_hook_block(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
*block_id,
|
||||||
|
input,
|
||||||
|
arguments,
|
||||||
|
*block_span,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Value::Closure {
|
||||||
|
val: block_id,
|
||||||
|
span: block_span,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
output = run_hook_block(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
*block_id,
|
||||||
|
input,
|
||||||
|
arguments,
|
||||||
|
*block_span,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
|
"block, record, or list of records".into(),
|
||||||
|
format!("{}", other.get_type()),
|
||||||
|
other.span()?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||||
|
engine_state.merge_env(stack, cwd)?;
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_hook_block(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
block_id: BlockId,
|
||||||
|
optional_input: Option<PipelineData>,
|
||||||
|
arguments: Vec<(String, Value)>,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let block = engine_state.get_block(block_id);
|
||||||
|
|
||||||
|
let input = optional_input.unwrap_or_else(PipelineData::empty);
|
||||||
|
|
||||||
|
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||||
|
|
||||||
|
for (idx, PositionalArg { var_id, .. }) in
|
||||||
|
block.signature.required_positional.iter().enumerate()
|
||||||
|
{
|
||||||
|
if let Some(var_id) = var_id {
|
||||||
|
if let Some(arg) = arguments.get(idx) {
|
||||||
|
callee_stack.add_var(*var_id, arg.1.clone())
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::IncompatibleParametersSingle {
|
||||||
|
msg: "This hook block has too many parameters".into(),
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pipeline_data =
|
||||||
|
eval_block_with_early_return(engine_state, &mut callee_stack, block, input, false, false)?;
|
||||||
|
|
||||||
|
if let PipelineData::Value(Value::Error { error }, _) = pipeline_data {
|
||||||
|
return Err(*error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all went fine, preserve the environment of the called block
|
||||||
|
let caller_env_vars = stack.get_env_var_names(engine_state);
|
||||||
|
|
||||||
|
// remove env vars that are present in the caller but not in the callee
|
||||||
|
// (the callee hid them)
|
||||||
|
for var in caller_env_vars.iter() {
|
||||||
|
if !callee_stack.has_env_var(engine_state, var) {
|
||||||
|
stack.remove_env_var(engine_state, var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new env vars from callee to caller
|
||||||
|
for (var, value) in callee_stack.get_stack_env_vars() {
|
||||||
|
stack.add_env_var(var, value);
|
||||||
|
}
|
||||||
|
Ok(pipeline_data)
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ mod filters;
|
||||||
mod formats;
|
mod formats;
|
||||||
mod generators;
|
mod generators;
|
||||||
mod hash;
|
mod hash;
|
||||||
|
pub mod hook;
|
||||||
mod input_handler;
|
mod input_handler;
|
||||||
mod math;
|
mod math;
|
||||||
mod misc;
|
mod misc;
|
||||||
|
@ -26,6 +27,7 @@ mod shells;
|
||||||
mod sort_utils;
|
mod sort_utils;
|
||||||
mod strings;
|
mod strings;
|
||||||
mod system;
|
mod system;
|
||||||
|
pub mod util;
|
||||||
mod viewers;
|
mod viewers;
|
||||||
|
|
||||||
pub use bits::*;
|
pub use bits::*;
|
||||||
|
@ -45,6 +47,7 @@ pub use filters::*;
|
||||||
pub use formats::*;
|
pub use formats::*;
|
||||||
pub use generators::*;
|
pub use generators::*;
|
||||||
pub use hash::*;
|
pub use hash::*;
|
||||||
|
pub use hook::*;
|
||||||
pub use math::*;
|
pub use math::*;
|
||||||
pub use misc::*;
|
pub use misc::*;
|
||||||
pub use network::*;
|
pub use network::*;
|
||||||
|
@ -55,6 +58,7 @@ pub use shells::*;
|
||||||
pub use sort_utils::*;
|
pub use sort_utils::*;
|
||||||
pub use strings::*;
|
pub use strings::*;
|
||||||
pub use system::*;
|
pub use system::*;
|
||||||
|
pub use util::*;
|
||||||
pub use viewers::*;
|
pub use viewers::*;
|
||||||
|
|
||||||
#[cfg(feature = "dataframe")]
|
#[cfg(feature = "dataframe")]
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::hook::eval_hook;
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nu_engine::env_to_strings;
|
use nu_engine::env_to_strings;
|
||||||
|
@ -324,9 +325,34 @@ impl ExternalCommand {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut err_str = err.to_string();
|
||||||
|
if engine_state.is_interactive {
|
||||||
|
let mut engine_state = engine_state.clone();
|
||||||
|
if let Some(hook) = engine_state.config.hooks.command_not_found.clone()
|
||||||
|
{
|
||||||
|
if let Ok(PipelineData::Value(Value::String { val, .. }, ..)) =
|
||||||
|
eval_hook(
|
||||||
|
&mut engine_state,
|
||||||
|
stack,
|
||||||
|
None,
|
||||||
|
vec![(
|
||||||
|
"cmd_name".into(),
|
||||||
|
Value::string(
|
||||||
|
self.name.item.to_string(),
|
||||||
|
self.name.span,
|
||||||
|
),
|
||||||
|
)],
|
||||||
|
&hook,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
err_str = format!("{}\n{}", err_str, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Err(ShellError::ExternalCommand {
|
Err(ShellError::ExternalCommand {
|
||||||
label,
|
label,
|
||||||
help: err.to_string(),
|
help: err_str,
|
||||||
span: self.name.span,
|
span: self.name.span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
40
crates/nu-command/src/util.rs
Normal file
40
crates/nu-command/src/util.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
|
use nu_protocol::CliError;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub fn report_error(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||||
|
) {
|
||||||
|
eprintln!("Error: {:?}", CliError(error, working_set));
|
||||||
|
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
let _ = nu_utils::enable_vt_processing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report_error_new(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||||
|
) {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
report_error(&working_set, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ pub struct Hooks {
|
||||||
pub pre_execution: Option<Value>,
|
pub pre_execution: Option<Value>,
|
||||||
pub env_change: Option<Value>,
|
pub env_change: Option<Value>,
|
||||||
pub display_output: Option<Value>,
|
pub display_output: Option<Value>,
|
||||||
|
pub command_not_found: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hooks {
|
impl Hooks {
|
||||||
|
@ -42,6 +43,7 @@ impl Hooks {
|
||||||
pre_execution: None,
|
pre_execution: None,
|
||||||
env_change: None,
|
env_change: None,
|
||||||
display_output: None,
|
display_output: None,
|
||||||
|
command_not_found: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1529,9 +1531,10 @@ fn create_hooks(value: &Value) -> Result<Hooks, ShellError> {
|
||||||
"pre_execution" => hooks.pre_execution = Some(vals[idx].clone()),
|
"pre_execution" => hooks.pre_execution = Some(vals[idx].clone()),
|
||||||
"env_change" => hooks.env_change = Some(vals[idx].clone()),
|
"env_change" => hooks.env_change = Some(vals[idx].clone()),
|
||||||
"display_output" => hooks.display_output = Some(vals[idx].clone()),
|
"display_output" => hooks.display_output = Some(vals[idx].clone()),
|
||||||
|
"command_not_found" => hooks.command_not_found = Some(vals[idx].clone()),
|
||||||
x => {
|
x => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"'pre_prompt', 'pre_execution', 'env_change', 'display_output'"
|
"'pre_prompt', 'pre_execution', 'env_change', 'display_output', 'command_not_found'"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
x.to_string(),
|
x.to_string(),
|
||||||
*span,
|
*span,
|
||||||
|
|
|
@ -304,6 +304,9 @@ let-env config = {
|
||||||
display_output: {||
|
display_output: {||
|
||||||
if (term size).columns >= 100 { table -e } else { table }
|
if (term size).columns >= 100 { table -e } else { table }
|
||||||
}
|
}
|
||||||
|
command_not_found: {
|
||||||
|
null # replace with source code to return an error message when a command is not found
|
||||||
|
}
|
||||||
}
|
}
|
||||||
menus: [
|
menus: [
|
||||||
# Configuration for default nushell menus
|
# Configuration for default nushell menus
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use nu_cli::report_error;
|
use nu_command::util::report_error;
|
||||||
use nu_engine::{get_full_help, CallExt};
|
use nu_engine::{get_full_help, CallExt};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_parser::{escape_for_script_arg, escape_quote_string};
|
use nu_parser::{escape_for_script_arg, escape_quote_string};
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use log::info;
|
use log::info;
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_cli::read_plugin_file;
|
use nu_cli::read_plugin_file;
|
||||||
use nu_cli::{eval_config_contents, eval_source, report_error};
|
use nu_cli::{eval_config_contents, eval_source};
|
||||||
|
use nu_command::util::report_error;
|
||||||
use nu_parser::ParseError;
|
use nu_parser::ParseError;
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
|
|
|
@ -17,8 +17,9 @@ use crate::{
|
||||||
use command::gather_commandline_args;
|
use command::gather_commandline_args;
|
||||||
use log::Level;
|
use log::Level;
|
||||||
use miette::Result;
|
use miette::Result;
|
||||||
use nu_cli::{gather_parent_env_vars, get_init_cwd, report_error_new};
|
use nu_cli::gather_parent_env_vars;
|
||||||
use nu_command::create_default_context;
|
use nu_command::util::report_error_new;
|
||||||
|
use nu_command::{create_default_context, get_init_cwd};
|
||||||
use nu_protocol::{util::BufferedReader, PipelineData, RawStream};
|
use nu_protocol::{util::BufferedReader, PipelineData, RawStream};
|
||||||
use nu_utils::utils::perf;
|
use nu_utils::utils::perf;
|
||||||
use run::{run_commands, run_file, run_repl};
|
use run::{run_commands, run_file, run_repl};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::io::{self, BufRead, Read, Write};
|
use std::io::{self, BufRead, Read, Write};
|
||||||
|
|
||||||
use nu_cli::{eval_env_change_hook, eval_hook};
|
|
||||||
use nu_command::create_default_context;
|
use nu_command::create_default_context;
|
||||||
|
use nu_command::hook::{eval_env_change_hook, eval_hook};
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
|
|
Loading…
Reference in a new issue