From beec6588722c96928b58a8bc300f4353220493c1 Mon Sep 17 00:00:00 2001 From: Per Bothner Date: Sat, 5 Nov 2022 17:46:40 -0700 Subject: [PATCH] New "display_output" hook. (#6915) * New "display_output" hook. * Fix unrelated "clippy" complaint in nu-tables crate. * Fix code-formattng and style issues in "display_output" hook * Enhance eval_hook to return PipelineData. This allows a hook (including display_output) to return a value. Co-authored-by: JT <547158+jntrnr@users.noreply.github.com> --- crates/nu-cli/src/repl.rs | 42 ++++++++++++---- crates/nu-cli/src/util.rs | 31 ++++++++++-- crates/nu-protocol/src/config.rs | 5 +- crates/nu-protocol/src/pipeline_data.rs | 67 +++++++++++++++---------- src/test_bins.rs | 4 +- 5 files changed, 106 insertions(+), 43 deletions(-) diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index aa97da766a..02f79343aa 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -278,7 +278,7 @@ pub fn evaluate_repl( // Right before we start our prompt and take input from the user, // fire the "pre_prompt" hook if let Some(hook) = config.hooks.pre_prompt.clone() { - if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) { + if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) { report_error_new(engine_state, &err); } } @@ -334,7 +334,7 @@ pub fn evaluate_repl( // Right before we start running the code the user gave us, // fire the "pre_execution" hook if let Some(hook) = config.hooks.pre_execution.clone() { - if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) { + if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) { report_error_new(engine_state, &err); } } @@ -686,6 +686,7 @@ pub fn eval_env_change_hook( eval_hook( engine_state, stack, + None, vec![("$before".into(), before), ("$after".into(), after.clone())], hook_value, )?; @@ -711,15 +712,17 @@ pub fn eval_env_change_hook( pub fn eval_hook( engine_state: &mut EngineState, stack: &mut Stack, + input: Option, arguments: Vec<(String, Value)>, value: &Value, -) -> Result<(), ShellError> { +) -> Result { let value_span = value.span()?; let condition_path = PathMember::String { val: "condition".to_string(), span: value_span, }; + let mut output = PipelineData::new(Span::new(0, 0)); let code_path = PathMember::String { val: "code".to_string(), @@ -729,7 +732,7 @@ pub fn eval_hook( match value { Value::List { vals, .. } => { for val in vals { - eval_hook(engine_state, stack, arguments.clone(), val)? + eval_hook(engine_state, stack, None, arguments.clone(), val)?; } } Value::Record { .. } => { @@ -745,6 +748,7 @@ pub fn eval_hook( engine_state, stack, block_id, + None, arguments.clone(), block_span, ) { @@ -824,7 +828,9 @@ pub fn eval_hook( .collect(); match eval_block(engine_state, stack, &block, input, false, false) { - Ok(_) => {} + Ok(pipeline_data) => { + output = pipeline_data; + } Err(err) => { report_error_new(engine_state, &err); } @@ -839,7 +845,14 @@ pub fn eval_hook( span: block_span, .. } => { - run_hook_block(engine_state, stack, block_id, arguments, block_span)?; + run_hook_block( + engine_state, + stack, + block_id, + input, + arguments, + block_span, + )?; } other => { return Err(ShellError::UnsupportedConfigValue( @@ -856,7 +869,17 @@ pub fn eval_hook( span: block_span, .. } => { - run_hook_block(engine_state, stack, *block_id, arguments, *block_span)?; + output = PipelineData::Value( + run_hook_block( + engine_state, + stack, + *block_id, + input, + arguments, + *block_span, + )?, + None, + ); } other => { return Err(ShellError::UnsupportedConfigValue( @@ -870,19 +893,20 @@ pub fn eval_hook( let cwd = get_guaranteed_cwd(engine_state, stack); engine_state.merge_env(stack, cwd)?; - Ok(()) + Ok(output) } pub fn run_hook_block( engine_state: &EngineState, stack: &mut Stack, block_id: BlockId, + optional_input: Option, arguments: Vec<(String, Value)>, span: Span, ) -> Result { let block = engine_state.get_block(block_id); - let input = PipelineData::new(span); + let input = optional_input.unwrap_or_else(|| PipelineData::new(span)); let mut callee_stack = stack.gather_captures(&block.captures); diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index 2ae77dcc71..26494c61c1 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -1,11 +1,11 @@ -use log::trace; +use crate::repl::eval_hook; use nu_engine::eval_block; use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents}; use nu_protocol::engine::StateWorkingSet; use nu_protocol::CliError; use nu_protocol::{ engine::{EngineState, Stack}, - PipelineData, ShellError, Span, Value, + print_if_stream, PipelineData, ShellError, Span, Value, }; #[cfg(windows)] use nu_utils::enable_vt_processing; @@ -204,8 +204,6 @@ pub fn eval_source( fname: &str, input: PipelineData, ) -> bool { - trace!("eval_source"); - let (block, delta) = { let mut working_set = StateWorkingSet::new(engine_state); let (output, err) = parse( @@ -232,7 +230,30 @@ pub fn eval_source( match eval_block(engine_state, stack, &block, input, false, false) { Ok(pipeline_data) => { - match pipeline_data.print(engine_state, stack, false, false) { + let config = engine_state.get_config(); + let result; + if let PipelineData::ExternalStream { + stdout: stream, + stderr: stderr_stream, + exit_code, + .. + } = pipeline_data + { + result = print_if_stream(stream, stderr_stream, false, exit_code); + } else if let Some(hook) = config.hooks.display_output.clone() { + match eval_hook(engine_state, stack, Some(pipeline_data), vec![], &hook) { + Err(err) => { + result = Err(err); + } + Ok(val) => { + result = val.print(engine_state, stack, false, false); + } + } + } else { + result = pipeline_data.print(engine_state, stack, false, false); + } + + match result { Err(err) => { let working_set = StateWorkingSet::new(engine_state); diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index fc7d927d66..13fe55bc42 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -32,6 +32,7 @@ pub struct Hooks { pub pre_prompt: Option, pub pre_execution: Option, pub env_change: Option, + pub display_output: Option, } impl Hooks { @@ -40,6 +41,7 @@ impl Hooks { pre_prompt: None, pre_execution: None, env_change: None, + display_output: None, } } } @@ -571,9 +573,10 @@ fn create_hooks(value: &Value) -> Result { "pre_prompt" => hooks.pre_prompt = Some(vals[idx].clone()), "pre_execution" => hooks.pre_execution = Some(vals[idx].clone()), "env_change" => hooks.env_change = Some(vals[idx].clone()), + "display_output" => hooks.display_output = Some(vals[idx].clone()), x => { return Err(ShellError::UnsupportedConfigValue( - "'pre_prompt', 'pre_execution', or 'env_change'".to_string(), + "'pre_prompt', 'pre_execution', 'env_change'".to_string(), x.to_string(), *span, )); diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index 7afa5e5a6a..2699bab701 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -426,34 +426,13 @@ impl PipelineData { .. } = self { - // NOTE: currently we don't need anything from stderr - // so directly consumes `stderr_stream` to make sure that everything is done. - std::thread::spawn(move || stderr_stream.map(|x| x.into_bytes())); - if let Some(stream) = stream { - for s in stream { - let s_live = s?; - let bin_output = s_live.as_binary()?; - - if !to_stderr { - stdout_write_all_and_flush(bin_output)? - } else { - stderr_write_all_and_flush(bin_output)? - } - } + return print_if_stream(stream, stderr_stream, to_stderr, exit_code); + /* + if let Ok(exit_code) = print_if_stream(stream, stderr_stream, to_stderr, exit_code) { + return Ok(exit_code); } - - // Make sure everything has finished - if let Some(exit_code) = exit_code { - let mut exit_codes: Vec<_> = exit_code.into_iter().collect(); - return match exit_codes.pop() { - #[cfg(unix)] - Some(Value::Error { error }) => Err(error), - Some(Value::Int { val, .. }) => Ok(val), - _ => Ok(0), - }; - } - return Ok(0); + */ } match engine_state.find_decl("table".as_bytes(), &[]) { @@ -549,6 +528,42 @@ impl IntoIterator for PipelineData { } } +pub fn print_if_stream( + stream: Option, + stderr_stream: Option, + to_stderr: bool, + exit_code: Option, +) -> Result { + // NOTE: currently we don't need anything from stderr + // so directly consumes `stderr_stream` to make sure that everything is done. + std::thread::spawn(move || stderr_stream.map(|x| x.into_bytes())); + if let Some(stream) = stream { + for s in stream { + let s_live = s?; + let bin_output = s_live.as_binary()?; + + if !to_stderr { + stdout_write_all_and_flush(bin_output)? + } else { + stderr_write_all_and_flush(bin_output)? + } + } + } + + // Make sure everything has finished + if let Some(exit_code) = exit_code { + let mut exit_codes: Vec<_> = exit_code.into_iter().collect(); + return match exit_codes.pop() { + #[cfg(unix)] + Some(Value::Error { error }) => Err(error), + Some(Value::Int { val, .. }) => Ok(val), + _ => Ok(0), + }; + } + + Ok(0) +} + impl Iterator for PipelineIterator { type Item = Value; diff --git a/src/test_bins.rs b/src/test_bins.rs index dd432711a2..a1d59e32d1 100644 --- a/src/test_bins.rs +++ b/src/test_bins.rs @@ -176,7 +176,7 @@ pub fn nu_repl() { // Check for pre_prompt hook let config = engine_state.get_config(); if let Some(hook) = config.hooks.pre_prompt.clone() { - if let Err(err) = eval_hook(&mut engine_state, &mut stack, vec![], &hook) { + if let Err(err) = eval_hook(&mut engine_state, &mut stack, None, vec![], &hook) { outcome_err(&engine_state, &err); } } @@ -194,7 +194,7 @@ pub fn nu_repl() { // Check for pre_execution hook let config = engine_state.get_config(); if let Some(hook) = config.hooks.pre_execution.clone() { - if let Err(err) = eval_hook(&mut engine_state, &mut stack, vec![], &hook) { + if let Err(err) = eval_hook(&mut engine_state, &mut stack, None, vec![], &hook) { outcome_err(&engine_state, &err); } }