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>
This commit is contained in:
Per Bothner 2022-11-05 17:46:40 -07:00 committed by GitHub
parent b90d701f89
commit beec658872
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 106 additions and 43 deletions

View file

@ -278,7 +278,7 @@ pub fn evaluate_repl(
// Right before we start our prompt and take input from the user, // Right before we start our prompt and take input from the user,
// fire the "pre_prompt" hook // fire the "pre_prompt" hook
if let Some(hook) = config.hooks.pre_prompt.clone() { 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); 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, // Right before we start running the code the user gave us,
// fire the "pre_execution" hook // fire the "pre_execution" hook
if let Some(hook) = config.hooks.pre_execution.clone() { 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); report_error_new(engine_state, &err);
} }
} }
@ -686,6 +686,7 @@ pub fn eval_env_change_hook(
eval_hook( eval_hook(
engine_state, engine_state,
stack, stack,
None,
vec![("$before".into(), before), ("$after".into(), after.clone())], vec![("$before".into(), before), ("$after".into(), after.clone())],
hook_value, hook_value,
)?; )?;
@ -711,15 +712,17 @@ pub fn eval_env_change_hook(
pub fn eval_hook( pub fn eval_hook(
engine_state: &mut EngineState, engine_state: &mut EngineState,
stack: &mut Stack, stack: &mut Stack,
input: Option<PipelineData>,
arguments: Vec<(String, Value)>, arguments: Vec<(String, Value)>,
value: &Value, value: &Value,
) -> Result<(), ShellError> { ) -> Result<PipelineData, ShellError> {
let value_span = value.span()?; let value_span = value.span()?;
let condition_path = PathMember::String { let condition_path = PathMember::String {
val: "condition".to_string(), val: "condition".to_string(),
span: value_span, span: value_span,
}; };
let mut output = PipelineData::new(Span::new(0, 0));
let code_path = PathMember::String { let code_path = PathMember::String {
val: "code".to_string(), val: "code".to_string(),
@ -729,7 +732,7 @@ pub fn eval_hook(
match value { match value {
Value::List { vals, .. } => { Value::List { vals, .. } => {
for val in vals { for val in vals {
eval_hook(engine_state, stack, arguments.clone(), val)? eval_hook(engine_state, stack, None, arguments.clone(), val)?;
} }
} }
Value::Record { .. } => { Value::Record { .. } => {
@ -745,6 +748,7 @@ pub fn eval_hook(
engine_state, engine_state,
stack, stack,
block_id, block_id,
None,
arguments.clone(), arguments.clone(),
block_span, block_span,
) { ) {
@ -824,7 +828,9 @@ pub fn eval_hook(
.collect(); .collect();
match eval_block(engine_state, stack, &block, input, false, false) { match eval_block(engine_state, stack, &block, input, false, false) {
Ok(_) => {} Ok(pipeline_data) => {
output = pipeline_data;
}
Err(err) => { Err(err) => {
report_error_new(engine_state, &err); report_error_new(engine_state, &err);
} }
@ -839,7 +845,14 @@ pub fn eval_hook(
span: block_span, 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 => { other => {
return Err(ShellError::UnsupportedConfigValue( return Err(ShellError::UnsupportedConfigValue(
@ -856,7 +869,17 @@ pub fn eval_hook(
span: block_span, 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 => { other => {
return Err(ShellError::UnsupportedConfigValue( return Err(ShellError::UnsupportedConfigValue(
@ -870,19 +893,20 @@ pub fn eval_hook(
let cwd = get_guaranteed_cwd(engine_state, stack); let cwd = get_guaranteed_cwd(engine_state, stack);
engine_state.merge_env(stack, cwd)?; engine_state.merge_env(stack, cwd)?;
Ok(()) Ok(output)
} }
pub fn run_hook_block( pub fn run_hook_block(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
block_id: BlockId, block_id: BlockId,
optional_input: Option<PipelineData>,
arguments: Vec<(String, Value)>, arguments: Vec<(String, Value)>,
span: Span, span: Span,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let block = engine_state.get_block(block_id); 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); let mut callee_stack = stack.gather_captures(&block.captures);

View file

@ -1,11 +1,11 @@
use log::trace; use crate::repl::eval_hook;
use nu_engine::eval_block; use nu_engine::eval_block;
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::CliError;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack}, engine::{EngineState, Stack},
PipelineData, ShellError, Span, Value, print_if_stream, PipelineData, ShellError, Span, Value,
}; };
#[cfg(windows)] #[cfg(windows)]
use nu_utils::enable_vt_processing; use nu_utils::enable_vt_processing;
@ -204,8 +204,6 @@ pub fn eval_source(
fname: &str, fname: &str,
input: PipelineData, input: PipelineData,
) -> bool { ) -> bool {
trace!("eval_source");
let (block, delta) = { let (block, delta) = {
let mut working_set = StateWorkingSet::new(engine_state); let mut working_set = StateWorkingSet::new(engine_state);
let (output, err) = parse( let (output, err) = parse(
@ -232,7 +230,30 @@ pub fn eval_source(
match eval_block(engine_state, stack, &block, input, false, false) { match eval_block(engine_state, stack, &block, input, false, false) {
Ok(pipeline_data) => { 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) => { Err(err) => {
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);

View file

@ -32,6 +32,7 @@ pub struct Hooks {
pub pre_prompt: Option<Value>, pub pre_prompt: Option<Value>,
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>,
} }
impl Hooks { impl Hooks {
@ -40,6 +41,7 @@ impl Hooks {
pre_prompt: None, pre_prompt: None,
pre_execution: None, pre_execution: None,
env_change: None, env_change: None,
display_output: None,
} }
} }
} }
@ -571,9 +573,10 @@ fn create_hooks(value: &Value) -> Result<Hooks, ShellError> {
"pre_prompt" => hooks.pre_prompt = Some(vals[idx].clone()), "pre_prompt" => hooks.pre_prompt = Some(vals[idx].clone()),
"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()),
x => { x => {
return Err(ShellError::UnsupportedConfigValue( return Err(ShellError::UnsupportedConfigValue(
"'pre_prompt', 'pre_execution', or 'env_change'".to_string(), "'pre_prompt', 'pre_execution', 'env_change'".to_string(),
x.to_string(), x.to_string(),
*span, *span,
)); ));

View file

@ -426,34 +426,13 @@ impl PipelineData {
.. ..
} = self } = self
{ {
// NOTE: currently we don't need anything from stderr return print_if_stream(stream, stderr_stream, to_stderr, exit_code);
// 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 Ok(exit_code) = print_if_stream(stream, stderr_stream, to_stderr, exit_code) {
if let Some(stream) = stream { return Ok(exit_code);
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),
};
}
return Ok(0); return Ok(0);
*/
} }
match engine_state.find_decl("table".as_bytes(), &[]) { match engine_state.find_decl("table".as_bytes(), &[]) {
@ -549,6 +528,42 @@ impl IntoIterator for PipelineData {
} }
} }
pub fn print_if_stream(
stream: Option<RawStream>,
stderr_stream: Option<RawStream>,
to_stderr: bool,
exit_code: Option<ListStream>,
) -> Result<i64, ShellError> {
// 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 { impl Iterator for PipelineIterator {
type Item = Value; type Item = Value;

View file

@ -176,7 +176,7 @@ pub fn nu_repl() {
// Check for pre_prompt hook // Check for pre_prompt hook
let config = engine_state.get_config(); let config = engine_state.get_config();
if let Some(hook) = config.hooks.pre_prompt.clone() { 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); outcome_err(&engine_state, &err);
} }
} }
@ -194,7 +194,7 @@ pub fn nu_repl() {
// Check for pre_execution hook // Check for pre_execution hook
let config = engine_state.get_config(); let config = engine_state.get_config();
if let Some(hook) = config.hooks.pre_execution.clone() { 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); outcome_err(&engine_state, &err);
} }
} }