diff --git a/crates/nu-cli/src/completions/completer.rs b/crates/nu-cli/src/completions/completer.rs index 9400568bc3..ac6d727bc1 100644 --- a/crates/nu-cli/src/completions/completer.rs +++ b/crates/nu-cli/src/completions/completer.rs @@ -4,10 +4,9 @@ use crate::completions::{ }; use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style}; use nu_engine::eval_block; -use nu_parser::{flatten_expression, parse, FlatShape}; +use nu_parser::{flatten_pipeline_element, parse, FlatShape}; use nu_protocol::debugger::WithoutDebug; use nu_protocol::{ - ast::PipelineElement, engine::{EngineState, Stack, StateWorkingSet}, BlockId, PipelineData, Span, Value, }; @@ -25,7 +24,7 @@ impl NuCompleter { pub fn new(engine_state: Arc, stack: Stack) -> Self { Self { engine_state, - stack, + stack: stack.reset_stdio().capture(), } } @@ -64,9 +63,10 @@ impl NuCompleter { offset: usize, span: Span, ) -> Option> { - let stack = self.stack.clone(); let block = self.engine_state.get_block(block_id); - let mut callee_stack = stack.gather_captures(&self.engine_state, &block.captures); + let mut callee_stack = self + .stack + .gather_captures(&self.engine_state, &block.captures); // Line if let Some(pos_arg) = block.signature.required_positional.first() { @@ -89,8 +89,6 @@ impl NuCompleter { &mut callee_stack, block, PipelineData::empty(), - true, - true, ); match result { @@ -128,281 +126,265 @@ impl NuCompleter { for pipeline in output.pipelines.into_iter() { for pipeline_element in pipeline.elements { - match pipeline_element { - PipelineElement::Expression(_, expr) - | PipelineElement::ErrPipedExpression(_, expr) - | PipelineElement::OutErrPipedExpression(_, expr) - | PipelineElement::Redirection(_, _, expr, _) - | PipelineElement::And(_, expr) - | PipelineElement::Or(_, expr) - | PipelineElement::SameTargetRedirection { cmd: (_, expr), .. } - | PipelineElement::SeparateRedirection { - out: (_, expr, _), .. - } => { - let flattened: Vec<_> = flatten_expression(&working_set, &expr); - let mut spans: Vec = vec![]; + let flattened = flatten_pipeline_element(&working_set, &pipeline_element); + let mut spans: Vec = vec![]; - for (flat_idx, flat) in flattened.iter().enumerate() { - let is_passthrough_command = spans - .first() - .filter(|content| { - content.as_str() == "sudo" || content.as_str() == "doas" - }) - .is_some(); - // Read the current spam to string - let current_span = working_set.get_span_contents(flat.0).to_vec(); - let current_span_str = String::from_utf8_lossy(¤t_span); + for (flat_idx, flat) in flattened.iter().enumerate() { + let is_passthrough_command = spans + .first() + .filter(|content| content.as_str() == "sudo" || content.as_str() == "doas") + .is_some(); + // Read the current spam to string + let current_span = working_set.get_span_contents(flat.0).to_vec(); + let current_span_str = String::from_utf8_lossy(¤t_span); - let is_last_span = pos >= flat.0.start && pos < flat.0.end; + let is_last_span = pos >= flat.0.start && pos < flat.0.end; - // Skip the last 'a' as span item - if is_last_span { - let offset = pos - flat.0.start; - if offset == 0 { - spans.push(String::new()) - } else { - let mut current_span_str = current_span_str.to_string(); - current_span_str.remove(offset); - spans.push(current_span_str); - } - } else { - spans.push(current_span_str.to_string()); + // Skip the last 'a' as span item + if is_last_span { + let offset = pos - flat.0.start; + if offset == 0 { + spans.push(String::new()) + } else { + let mut current_span_str = current_span_str.to_string(); + current_span_str.remove(offset); + spans.push(current_span_str); + } + } else { + spans.push(current_span_str.to_string()); + } + + // Complete based on the last span + if is_last_span { + // Context variables + let most_left_var = + most_left_variable(flat_idx, &working_set, flattened.clone()); + + // Create a new span + let new_span = Span::new(flat.0.start, flat.0.end - 1); + + // Parses the prefix. Completion should look up to the cursor position, not after. + let mut prefix = working_set.get_span_contents(flat.0).to_vec(); + let index = pos - flat.0.start; + prefix.drain(index..); + + // Variables completion + if prefix.starts_with(b"$") || most_left_var.is_some() { + let mut completer = VariableCompletion::new( + self.engine_state.clone(), + self.stack.clone(), + most_left_var.unwrap_or((vec![], vec![])), + ); + + return self.process_completion( + &mut completer, + &working_set, + prefix, + new_span, + fake_offset, + pos, + ); + } + + // Flags completion + if prefix.starts_with(b"-") { + // Try to complete flag internally + let mut completer = FlagCompletion::new(pipeline_element.expr.clone()); + let result = self.process_completion( + &mut completer, + &working_set, + prefix.clone(), + new_span, + fake_offset, + pos, + ); + + if !result.is_empty() { + return result; } - // Complete based on the last span - if is_last_span { - // Context variables - let most_left_var = - most_left_variable(flat_idx, &working_set, flattened.clone()); - - // Create a new span - let new_span = Span::new(flat.0.start, flat.0.end - 1); - - // Parses the prefix. Completion should look up to the cursor position, not after. - let mut prefix = working_set.get_span_contents(flat.0).to_vec(); - let index = pos - flat.0.start; - prefix.drain(index..); - - // Variables completion - if prefix.starts_with(b"$") || most_left_var.is_some() { - let mut completer = VariableCompletion::new( - self.engine_state.clone(), - self.stack.clone(), - most_left_var.unwrap_or((vec![], vec![])), - ); - - return self.process_completion( - &mut completer, - &working_set, - prefix, - new_span, - fake_offset, - pos, - ); + // We got no results for internal completion + // now we can check if external completer is set and use it + if let Some(block_id) = config.external_completer { + if let Some(external_result) = self.external_completion( + block_id, + &spans, + fake_offset, + new_span, + ) { + return external_result; } - - // Flags completion - if prefix.starts_with(b"-") { - // Try to complete flag internally - let mut completer = FlagCompletion::new(expr.clone()); - let result = self.process_completion( - &mut completer, - &working_set, - prefix.clone(), - new_span, - fake_offset, - pos, - ); - - if !result.is_empty() { - return result; - } - - // We got no results for internal completion - // now we can check if external completer is set and use it - if let Some(block_id) = config.external_completer { - if let Some(external_result) = self.external_completion( - block_id, - &spans, - fake_offset, - new_span, - ) { - return external_result; - } - } - } - - // specially check if it is currently empty - always complete commands - if (is_passthrough_command && flat_idx == 1) - || (flat_idx == 0 - && working_set.get_span_contents(new_span).is_empty()) - { - let mut completer = CommandCompletion::new( - self.engine_state.clone(), - &working_set, - flattened.clone(), - // flat_idx, - FlatShape::String, - true, - ); - return self.process_completion( - &mut completer, - &working_set, - prefix, - new_span, - fake_offset, - pos, - ); - } - - // Completions that depends on the previous expression (e.g: use, source-env) - if (is_passthrough_command && flat_idx > 1) || flat_idx > 0 { - if let Some(previous_expr) = flattened.get(flat_idx - 1) { - // Read the content for the previous expression - let prev_expr_str = - working_set.get_span_contents(previous_expr.0).to_vec(); - - // Completion for .nu files - if prev_expr_str == b"use" - || prev_expr_str == b"overlay use" - || prev_expr_str == b"source-env" - { - let mut completer = DotNuCompletion::new( - self.engine_state.clone(), - self.stack.clone(), - ); - - return self.process_completion( - &mut completer, - &working_set, - prefix, - new_span, - fake_offset, - pos, - ); - } else if prev_expr_str == b"ls" { - let mut completer = FileCompletion::new( - self.engine_state.clone(), - self.stack.clone(), - ); - - return self.process_completion( - &mut completer, - &working_set, - prefix, - new_span, - fake_offset, - pos, - ); - } - } - } - - // Match other types - match &flat.1 { - FlatShape::Custom(decl_id) => { - let mut completer = CustomCompletion::new( - self.engine_state.clone(), - self.stack.clone(), - *decl_id, - initial_line, - ); - - return self.process_completion( - &mut completer, - &working_set, - prefix, - new_span, - fake_offset, - pos, - ); - } - FlatShape::Directory => { - let mut completer = DirectoryCompletion::new( - self.engine_state.clone(), - self.stack.clone(), - ); - - return self.process_completion( - &mut completer, - &working_set, - prefix, - new_span, - fake_offset, - pos, - ); - } - FlatShape::Filepath | FlatShape::GlobPattern => { - let mut completer = FileCompletion::new( - self.engine_state.clone(), - self.stack.clone(), - ); - - return self.process_completion( - &mut completer, - &working_set, - prefix, - new_span, - fake_offset, - pos, - ); - } - flat_shape => { - let mut completer = CommandCompletion::new( - self.engine_state.clone(), - &working_set, - flattened.clone(), - // flat_idx, - flat_shape.clone(), - false, - ); - - let mut out: Vec<_> = self.process_completion( - &mut completer, - &working_set, - prefix.clone(), - new_span, - fake_offset, - pos, - ); - - if !out.is_empty() { - return out; - } - - // Try to complete using an external completer (if set) - if let Some(block_id) = config.external_completer { - if let Some(external_result) = self.external_completion( - block_id, - &spans, - fake_offset, - new_span, - ) { - return external_result; - } - } - - // Check for file completion - let mut completer = FileCompletion::new( - self.engine_state.clone(), - self.stack.clone(), - ); - out = self.process_completion( - &mut completer, - &working_set, - prefix, - new_span, - fake_offset, - pos, - ); - - if !out.is_empty() { - return out; - } - } - }; } } + + // specially check if it is currently empty - always complete commands + if (is_passthrough_command && flat_idx == 1) + || (flat_idx == 0 && working_set.get_span_contents(new_span).is_empty()) + { + let mut completer = CommandCompletion::new( + self.engine_state.clone(), + &working_set, + flattened.clone(), + // flat_idx, + FlatShape::String, + true, + ); + return self.process_completion( + &mut completer, + &working_set, + prefix, + new_span, + fake_offset, + pos, + ); + } + + // Completions that depends on the previous expression (e.g: use, source-env) + if (is_passthrough_command && flat_idx > 1) || flat_idx > 0 { + if let Some(previous_expr) = flattened.get(flat_idx - 1) { + // Read the content for the previous expression + let prev_expr_str = + working_set.get_span_contents(previous_expr.0).to_vec(); + + // Completion for .nu files + if prev_expr_str == b"use" + || prev_expr_str == b"overlay use" + || prev_expr_str == b"source-env" + { + let mut completer = DotNuCompletion::new( + self.engine_state.clone(), + self.stack.clone(), + ); + + return self.process_completion( + &mut completer, + &working_set, + prefix, + new_span, + fake_offset, + pos, + ); + } else if prev_expr_str == b"ls" { + let mut completer = FileCompletion::new( + self.engine_state.clone(), + self.stack.clone(), + ); + + return self.process_completion( + &mut completer, + &working_set, + prefix, + new_span, + fake_offset, + pos, + ); + } + } + } + + // Match other types + match &flat.1 { + FlatShape::Custom(decl_id) => { + let mut completer = CustomCompletion::new( + self.engine_state.clone(), + self.stack.clone(), + *decl_id, + initial_line, + ); + + return self.process_completion( + &mut completer, + &working_set, + prefix, + new_span, + fake_offset, + pos, + ); + } + FlatShape::Directory => { + let mut completer = DirectoryCompletion::new( + self.engine_state.clone(), + self.stack.clone(), + ); + + return self.process_completion( + &mut completer, + &working_set, + prefix, + new_span, + fake_offset, + pos, + ); + } + FlatShape::Filepath | FlatShape::GlobPattern => { + let mut completer = FileCompletion::new( + self.engine_state.clone(), + self.stack.clone(), + ); + + return self.process_completion( + &mut completer, + &working_set, + prefix, + new_span, + fake_offset, + pos, + ); + } + flat_shape => { + let mut completer = CommandCompletion::new( + self.engine_state.clone(), + &working_set, + flattened.clone(), + // flat_idx, + flat_shape.clone(), + false, + ); + + let mut out: Vec<_> = self.process_completion( + &mut completer, + &working_set, + prefix.clone(), + new_span, + fake_offset, + pos, + ); + + if !out.is_empty() { + return out; + } + + // Try to complete using an external completer (if set) + if let Some(block_id) = config.external_completer { + if let Some(external_result) = self.external_completion( + block_id, + &spans, + fake_offset, + new_span, + ) { + return external_result; + } + } + + // Check for file completion + let mut completer = FileCompletion::new( + self.engine_state.clone(), + self.stack.clone(), + ); + out = self.process_completion( + &mut completer, + &working_set, + prefix, + new_span, + fake_offset, + pos, + ); + + if !out.is_empty() { + return out; + } + } + }; } } } diff --git a/crates/nu-cli/src/completions/custom_completions.rs b/crates/nu-cli/src/completions/custom_completions.rs index 741c6158af..920eeed618 100644 --- a/crates/nu-cli/src/completions/custom_completions.rs +++ b/crates/nu-cli/src/completions/custom_completions.rs @@ -25,7 +25,7 @@ impl CustomCompletion { pub fn new(engine_state: Arc, stack: Stack, decl_id: usize, line: String) -> Self { Self { engine_state, - stack, + stack: stack.reset_stdio().capture(), decl_id, line, sort_by: SortBy::None, @@ -67,8 +67,6 @@ impl Completer for CustomCompletion { custom_completion: None, }), ], - redirect_stdout: true, - redirect_stderr: true, parser_info: HashMap::new(), }, PipelineData::empty(), diff --git a/crates/nu-cli/src/eval_cmds.rs b/crates/nu-cli/src/eval_cmds.rs index daaa5c237f..9b37aed33e 100644 --- a/crates/nu-cli/src/eval_cmds.rs +++ b/crates/nu-cli/src/eval_cmds.rs @@ -56,27 +56,21 @@ pub fn evaluate_commands( } // Run the block - let exit_code = - match eval_block::(engine_state, stack, &block, input, false, false) { - Ok(pipeline_data) => { - let mut config = engine_state.get_config().clone(); - if let Some(t_mode) = table_mode { - config.table_mode = t_mode.coerce_str()?.parse().unwrap_or_default(); - } - crate::eval_file::print_table_or_error( - engine_state, - stack, - pipeline_data, - &mut config, - ) + let exit_code = match eval_block::(engine_state, stack, &block, input) { + Ok(pipeline_data) => { + let mut config = engine_state.get_config().clone(); + if let Some(t_mode) = table_mode { + config.table_mode = t_mode.coerce_str()?.parse().unwrap_or_default(); } - Err(err) => { - let working_set = StateWorkingSet::new(engine_state); + crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config) + } + Err(err) => { + let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &err); - std::process::exit(1); - } - }; + report_error(&working_set, &err); + std::process::exit(1); + } + }; info!("evaluate {}:{}:{}", file!(), line!(), column!()); diff --git a/crates/nu-cli/src/eval_file.rs b/crates/nu-cli/src/eval_file.rs index 8c0f4be2b2..349a4aece5 100644 --- a/crates/nu-cli/src/eval_file.rs +++ b/crates/nu-cli/src/eval_file.rs @@ -131,14 +131,8 @@ pub fn evaluate_file( if engine_state.find_decl(b"main", &[]).is_some() { let args = format!("main {}", args.join(" ")); - let pipeline_data = eval_block::( - engine_state, - stack, - &block, - PipelineData::empty(), - false, - false, - ); + let pipeline_data = + eval_block::(engine_state, stack, &block, PipelineData::empty()); let pipeline_data = match pipeline_data { Err(ShellError::Return { .. }) => { // allows early exists before `main` is run. @@ -214,8 +208,7 @@ pub(crate) fn print_table_or_error( print_or_exit(pipeline_data, engine_state, config); } else { // The final call on table command, it's ok to set redirect_output to false. - let mut call = Call::new(Span::new(0, 0)); - call.redirect_stdout = false; + let call = Call::new(Span::new(0, 0)); let table = command.run(engine_state, stack, &call, pipeline_data); match table { diff --git a/crates/nu-cli/src/menus/menu_completions.rs b/crates/nu-cli/src/menus/menu_completions.rs index 18e20222b4..ef7facc900 100644 --- a/crates/nu-cli/src/menus/menu_completions.rs +++ b/crates/nu-cli/src/menus/menu_completions.rs @@ -28,7 +28,7 @@ impl NuMenuCompleter { Self { block_id, span, - stack, + stack: stack.reset_stdio().capture(), engine_state, only_buffer_difference, } @@ -57,14 +57,7 @@ impl Completer for NuMenuCompleter { let input = Value::nothing(self.span).into_pipeline_data(); - let res = eval_block::( - &self.engine_state, - &mut self.stack, - block, - input, - false, - false, - ); + let res = eval_block::(&self.engine_state, &mut self.stack, block, input); if let Ok(values) = res { let values = values.into_value(self.span); diff --git a/crates/nu-cli/src/reedline_config.rs b/crates/nu-cli/src/reedline_config.rs index 24d770e1b7..65938dab8f 100644 --- a/crates/nu-cli/src/reedline_config.rs +++ b/crates/nu-cli/src/reedline_config.rs @@ -109,17 +109,9 @@ pub(crate) fn add_menus( (output, working_set.render()) }; - let mut temp_stack = Stack::new(); + let mut temp_stack = Stack::new().capture(); let input = PipelineData::Empty; - - let res = eval_block::( - &engine_state, - &mut temp_stack, - &block, - input, - false, - false, - )?; + let res = eval_block::(&engine_state, &mut temp_stack, &block, input)?; if let PipelineData::Value(value, None) = res { for menu in create_menus(&value)? { diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index d263e9a6a0..712faea6f2 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -3,7 +3,7 @@ use nu_ansi_term::Style; use nu_color_config::{get_matching_brackets_style, get_shape_color}; use nu_engine::env; use nu_parser::{flatten_block, parse, FlatShape}; -use nu_protocol::ast::{Argument, Block, Expr, Expression, PipelineElement, RecordItem}; +use nu_protocol::ast::{Argument, Block, Expr, Expression, PipelineRedirection, RecordItem}; use nu_protocol::engine::{EngineState, Stack, StateWorkingSet}; use nu_protocol::{Config, Span}; use reedline::{Highlighter, StyledText}; @@ -262,26 +262,38 @@ fn find_matching_block_end_in_block( ) -> Option { for p in &block.pipelines { for e in &p.elements { - match e { - PipelineElement::Expression(_, e) - | PipelineElement::ErrPipedExpression(_, e) - | PipelineElement::OutErrPipedExpression(_, e) - | PipelineElement::Redirection(_, _, e, _) - | PipelineElement::And(_, e) - | PipelineElement::Or(_, e) - | PipelineElement::SameTargetRedirection { cmd: (_, e), .. } - | PipelineElement::SeparateRedirection { out: (_, e, _), .. } => { - if e.span.contains(global_cursor_offset) { - if let Some(pos) = find_matching_block_end_in_expr( - line, - working_set, - e, - global_span_offset, - global_cursor_offset, - ) { + if e.expr.span.contains(global_cursor_offset) { + if let Some(pos) = find_matching_block_end_in_expr( + line, + working_set, + &e.expr, + global_span_offset, + global_cursor_offset, + ) { + return Some(pos); + } + } + + if let Some(redirection) = e.redirection.as_ref() { + match redirection { + PipelineRedirection::Single { target, .. } + | PipelineRedirection::Separate { out: target, .. } + | PipelineRedirection::Separate { err: target, .. } + if target.span().contains(global_cursor_offset) => + { + if let Some(pos) = target.expr().and_then(|expr| { + find_matching_block_end_in_expr( + line, + working_set, + expr, + global_span_offset, + global_cursor_offset, + ) + }) { return Some(pos); } } + _ => {} } } } diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index 369556326c..c2a6743c86 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -241,16 +241,9 @@ pub fn eval_source( } let b = if allow_return { - eval_block_with_early_return::( - engine_state, - stack, - &block, - input, - false, - false, - ) + eval_block_with_early_return::(engine_state, stack, &block, input) } else { - eval_block::(engine_state, stack, &block, input, false, false) + eval_block::(engine_state, stack, &block, input) }; match b { diff --git a/crates/nu-cli/tests/support/completions_helpers.rs b/crates/nu-cli/tests/support/completions_helpers.rs index 880bf2fefe..1c6b125c75 100644 --- a/crates/nu-cli/tests/support/completions_helpers.rs +++ b/crates/nu-cli/tests/support/completions_helpers.rs @@ -200,9 +200,7 @@ pub fn merge_input( engine_state, stack, &block, - PipelineData::Value(Value::nothing(Span::unknown(),), None), - false, - false, + PipelineData::Value(Value::nothing(Span::unknown()), None), ) .is_ok()); diff --git a/crates/nu-cmd-base/src/hook.rs b/crates/nu-cmd-base/src/hook.rs index 8422233e71..a5f13a4b2d 100644 --- a/crates/nu-cmd-base/src/hook.rs +++ b/crates/nu-cmd-base/src/hook.rs @@ -116,7 +116,7 @@ pub fn eval_hook( }) .collect(); - match eval_block::(engine_state, stack, &block, input, false, false) { + match eval_block::(engine_state, stack, &block, input) { Ok(pipeline_data) => { output = pipeline_data; } @@ -244,14 +244,7 @@ pub fn eval_hook( }) .collect(); - match eval_block::( - engine_state, - stack, - &block, - input, - false, - false, - ) { + match eval_block::(engine_state, stack, &block, input) { Ok(pipeline_data) => { output = pipeline_data; } @@ -327,7 +320,9 @@ fn run_hook_block( let input = optional_input.unwrap_or_else(PipelineData::empty); - let mut callee_stack = stack.gather_captures(engine_state, &block.captures); + let mut callee_stack = stack + .gather_captures(engine_state, &block.captures) + .reset_pipes(); for (idx, PositionalArg { var_id, .. }) in block.signature.required_positional.iter().enumerate() @@ -349,8 +344,6 @@ fn run_hook_block( &mut callee_stack, block, input, - false, - false, )?; if let PipelineData::Value(Value::Error { error, .. }, _) = pipeline_data { diff --git a/crates/nu-cmd-dataframe/src/dataframe/test_dataframe.rs b/crates/nu-cmd-dataframe/src/dataframe/test_dataframe.rs index eef6120707..b668745ec2 100644 --- a/crates/nu-cmd-dataframe/src/dataframe/test_dataframe.rs +++ b/crates/nu-cmd-dataframe/src/dataframe/test_dataframe.rs @@ -79,18 +79,12 @@ pub fn test_dataframe_example(engine_state: &mut Box, example: &Exa .merge_delta(delta) .expect("Error merging delta"); - let mut stack = Stack::new(); + let mut stack = Stack::new().capture(); - let result = eval_block::( - engine_state, - &mut stack, - &block, - PipelineData::empty(), - true, - true, - ) - .unwrap_or_else(|err| panic!("test eval error in `{}`: {:?}", example.example, err)) - .into_value(Span::test_data()); + let result = + eval_block::(engine_state, &mut stack, &block, PipelineData::empty()) + .unwrap_or_else(|err| panic!("test eval error in `{}`: {:?}", example.example, err)) + .into_value(Span::test_data()); println!("input: {}", example.example); println!("result: {result:?}"); diff --git a/crates/nu-cmd-extra/src/extra/filters/each_while.rs b/crates/nu-cmd-extra/src/extra/filters/each_while.rs index 03b16dc6d8..133ec43c3b 100644 --- a/crates/nu-cmd-extra/src/extra/filters/each_while.rs +++ b/crates/nu-cmd-extra/src/extra/filters/each_while.rs @@ -83,8 +83,6 @@ impl Command for EachWhile { let orig_env_vars = stack.env_vars.clone(); let orig_env_hidden = stack.env_hidden.clone(); let span = call.head; - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; let eval_block_with_early_return = get_eval_block_with_early_return(&engine_state); match input { @@ -111,8 +109,6 @@ impl Command for EachWhile { &mut stack, &block, x.into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) { Ok(v) => { let value = v.into_value(span); @@ -155,8 +151,6 @@ impl Command for EachWhile { &mut stack, &block, x.into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) { Ok(v) => { let value = v.into_value(span); @@ -185,8 +179,6 @@ impl Command for EachWhile { &mut stack, &block, x.into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) } } diff --git a/crates/nu-cmd-extra/src/extra/filters/update_cells.rs b/crates/nu-cmd-extra/src/extra/filters/update_cells.rs index 85c6b6147b..4dbd9837c8 100644 --- a/crates/nu-cmd-extra/src/extra/filters/update_cells.rs +++ b/crates/nu-cmd-extra/src/extra/filters/update_cells.rs @@ -105,9 +105,6 @@ impl Command for UpdateCells { let block: Block = engine_state.get_block(block.block_id).clone(); let eval_block_fn = get_eval_block(&engine_state); - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; - let span = call.head; stack.with_env(&orig_env_vars, &orig_env_hidden); @@ -130,8 +127,6 @@ impl Command for UpdateCells { stack, block, columns, - redirect_stdout, - redirect_stderr, span, eval_block_fn, } @@ -146,8 +141,6 @@ struct UpdateCellIterator { engine_state: EngineState, stack: Stack, block: Block, - redirect_stdout: bool, - redirect_stderr: bool, eval_block_fn: EvalBlockFn, span: Span, } @@ -177,8 +170,6 @@ impl Iterator for UpdateCellIterator { &self.engine_state, &mut self.stack, &self.block, - self.redirect_stdout, - self.redirect_stderr, span, self.eval_block_fn, ), @@ -192,8 +183,6 @@ impl Iterator for UpdateCellIterator { &self.engine_state, &mut self.stack, &self.block, - self.redirect_stdout, - self.redirect_stderr, self.span, self.eval_block_fn, )), @@ -210,8 +199,6 @@ fn process_cell( engine_state: &EngineState, stack: &mut Stack, block: &Block, - redirect_stdout: bool, - redirect_stderr: bool, span: Span, eval_block_fn: EvalBlockFn, ) -> Value { @@ -221,14 +208,7 @@ fn process_cell( } } - match eval_block_fn( - engine_state, - stack, - block, - val.into_pipeline_data(), - redirect_stdout, - redirect_stderr, - ) { + match eval_block_fn(engine_state, stack, block, val.into_pipeline_data()) { Ok(pd) => pd.into_value(span), Err(e) => Value::error(e, span), } diff --git a/crates/nu-cmd-extra/src/extra/strings/format/command.rs b/crates/nu-cmd-extra/src/extra/strings/format/command.rs index 2a5a5a1611..4ec95ff37b 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/command.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/command.rs @@ -295,7 +295,7 @@ fn format_record( } } FormatOperation::ValueNeedEval(_col_name, span) => { - let exp = parse_expression(working_set, &[*span], false); + let exp = parse_expression(working_set, &[*span]); match working_set.parse_errors.first() { None => { let parsed_result = eval_expression(engine_state, stack, &exp); diff --git a/crates/nu-cmd-lang/src/core_commands/collect.rs b/crates/nu-cmd-lang/src/core_commands/collect.rs index 55fefec087..be061373bc 100644 --- a/crates/nu-cmd-lang/src/core_commands/collect.rs +++ b/crates/nu-cmd-lang/src/core_commands/collect.rs @@ -45,7 +45,8 @@ impl Command for Collect { let capture_block: Closure = call.req(engine_state, stack, 0)?; let block = engine_state.get_block(capture_block.block_id).clone(); - let mut stack_captures = stack.captures_to_stack(capture_block.captures.clone()); + let mut stack_captures = + stack.captures_to_stack_preserve_stdio(capture_block.captures.clone()); let metadata = input.metadata(); let input: Value = input.into_value(call.head); @@ -65,8 +66,6 @@ impl Command for Collect { &mut stack_captures, &block, input.into_pipeline_data(), - call.redirect_stdout, - call.redirect_stderr, ) .map(|x| x.set_metadata(metadata)); diff --git a/crates/nu-cmd-lang/src/core_commands/do_.rs b/crates/nu-cmd-lang/src/core_commands/do_.rs index bc99793977..62cff41196 100644 --- a/crates/nu-cmd-lang/src/core_commands/do_.rs +++ b/crates/nu-cmd-lang/src/core_commands/do_.rs @@ -5,8 +5,8 @@ use nu_protocol::ast::Call; use nu_protocol::engine::{Closure, Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, IntoSpanned, ListStream, PipelineData, RawStream, ShellError, Signature, - Span, SyntaxShape, Type, Value, + Category, Example, IntoSpanned, IoStream, ListStream, PipelineData, RawStream, ShellError, + Signature, Span, SyntaxShape, Type, Value, }; #[derive(Clone)] @@ -79,19 +79,12 @@ impl Command for Do { let capture_errors = call.has_flag(engine_state, caller_stack, "capture-errors")?; let has_env = call.has_flag(engine_state, caller_stack, "env")?; - let mut callee_stack = caller_stack.captures_to_stack(block.captures); + let mut callee_stack = caller_stack.captures_to_stack_preserve_stdio(block.captures); let block = engine_state.get_block(block.block_id); bind_args_to(&mut callee_stack, &block.signature, rest, call.head)?; let eval_block_with_early_return = get_eval_block_with_early_return(engine_state); - let result = eval_block_with_early_return( - engine_state, - &mut callee_stack, - block, - input, - call.redirect_stdout, - call.redirect_stdout, - ); + let result = eval_block_with_early_return(engine_state, &mut callee_stack, block, input); if has_env { // Merge the block's environment to the current stack @@ -204,7 +197,9 @@ impl Command for Do { span, metadata, trim_end_newline, - }) if ignore_program_errors && !call.redirect_stdout => { + }) if ignore_program_errors + && !matches!(caller_stack.stdout(), IoStream::Pipe | IoStream::Capture) => + { Ok(PipelineData::ExternalStream { stdout, stderr, diff --git a/crates/nu-cmd-lang/src/core_commands/echo.rs b/crates/nu-cmd-lang/src/core_commands/echo.rs index b18490c58e..6dc7769652 100644 --- a/crates/nu-cmd-lang/src/core_commands/echo.rs +++ b/crates/nu-cmd-lang/src/core_commands/echo.rs @@ -2,8 +2,8 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, - Value, + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, + Type, Value, }; #[derive(Clone)] @@ -38,8 +38,13 @@ little reason to use this over just writing the values as-is."# call: &Call, _input: PipelineData, ) -> Result { - let args = call.rest(engine_state, stack, 0); - run(engine_state, args, stack, call) + let mut args = call.rest(engine_state, stack, 0)?; + let value = match args.len() { + 0 => Value::string("", call.head), + 1 => args.pop().expect("one element"), + _ => Value::list(args, call.head), + }; + Ok(value.into_pipeline_data()) } fn examples(&self) -> Vec { @@ -62,43 +67,6 @@ little reason to use this over just writing the values as-is."# } } -fn run( - engine_state: &EngineState, - args: Result, ShellError>, - stack: &mut Stack, - call: &Call, -) -> Result { - let result = args.map(|to_be_echoed| { - let n = to_be_echoed.len(); - match n.cmp(&1usize) { - // More than one value is converted in a stream of values - std::cmp::Ordering::Greater => PipelineData::ListStream( - ListStream::from_stream(to_be_echoed.into_iter(), engine_state.ctrlc.clone()), - None, - ), - - // But a single value can be forwarded as it is - std::cmp::Ordering::Equal => PipelineData::Value(to_be_echoed[0].clone(), None), - - // When there are no elements, we echo the empty string - std::cmp::Ordering::Less => PipelineData::Value(Value::string("", call.head), None), - } - }); - - // If echo is not redirected, then print to the screen (to behave in a similar way to other shells) - if !call.redirect_stdout { - match result { - Ok(pipeline) => { - pipeline.print(engine_state, stack, false, false)?; - Ok(PipelineData::Empty) - } - Err(err) => Err(err), - } - } else { - result - } -} - #[cfg(test)] mod test { #[test] diff --git a/crates/nu-cmd-lang/src/core_commands/for_.rs b/crates/nu-cmd-lang/src/core_commands/for_.rs index b8c915f90c..f99617cd47 100644 --- a/crates/nu-cmd-lang/src/core_commands/for_.rs +++ b/crates/nu-cmd-lang/src/core_commands/for_.rs @@ -84,8 +84,8 @@ impl Command for For { let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); let block = engine_state.get_block(block.block_id).clone(); - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; + + let stack = &mut stack.push_redirection(None, None); match values { Value::List { vals, .. } => { @@ -109,14 +109,7 @@ impl Command for For { }, ); - match eval_block( - &engine_state, - stack, - &block, - PipelineData::empty(), - redirect_stdout, - redirect_stderr, - ) { + match eval_block(&engine_state, stack, &block, PipelineData::empty()) { Err(ShellError::Break { .. }) => { break; } @@ -154,14 +147,7 @@ impl Command for For { }, ); - match eval_block( - &engine_state, - stack, - &block, - PipelineData::empty(), - redirect_stdout, - redirect_stderr, - ) { + match eval_block(&engine_state, stack, &block, PipelineData::empty()) { Err(ShellError::Break { .. }) => { break; } @@ -185,15 +171,7 @@ impl Command for For { x => { stack.add_var(var_id, x); - eval_block( - &engine_state, - stack, - &block, - PipelineData::empty(), - redirect_stdout, - redirect_stderr, - )? - .into_value(head); + eval_block(&engine_state, stack, &block, PipelineData::empty())?.into_value(head); } } Ok(PipelineData::empty()) diff --git a/crates/nu-cmd-lang/src/core_commands/if_.rs b/crates/nu-cmd-lang/src/core_commands/if_.rs index 29a65843c9..0d8c388451 100644 --- a/crates/nu-cmd-lang/src/core_commands/if_.rs +++ b/crates/nu-cmd-lang/src/core_commands/if_.rs @@ -115,47 +115,19 @@ impl Command for If { Value::Bool { val, .. } => { if *val { let block = engine_state.get_block(then_block.block_id); - eval_block( - engine_state, - stack, - block, - input, - call.redirect_stdout, - call.redirect_stderr, - ) + eval_block(engine_state, stack, block, input) } else if let Some(else_case) = else_case { if let Some(else_expr) = else_case.as_keyword() { if let Some(block_id) = else_expr.as_block() { let block = engine_state.get_block(block_id); - eval_block( - engine_state, - stack, - block, - input, - call.redirect_stdout, - call.redirect_stderr, - ) + eval_block(engine_state, stack, block, input) } else { - eval_expression_with_input( - engine_state, - stack, - else_expr, - input, - call.redirect_stdout, - call.redirect_stderr, - ) - .map(|res| res.0) + eval_expression_with_input(engine_state, stack, else_expr, input) + .map(|res| res.0) } } else { - eval_expression_with_input( - engine_state, - stack, - else_case, - input, - call.redirect_stdout, - call.redirect_stderr, - ) - .map(|res| res.0) + eval_expression_with_input(engine_state, stack, else_case, input) + .map(|res| res.0) } } else { Ok(PipelineData::empty()) diff --git a/crates/nu-cmd-lang/src/core_commands/ignore.rs b/crates/nu-cmd-lang/src/core_commands/ignore.rs index 79746bb533..b28ba1810c 100644 --- a/crates/nu-cmd-lang/src/core_commands/ignore.rs +++ b/crates/nu-cmd-lang/src/core_commands/ignore.rs @@ -1,6 +1,8 @@ use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet}; -use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value}; +use nu_protocol::{ + Category, Example, IoStream, PipelineData, ShellError, Signature, Span, Type, Value, +}; #[derive(Clone)] pub struct Ignore; @@ -56,6 +58,10 @@ impl Command for Ignore { result: Some(Value::nothing(Span::test_data())), }] } + + fn stdio_redirect(&self) -> (Option, Option) { + (Some(IoStream::Null), None) + } } #[cfg(test)] diff --git a/crates/nu-cmd-lang/src/core_commands/lazy_make.rs b/crates/nu-cmd-lang/src/core_commands/lazy_make.rs index e3a7414e82..22671b501d 100644 --- a/crates/nu-cmd-lang/src/core_commands/lazy_make.rs +++ b/crates/nu-cmd-lang/src/core_commands/lazy_make.rs @@ -86,10 +86,12 @@ impl Command for LazyMake { } } + let stack = stack.clone().reset_stdio().capture(); + Ok(Value::lazy_record( Box::new(NuLazyRecord { engine_state: engine_state.clone(), - stack: Arc::new(Mutex::new(stack.clone())), + stack: Arc::new(Mutex::new(stack)), columns: columns.into_iter().map(|s| s.item).collect(), get_value, span, @@ -152,8 +154,6 @@ impl<'a> LazyRecord<'a> for NuLazyRecord { &mut stack, block, PipelineData::Value(column_value, None), - false, - false, ); pipeline_result.map(|data| match data { diff --git a/crates/nu-cmd-lang/src/core_commands/let_.rs b/crates/nu-cmd-lang/src/core_commands/let_.rs index 8aaa178846..639a6a43db 100644 --- a/crates/nu-cmd-lang/src/core_commands/let_.rs +++ b/crates/nu-cmd-lang/src/core_commands/let_.rs @@ -64,7 +64,8 @@ impl Command for Let { let block = engine_state.get_block(block_id); let eval_block = get_eval_block(engine_state); - let pipeline_data = eval_block(engine_state, stack, block, input, true, false)?; + let stack = &mut stack.start_capture(); + let pipeline_data = eval_block(engine_state, stack, block, input)?; let mut value = pipeline_data.into_value(call.head); // if given variable type is Glob, and our result is string diff --git a/crates/nu-cmd-lang/src/core_commands/loop_.rs b/crates/nu-cmd-lang/src/core_commands/loop_.rs index 63cabd6de0..9718114fb5 100644 --- a/crates/nu-cmd-lang/src/core_commands/loop_.rs +++ b/crates/nu-cmd-lang/src/core_commands/loop_.rs @@ -36,6 +36,8 @@ impl Command for Loop { let block: Block = call.req(engine_state, stack, 0)?; let eval_block = get_eval_block(engine_state); + let stack = &mut stack.push_redirection(None, None); + loop { if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { break; @@ -43,14 +45,7 @@ impl Command for Loop { let block = engine_state.get_block(block.block_id); - match eval_block( - engine_state, - stack, - block, - PipelineData::empty(), - call.redirect_stdout, - call.redirect_stderr, - ) { + match eval_block(engine_state, stack, block, PipelineData::empty()) { Err(ShellError::Break { .. }) => { break; } diff --git a/crates/nu-cmd-lang/src/core_commands/match_.rs b/crates/nu-cmd-lang/src/core_commands/match_.rs index aab9c7130c..79e82faa34 100644 --- a/crates/nu-cmd-lang/src/core_commands/match_.rs +++ b/crates/nu-cmd-lang/src/core_commands/match_.rs @@ -70,24 +70,10 @@ impl Command for Match { if guard_matches { return if let Some(block_id) = match_.1.as_block() { let block = engine_state.get_block(block_id); - eval_block( - engine_state, - stack, - block, - input, - call.redirect_stdout, - call.redirect_stderr, - ) + eval_block(engine_state, stack, block, input) } else { - eval_expression_with_input( - engine_state, - stack, - &match_.1, - input, - call.redirect_stdout, - call.redirect_stderr, - ) - .map(|x| x.0) + eval_expression_with_input(engine_state, stack, &match_.1, input) + .map(|x| x.0) }; } } diff --git a/crates/nu-cmd-lang/src/core_commands/mut_.rs b/crates/nu-cmd-lang/src/core_commands/mut_.rs index 24f2a8119f..b05a03ebe7 100644 --- a/crates/nu-cmd-lang/src/core_commands/mut_.rs +++ b/crates/nu-cmd-lang/src/core_commands/mut_.rs @@ -65,15 +65,8 @@ impl Command for Mut { let block = engine_state.get_block(block_id); let eval_block = get_eval_block(engine_state); - - let pipeline_data = eval_block( - engine_state, - stack, - block, - input, - call.redirect_stdout, - call.redirect_stderr, - )?; + let stack = &mut stack.start_capture(); + let pipeline_data = eval_block(engine_state, stack, block, input)?; let mut value = pipeline_data.into_value(call.head); // if given variable type is Glob, and our result is string diff --git a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs index 865f8728d6..f3d5610de1 100644 --- a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs @@ -124,7 +124,9 @@ impl Command for OverlayUse { )?; let block = engine_state.get_block(block_id); - let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures); + let mut callee_stack = caller_stack + .gather_captures(engine_state, &block.captures) + .reset_pipes(); if let Some(path) = &maybe_path { // Set the currently evaluated directory, if the argument is a valid path @@ -142,15 +144,7 @@ impl Command for OverlayUse { } let eval_block = get_eval_block(engine_state); - - let _ = eval_block( - engine_state, - &mut callee_stack, - block, - input, - call.redirect_stdout, - call.redirect_stderr, - ); + let _ = eval_block(engine_state, &mut callee_stack, block, input); // The export-env block should see the env vars *before* activating this overlay caller_stack.add_overlay(overlay_name); diff --git a/crates/nu-cmd-lang/src/core_commands/try_.rs b/crates/nu-cmd-lang/src/core_commands/try_.rs index a92ecadccb..fec1ec46d8 100644 --- a/crates/nu-cmd-lang/src/core_commands/try_.rs +++ b/crates/nu-cmd-lang/src/core_commands/try_.rs @@ -50,7 +50,7 @@ impl Command for Try { let try_block = engine_state.get_block(try_block.block_id); let eval_block = get_eval_block(engine_state); - let result = eval_block(engine_state, stack, try_block, input, false, false); + let result = eval_block(engine_state, stack, try_block, input); match result { Err(error) => { @@ -117,8 +117,6 @@ fn handle_catch( catch_block, // Make the error accessible with $in, too err_value.into_pipeline_data(), - false, - false, ) } else { Ok(PipelineData::empty()) diff --git a/crates/nu-cmd-lang/src/core_commands/use_.rs b/crates/nu-cmd-lang/src/core_commands/use_.rs index 29b7a90546..bd47991c21 100644 --- a/crates/nu-cmd-lang/src/core_commands/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/use_.rs @@ -105,7 +105,9 @@ This command is a parser keyword. For details, check: .as_ref() .and_then(|path| path.parent().map(|p| p.to_path_buf())); - let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures); + let mut callee_stack = caller_stack + .gather_captures(engine_state, &block.captures) + .reset_pipes(); // If so, set the currently evaluated directory (file-relative PWD) if let Some(parent) = maybe_parent { @@ -121,14 +123,7 @@ This command is a parser keyword. For details, check: let eval_block = get_eval_block(engine_state); // Run the block (discard the result) - let _ = eval_block( - engine_state, - &mut callee_stack, - block, - input, - call.redirect_stdout, - call.redirect_stderr, - )?; + let _ = eval_block(engine_state, &mut callee_stack, block, input)?; // Merge the block's environment to the current stack redirect_env(engine_state, caller_stack, &callee_stack); diff --git a/crates/nu-cmd-lang/src/core_commands/while_.rs b/crates/nu-cmd-lang/src/core_commands/while_.rs index 5795bf4923..d98c69d015 100644 --- a/crates/nu-cmd-lang/src/core_commands/while_.rs +++ b/crates/nu-cmd-lang/src/core_commands/while_.rs @@ -48,6 +48,8 @@ impl Command for While { let eval_expression = get_eval_expression(engine_state); let eval_block = get_eval_block(engine_state); + let stack = &mut stack.push_redirection(None, None); + loop { if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { break; @@ -60,14 +62,7 @@ impl Command for While { if *val { let block = engine_state.get_block(block.block_id); - match eval_block( - engine_state, - stack, - block, - PipelineData::empty(), - call.redirect_stdout, - call.redirect_stderr, - ) { + match eval_block(engine_state, stack, block, PipelineData::empty()) { Err(ShellError::Break { .. }) => { break; } diff --git a/crates/nu-cmd-lang/src/example_support.rs b/crates/nu-cmd-lang/src/example_support.rs index d4ff801eca..7010fc9a4e 100644 --- a/crates/nu-cmd-lang/src/example_support.rs +++ b/crates/nu-cmd-lang/src/example_support.rs @@ -112,12 +112,11 @@ pub fn eval_block( .merge_delta(delta) .expect("Error merging delta"); - let mut stack = Stack::new(); + let mut stack = Stack::new().capture(); stack.add_env_var("PWD".to_string(), Value::test_string(cwd.to_string_lossy())); - match nu_engine::eval_block::(engine_state, &mut stack, &block, input, true, true) - { + match nu_engine::eval_block::(engine_state, &mut stack, &block, input) { Err(err) => panic!("test eval error in `{}`: {:?}", "TODO", err), Ok(result) => result.into_value(Span::test_data()), } @@ -128,7 +127,7 @@ pub fn check_example_evaluates_to_expected_output( cwd: &std::path::Path, engine_state: &mut Box, ) { - let mut stack = Stack::new(); + let mut stack = Stack::new().capture(); // Set up PWD stack.add_env_var("PWD".to_string(), Value::test_string(cwd.to_string_lossy())); diff --git a/crates/nu-color-config/src/style_computer.rs b/crates/nu-color-config/src/style_computer.rs index 629313260a..4afd63e917 100644 --- a/crates/nu-color-config/src/style_computer.rs +++ b/crates/nu-color-config/src/style_computer.rs @@ -78,8 +78,6 @@ impl<'a> StyleComputer<'a> { &mut stack, &block, value.clone().into_pipeline_data(), - false, - false, ) { Ok(v) => { let value = v.into_value(span); diff --git a/crates/nu-command/src/debug/explain.rs b/crates/nu-command/src/debug/explain.rs index b02bd96828..5a128dab61 100644 --- a/crates/nu-command/src/debug/explain.rs +++ b/crates/nu-command/src/debug/explain.rs @@ -73,8 +73,9 @@ pub fn get_pipeline_elements( let mut i = 0; while i < pipeline.elements.len() { let pipeline_element = &pipeline.elements[i]; - let pipeline_expression = pipeline_element.expression().clone(); - let pipeline_span = pipeline_element.span(); + let pipeline_expression = &pipeline_element.expr; + let pipeline_span = pipeline_element.expr.span; + let element_str = String::from_utf8_lossy(engine_state.get_span_contents(pipeline_span)); let value = Value::string(element_str.to_string(), pipeline_span); diff --git a/crates/nu-command/src/debug/profile.rs b/crates/nu-command/src/debug/profile.rs index 1c0c712d0e..2127c09d64 100644 --- a/crates/nu-command/src/debug/profile.rs +++ b/crates/nu-command/src/debug/profile.rs @@ -133,8 +133,6 @@ confusing the id/parent_id hierarchy. The --expr flag is helpful for investigati &mut callee_stack, block, input, - call.redirect_stdout, - call.redirect_stdout, ); // TODO: See eval_source() diff --git a/crates/nu-command/src/debug/timeit.rs b/crates/nu-command/src/debug/timeit.rs index c10b4a4998..85d811476c 100644 --- a/crates/nu-command/src/debug/timeit.rs +++ b/crates/nu-command/src/debug/timeit.rs @@ -55,25 +55,11 @@ impl Command for TimeIt { if let Some(block_id) = command_to_run.as_block() { let eval_block = get_eval_block(engine_state); let block = engine_state.get_block(block_id); - eval_block( - engine_state, - stack, - block, - input, - call.redirect_stdout, - call.redirect_stderr, - )? + eval_block(engine_state, stack, block, input)? } else { let eval_expression_with_input = get_eval_expression_with_input(engine_state); - eval_expression_with_input( - engine_state, - stack, - command_to_run, - input, - call.redirect_stdout, - call.redirect_stderr, - ) - .map(|res| res.0)? + eval_expression_with_input(engine_state, stack, command_to_run, input) + .map(|res| res.0)? } } else { PipelineData::empty() diff --git a/crates/nu-command/src/env/config/utils.rs b/crates/nu-command/src/env/config/utils.rs index 0c6e5d4ff7..029f0864f3 100644 --- a/crates/nu-command/src/env/config/utils.rs +++ b/crates/nu-command/src/env/config/utils.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::path::PathBuf; -use nu_protocol::{Span, Spanned}; +use nu_protocol::{IoStream, Span, Spanned}; use crate::ExternalCommand; @@ -32,10 +32,8 @@ pub(crate) fn gen_command( name, args, arg_keep_raw: vec![false; number_of_args], - redirect_stdout: false, - redirect_stderr: false, - redirect_combine: false, + out: IoStream::Inherit, + err: IoStream::Inherit, env_vars: env_vars_str, - trim_end_newline: false, } } diff --git a/crates/nu-command/src/env/export_env.rs b/crates/nu-command/src/env/export_env.rs index 19d570b074..fd13151dfd 100644 --- a/crates/nu-command/src/env/export_env.rs +++ b/crates/nu-command/src/env/export_env.rs @@ -38,18 +38,13 @@ impl Command for ExportEnv { ) -> Result { let capture_block: Closure = call.req(engine_state, caller_stack, 0)?; let block = engine_state.get_block(capture_block.block_id); - let mut callee_stack = caller_stack.captures_to_stack(capture_block.captures); + let mut callee_stack = caller_stack + .captures_to_stack(capture_block.captures) + .reset_pipes(); let eval_block = get_eval_block(engine_state); - let _ = eval_block( - engine_state, - &mut callee_stack, - block, - input, - call.redirect_stdout, - call.redirect_stderr, - ); + let _ = eval_block(engine_state, &mut callee_stack, block, input); redirect_env(engine_state, caller_stack, &callee_stack); diff --git a/crates/nu-command/src/env/source_env.rs b/crates/nu-command/src/env/source_env.rs index e1bf4e12f9..88f0492308 100644 --- a/crates/nu-command/src/env/source_env.rs +++ b/crates/nu-command/src/env/source_env.rs @@ -76,18 +76,13 @@ impl Command for SourceEnv { // Evaluate the block let block = engine_state.get_block(block_id as usize).clone(); - let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures); + let mut callee_stack = caller_stack + .gather_captures(engine_state, &block.captures) + .reset_pipes(); let eval_block_with_early_return = get_eval_block_with_early_return(engine_state); - let result = eval_block_with_early_return( - engine_state, - &mut callee_stack, - &block, - input, - call.redirect_stdout, - call.redirect_stderr, - ); + let result = eval_block_with_early_return(engine_state, &mut callee_stack, &block, input); // Merge the block's environment to the current stack redirect_env(engine_state, caller_stack, &callee_stack); diff --git a/crates/nu-command/src/env/with_env.rs b/crates/nu-command/src/env/with_env.rs index b612b5af73..698f2ad215 100644 --- a/crates/nu-command/src/env/with_env.rs +++ b/crates/nu-command/src/env/with_env.rs @@ -81,12 +81,11 @@ fn with_env( call: &Call, input: PipelineData, ) -> Result { - // let external_redirection = args.call_info.args.external_redirection; let variable: Value = call.req(engine_state, stack, 0)?; let capture_block: Closure = call.req(engine_state, stack, 1)?; let block = engine_state.get_block(capture_block.block_id); - let mut stack = stack.captures_to_stack(capture_block.captures); + let mut stack = stack.captures_to_stack_preserve_stdio(capture_block.captures); let mut env: HashMap = HashMap::new(); @@ -145,14 +144,7 @@ fn with_env( stack.add_env_var(k, v); } - eval_block::( - engine_state, - &mut stack, - block, - input, - call.redirect_stdout, - call.redirect_stderr, - ) + eval_block::(engine_state, &mut stack, block, input) } #[cfg(test)] diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 3bbedcf735..a766e11c70 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -194,7 +194,7 @@ impl Command for Open { let decl = engine_state.get_decl(converter_id); let command_output = if let Some(block_id) = decl.get_block_id() { let block = engine_state.get_block(block_id); - eval_block(engine_state, stack, block, file_contents, false, false) + eval_block(engine_state, stack, block, file_contents) } else { decl.run(engine_state, stack, &Call::new(call_span), file_contents) }; diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 23bfd0dcc9..73ddacb4c9 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -5,7 +5,7 @@ use nu_protocol::ast::{Call, Expr, Expression}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::IntoSpanned; use nu_protocol::{ - Category, DataSource, Example, PipelineData, PipelineMetadata, RawStream, ShellError, + Category, DataSource, Example, IoStream, PipelineData, PipelineMetadata, RawStream, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, }; use std::fs::File; @@ -104,16 +104,7 @@ impl Command for Save { }); match input { - PipelineData::ExternalStream { stdout: None, .. } => { - // Open files to possibly truncate them - let _ = get_files(&path, stderr_path.as_ref(), append, false, false, force)?; - Ok(PipelineData::empty()) - } - PipelineData::ExternalStream { - stdout: Some(stream), - stderr, - .. - } => { + PipelineData::ExternalStream { stdout, stderr, .. } => { let (file, stderr_file) = get_files( &path, stderr_path.as_ref(), @@ -123,35 +114,42 @@ impl Command for Save { force, )?; - // delegate a thread to redirect stderr to result. - let handler = stderr - .map(|stderr_stream| match stderr_file { - Some(stderr_file) => thread::Builder::new() - .name("stderr redirector".to_string()) - .spawn(move || { - stream_to_file(stderr_stream, stderr_file, span, progress) - }), - None => thread::Builder::new() - .name("stderr redirector".to_string()) - .spawn(move || { - let _ = stderr_stream.into_bytes(); - Ok(PipelineData::empty()) - }), - }) - .transpose() - .map_err(|e| e.into_spanned(span))?; + match (stdout, stderr) { + (Some(stdout), stderr) => { + // delegate a thread to redirect stderr to result. + let handler = stderr + .map(|stderr| match stderr_file { + Some(stderr_file) => thread::Builder::new() + .name("stderr redirector".to_string()) + .spawn(move || { + stream_to_file(stderr, stderr_file, span, progress) + }), + None => thread::Builder::new() + .name("stderr redirector".to_string()) + .spawn(move || stderr.drain()), + }) + .transpose() + .map_err(|e| e.into_spanned(span))?; - let res = stream_to_file(stream, file, span, progress); - if let Some(h) = handler { - h.join().map_err(|err| ShellError::ExternalCommand { - label: "Fail to receive external commands stderr message".to_string(), - help: format!("{err:?}"), - span, - })??; - res - } else { - res - } + let res = stream_to_file(stdout, file, span, progress); + if let Some(h) = handler { + h.join().map_err(|err| ShellError::ExternalCommand { + label: "Fail to receive external commands stderr message" + .to_string(), + help: format!("{err:?}"), + span, + })??; + } + res?; + } + (None, Some(stderr)) => match stderr_file { + Some(stderr_file) => stream_to_file(stderr, stderr_file, span, progress)?, + None => stderr.drain()?, + }, + (None, None) => {} + }; + + Ok(PipelineData::Empty) } PipelineData::ListStream(ls, pipeline_metadata) if raw || prepare_path(&path, append, force)?.0.extension().is_none() => @@ -265,6 +263,10 @@ impl Command for Save { }, ] } + + fn stdio_redirect(&self) -> (Option, Option) { + (Some(IoStream::Capture), Some(IoStream::Capture)) + } } /// Convert [`PipelineData`] bytes to write in file, possibly converting @@ -430,13 +432,13 @@ fn get_files( fn stream_to_file( mut stream: RawStream, - file: File, + mut file: File, span: Span, progress: bool, -) -> Result { +) -> Result<(), ShellError> { // https://github.com/nushell/nushell/pull/9377 contains the reason // for not using BufWriter - let mut writer = file; + let writer = &mut file; let mut bytes_processed: u64 = 0; let bytes_processed_p = &mut bytes_processed; @@ -456,47 +458,45 @@ fn stream_to_file( (None, None) }; - let result = stream - .try_for_each(move |result| { - let buf = match result { - Ok(v) => match v { - Value::String { val, .. } => val.into_bytes(), - Value::Binary { val, .. } => val, - // Propagate errors by explicitly matching them before the final case. - Value::Error { error, .. } => return Err(*error), - other => { - return Err(ShellError::OnlySupportsThisInputType { - exp_input_type: "string or binary".into(), - wrong_type: other.get_type().to_string(), - dst_span: span, - src_span: other.span(), - }); - } - }, - Err(err) => { - *process_failed_p = true; - return Err(err); + stream.try_for_each(move |result| { + let buf = match result { + Ok(v) => match v { + Value::String { val, .. } => val.into_bytes(), + Value::Binary { val, .. } => val, + // Propagate errors by explicitly matching them before the final case. + Value::Error { error, .. } => return Err(*error), + other => { + return Err(ShellError::OnlySupportsThisInputType { + exp_input_type: "string or binary".into(), + wrong_type: other.get_type().to_string(), + dst_span: span, + src_span: other.span(), + }); } - }; - - // If the `progress` flag is set then - if progress { - // Update the total amount of bytes that has been saved and then print the progress bar - *bytes_processed_p += buf.len() as u64; - if let Some(bar) = &mut bar_opt { - bar.update_bar(*bytes_processed_p); - } - } - - if let Err(err) = writer.write(&buf) { + }, + Err(err) => { *process_failed_p = true; - return Err(ShellError::IOError { - msg: err.to_string(), - }); + return Err(err); } - Ok(()) - }) - .map(|_| PipelineData::empty()); + }; + + // If the `progress` flag is set then + if progress { + // Update the total amount of bytes that has been saved and then print the progress bar + *bytes_processed_p += buf.len() as u64; + if let Some(bar) = &mut bar_opt { + bar.update_bar(*bytes_processed_p); + } + } + + if let Err(err) = writer.write_all(&buf) { + *process_failed_p = true; + return Err(ShellError::IOError { + msg: err.to_string(), + }); + } + Ok(()) + })?; // If the `progress` flag is set then if progress { @@ -508,6 +508,7 @@ fn stream_to_file( } } - // And finally return the stream result. - result + file.flush()?; + + Ok(()) } diff --git a/crates/nu-command/src/filesystem/watch.rs b/crates/nu-command/src/filesystem/watch.rs index 06337a1d23..a53bed7d7b 100644 --- a/crates/nu-command/src/filesystem/watch.rs +++ b/crates/nu-command/src/filesystem/watch.rs @@ -217,8 +217,6 @@ impl Command for Watch { stack, &block, Value::nothing(call.span()).into_pipeline_data(), - call.redirect_stdout, - call.redirect_stderr, ); match eval_result { diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index 029ddd6bac..b039b58ad6 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -129,8 +129,6 @@ with 'transpose' first."# let orig_env_vars = stack.env_vars.clone(); let orig_env_hidden = stack.env_hidden.clone(); let span = call.head; - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; match input { PipelineData::Empty => Ok(PipelineData::Empty), @@ -157,10 +155,6 @@ with 'transpose' first."# &mut stack, &block, x.into_pipeline_data(), - redirect_stdout, - redirect_stderr, - // WithoutDebug, - // &None, ) { Ok(v) => Some(v.into_value(span)), Err(ShellError::Continue { span }) => Some(Value::nothing(span)), @@ -205,8 +199,6 @@ with 'transpose' first."# &mut stack, &block, x.into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) { Ok(v) => Some(v.into_value(span)), Err(ShellError::Continue { span }) => Some(Value::nothing(span)), @@ -232,8 +224,6 @@ with 'transpose' first."# &mut stack, &block, x.into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) } } diff --git a/crates/nu-command/src/filters/filter.rs b/crates/nu-command/src/filters/filter.rs index 0a81c6c83b..f7ae8ab970 100644 --- a/crates/nu-command/src/filters/filter.rs +++ b/crates/nu-command/src/filters/filter.rs @@ -62,8 +62,6 @@ a variable. On the other hand, the "row condition" syntax is not supported."# let orig_env_vars = stack.env_vars.clone(); let orig_env_hidden = stack.env_hidden.clone(); let span = call.head; - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; let eval_block = get_eval_block(&engine_state); match input { @@ -92,8 +90,6 @@ a variable. On the other hand, the "row condition" syntax is not supported."# &block, // clone() is used here because x is given to Ok() below. x.clone().into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) { Ok(v) => { if v.into_value(span).is_true() { @@ -136,8 +132,6 @@ a variable. On the other hand, the "row condition" syntax is not supported."# &block, // clone() is used here because x is given to Ok() below. x.clone().into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) { Ok(v) => { if v.into_value(span).is_true() { @@ -171,8 +165,6 @@ a variable. On the other hand, the "row condition" syntax is not supported."# &block, // clone() is used here because x is given to Ok() below. x.clone().into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) { Ok(v) => { if v.into_value(span).is_true() { diff --git a/crates/nu-command/src/filters/group_by.rs b/crates/nu-command/src/filters/group_by.rs index b80b330a24..e8fa87705a 100644 --- a/crates/nu-command/src/filters/group_by.rs +++ b/crates/nu-command/src/filters/group_by.rs @@ -171,7 +171,7 @@ pub fn group_by( Value::CellPath { val, .. } => group_cell_path(val, values)?, Value::Block { .. } | Value::Closure { .. } => { let block: Option = call.opt(engine_state, stack, 0)?; - group_closure(values, span, block, stack, engine_state, call)? + group_closure(values, span, block, stack, engine_state)? } _ => { @@ -234,7 +234,6 @@ fn group_closure( block: Option, stack: &mut Stack, engine_state: &EngineState, - call: &Call, ) -> Result>, ShellError> { let error_key = "error"; let mut groups: IndexMap> = IndexMap::new(); @@ -251,8 +250,6 @@ fn group_closure( &mut stack, block, value.clone().into_pipeline_data(), - call.redirect_stdout, - call.redirect_stderr, ); let group_key = match pipeline { diff --git a/crates/nu-command/src/filters/insert.rs b/crates/nu-command/src/filters/insert.rs index 3c9f3c2702..f738ca87c3 100644 --- a/crates/nu-command/src/filters/insert.rs +++ b/crates/nu-command/src/filters/insert.rs @@ -130,9 +130,6 @@ fn insert( let cell_path: CellPath = call.req(engine_state, stack, 0)?; let replacement: Value = call.req(engine_state, stack, 1)?; - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; - let ctrlc = engine_state.ctrlc.clone(); let eval_block = get_eval_block(engine_state); @@ -153,8 +150,6 @@ fn insert( span, engine_state, &mut stack, - redirect_stdout, - redirect_stderr, block, &cell_path.members, false, @@ -168,8 +163,6 @@ fn insert( replacement, engine_state, stack, - redirect_stdout, - redirect_stderr, &cell_path.members, matches!(first, Some(PathMember::Int { .. })), eval_block, @@ -225,8 +218,6 @@ fn insert( &mut stack, block, value.clone().into_pipeline_data(), - redirect_stdout, - redirect_stderr, )?; pre_elems.push(output.into_value(span)); @@ -243,8 +234,6 @@ fn insert( replacement, engine_state, stack, - redirect_stdout, - redirect_stderr, path, true, eval_block, @@ -282,8 +271,6 @@ fn insert( replacement_span, &engine_state, &mut stack, - redirect_stdout, - redirect_stderr, &block, &cell_path.members, false, @@ -330,8 +317,6 @@ fn insert_value_by_closure( span: Span, engine_state: &EngineState, stack: &mut Stack, - redirect_stdout: bool, - redirect_stderr: bool, block: &Block, cell_path: &[PathMember], first_path_member_int: bool, @@ -356,14 +341,7 @@ fn insert_value_by_closure( .map(IntoPipelineData::into_pipeline_data) .unwrap_or(PipelineData::Empty); - let output = eval_block_fn( - engine_state, - stack, - block, - input_at_path, - redirect_stdout, - redirect_stderr, - )?; + let output = eval_block_fn(engine_state, stack, block, input_at_path)?; value.insert_data_at_cell_path(cell_path, output.into_value(span), span) } @@ -374,8 +352,6 @@ fn insert_single_value_by_closure( replacement: Value, engine_state: &EngineState, stack: &mut Stack, - redirect_stdout: bool, - redirect_stderr: bool, cell_path: &[PathMember], first_path_member_int: bool, eval_block_fn: EvalBlockFn, @@ -390,8 +366,6 @@ fn insert_single_value_by_closure( span, engine_state, &mut stack, - redirect_stdout, - redirect_stderr, block, cell_path, first_path_member_int, diff --git a/crates/nu-command/src/filters/interleave.rs b/crates/nu-command/src/filters/interleave.rs index cc6d5c357a..6e1e0fd265 100644 --- a/crates/nu-command/src/filters/interleave.rs +++ b/crates/nu-command/src/filters/interleave.rs @@ -128,14 +128,7 @@ interleave // Evaluate the closure on this thread let block = engine_state.get_block(closure.block_id); let mut stack = stack.captures_to_stack(closure.captures); - eval_block_with_early_return( - engine_state, - &mut stack, - block, - PipelineData::Empty, - true, - false, - ) + eval_block_with_early_return(engine_state, &mut stack, block, PipelineData::Empty) })) .try_for_each(|stream| { stream.and_then(|stream| { diff --git a/crates/nu-command/src/filters/items.rs b/crates/nu-command/src/filters/items.rs index 693091a3ee..8da1116c88 100644 --- a/crates/nu-command/src/filters/items.rs +++ b/crates/nu-command/src/filters/items.rs @@ -54,7 +54,6 @@ impl Command for Items { let orig_env_vars = stack.env_vars.clone(); let orig_env_hidden = stack.env_hidden.clone(); let span = call.head; - let redirect_stderr = call.redirect_stderr; let eval_block_with_early_return = get_eval_block_with_early_return(&engine_state); let input_span = input.span().unwrap_or(call.head); @@ -81,8 +80,6 @@ impl Command for Items { &mut stack, &block, PipelineData::empty(), - true, - redirect_stderr, ) { Ok(v) => Some(v.into_value(span)), Err(ShellError::Break { .. }) => None, diff --git a/crates/nu-command/src/filters/par_each.rs b/crates/nu-command/src/filters/par_each.rs index a0de791807..ebda3c7b6f 100644 --- a/crates/nu-command/src/filters/par_each.rs +++ b/crates/nu-command/src/filters/par_each.rs @@ -129,8 +129,6 @@ impl Command for ParEach { let block_id = capture_block.block_id; let mut stack = stack.captures_to_stack(capture_block.captures); let span = call.head; - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; // A helper function sorts the output if needed let apply_order = |mut vec: Vec<(usize, Value)>| { @@ -173,8 +171,6 @@ impl Command for ParEach { &mut stack, block, x.into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) { Ok(v) => v.into_value(span), Err(error) => Value::error( @@ -213,8 +209,6 @@ impl Command for ParEach { &mut stack, block, x.clone().into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) { Ok(v) => v.into_value(span), Err(error) => Value::error( @@ -252,8 +246,6 @@ impl Command for ParEach { &mut stack, block, x.into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) { Ok(v) => v.into_value(span), Err(error) => Value::error( @@ -297,8 +289,6 @@ impl Command for ParEach { &mut stack, block, x.into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) { Ok(v) => v.into_value(span), Err(error) => Value::error(error, span), @@ -326,8 +316,6 @@ impl Command for ParEach { &mut stack, block, x.into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) } } diff --git a/crates/nu-command/src/filters/reduce.rs b/crates/nu-command/src/filters/reduce.rs index 8635fb81bf..0e539d6f8d 100644 --- a/crates/nu-command/src/filters/reduce.rs +++ b/crates/nu-command/src/filters/reduce.rs @@ -107,9 +107,6 @@ impl Command for Reduce { let orig_env_vars = stack.env_vars.clone(); let orig_env_hidden = stack.env_hidden.clone(); - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; - // To enumerate over the input (for the index argument), // it must be converted into an iterator using into_iter(). let mut input_iter = input.into_iter(); @@ -130,9 +127,7 @@ impl Command for Reduce { let mut acc = start_val; - let mut input_iter = input_iter.peekable(); - - while let Some(x) = input_iter.next() { + for x in input_iter { // with_env() is used here to ensure that each iteration uses // a different set of environment variables. // Hence, a 'cd' in the first loop won't affect the next loop. @@ -157,9 +152,6 @@ impl Command for Reduce { &mut stack, block, PipelineData::empty(), - // redirect stdout until its the last input value - redirect_stdout || input_iter.peek().is_some(), - redirect_stderr, )? .into_value(span); diff --git a/crates/nu-command/src/filters/rename.rs b/crates/nu-command/src/filters/rename.rs index 0998d99b5c..2cf8cf8111 100644 --- a/crates/nu-command/src/filters/rename.rs +++ b/crates/nu-command/src/filters/rename.rs @@ -140,8 +140,6 @@ fn rename( } None => None, }; - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; let block_info = if let Some(capture_block) = call.get_flag::(engine_state, stack, "block")? { let engine_state = engine_state.clone(); @@ -185,8 +183,6 @@ fn rename( &mut stack, &block, Value::string(c.clone(), span).into_pipeline_data(), - redirect_stdout, - redirect_stderr, ); match eval_result { diff --git a/crates/nu-command/src/filters/skip/skip_until.rs b/crates/nu-command/src/filters/skip/skip_until.rs index b26d134c59..bef2780223 100644 --- a/crates/nu-command/src/filters/skip/skip_until.rs +++ b/crates/nu-command/src/filters/skip/skip_until.rs @@ -92,9 +92,6 @@ impl Command for SkipUntil { let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; - let eval_block = get_eval_block(&engine_state); Ok(input @@ -104,17 +101,10 @@ impl Command for SkipUntil { stack.add_var(var_id, value.clone()); } - !eval_block( - &engine_state, - &mut stack, - &block, - PipelineData::empty(), - redirect_stdout, - redirect_stderr, - ) - .map_or(false, |pipeline_data| { - pipeline_data.into_value(span).is_true() - }) + !eval_block(&engine_state, &mut stack, &block, PipelineData::empty()) + .map_or(false, |pipeline_data| { + pipeline_data.into_value(span).is_true() + }) }) .into_pipeline_data_with_metadata(metadata, ctrlc)) } diff --git a/crates/nu-command/src/filters/skip/skip_while.rs b/crates/nu-command/src/filters/skip/skip_while.rs index 8dfbfe4162..3460c2f493 100644 --- a/crates/nu-command/src/filters/skip/skip_while.rs +++ b/crates/nu-command/src/filters/skip/skip_while.rs @@ -97,9 +97,6 @@ impl Command for SkipWhile { let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; - let eval_block = get_eval_block(&engine_state); Ok(input @@ -109,17 +106,10 @@ impl Command for SkipWhile { stack.add_var(var_id, value.clone()); } - eval_block( - &engine_state, - &mut stack, - &block, - PipelineData::empty(), - redirect_stdout, - redirect_stderr, - ) - .map_or(false, |pipeline_data| { - pipeline_data.into_value(span).is_true() - }) + eval_block(&engine_state, &mut stack, &block, PipelineData::empty()) + .map_or(false, |pipeline_data| { + pipeline_data.into_value(span).is_true() + }) }) .into_pipeline_data_with_metadata(metadata, ctrlc)) } diff --git a/crates/nu-command/src/filters/take/take_until.rs b/crates/nu-command/src/filters/take/take_until.rs index 88f64a3e61..e4f9c47b6e 100644 --- a/crates/nu-command/src/filters/take/take_until.rs +++ b/crates/nu-command/src/filters/take/take_until.rs @@ -89,9 +89,6 @@ impl Command for TakeUntil { let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; - let eval_block = get_eval_block(&engine_state); Ok(input @@ -101,17 +98,10 @@ impl Command for TakeUntil { stack.add_var(var_id, value.clone()); } - !eval_block( - &engine_state, - &mut stack, - &block, - PipelineData::empty(), - redirect_stdout, - redirect_stderr, - ) - .map_or(false, |pipeline_data| { - pipeline_data.into_value(span).is_true() - }) + !eval_block(&engine_state, &mut stack, &block, PipelineData::empty()) + .map_or(false, |pipeline_data| { + pipeline_data.into_value(span).is_true() + }) }) .into_pipeline_data_with_metadata(metadata, ctrlc)) } diff --git a/crates/nu-command/src/filters/take/take_while.rs b/crates/nu-command/src/filters/take/take_while.rs index 3777af212e..99848c9829 100644 --- a/crates/nu-command/src/filters/take/take_while.rs +++ b/crates/nu-command/src/filters/take/take_while.rs @@ -88,9 +88,6 @@ impl Command for TakeWhile { let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; - let eval_block = get_eval_block(&engine_state); Ok(input @@ -100,17 +97,10 @@ impl Command for TakeWhile { stack.add_var(var_id, value.clone()); } - eval_block( - &engine_state, - &mut stack, - &block, - PipelineData::empty(), - redirect_stdout, - redirect_stderr, - ) - .map_or(false, |pipeline_data| { - pipeline_data.into_value(span).is_true() - }) + eval_block(&engine_state, &mut stack, &block, PipelineData::empty()) + .map_or(false, |pipeline_data| { + pipeline_data.into_value(span).is_true() + }) }) .into_pipeline_data_with_metadata(metadata, ctrlc)) } diff --git a/crates/nu-command/src/filters/tee.rs b/crates/nu-command/src/filters/tee.rs index 19c0454197..85818488ab 100644 --- a/crates/nu-command/src/filters/tee.rs +++ b/crates/nu-command/src/filters/tee.rs @@ -4,8 +4,8 @@ use nu_engine::{get_eval_block_with_early_return, CallExt}; use nu_protocol::{ ast::Call, engine::{Closure, Command, EngineState, Stack}, - Category, Example, IntoInterruptiblePipelineData, IntoSpanned, PipelineData, RawStream, - ShellError, Signature, Spanned, SyntaxShape, Type, Value, + Category, Example, IntoInterruptiblePipelineData, IntoSpanned, IoStream, PipelineData, + RawStream, ShellError, Signature, Spanned, SyntaxShape, Type, Value, }; #[derive(Clone)] @@ -49,8 +49,8 @@ use it in your pipeline."# result: None, }, Example { - example: "do { nu --commands 'print -e error; print ok' } | \ - tee --stderr { save error.log } | complete", + example: + "nu -c 'print -e error; print ok' | tee --stderr { save error.log } | complete", description: "Save error messages from an external command to a file without \ redirecting them", result: None, @@ -78,7 +78,9 @@ use it in your pipeline."# } = call.req(engine_state, stack, 0)?; let closure_engine_state = engine_state.clone(); - let mut closure_stack = stack.captures_to_stack(captures); + let mut closure_stack = stack + .captures_to_stack_preserve_stdio(captures) + .reset_pipes(); let metadata = input.metadata(); let metadata_clone = metadata.clone(); @@ -121,46 +123,32 @@ use it in your pipeline."# &mut closure_stack, closure_engine_state.get_block(block_id), input_from_channel, - false, - false, ); // Make sure to drain any iterator produced to avoid unexpected behavior result.and_then(|data| data.drain()) }; if use_stderr { - if let Some(stderr) = stderr { - let iter = tee(stderr.stream, with_stream) - .map_err(|e| e.into_spanned(call.head))?; - let raw_stream = RawStream::new( - Box::new(iter.map(flatten_result)), - stderr.ctrlc, - stderr.span, - stderr.known_size, - ); - Ok(PipelineData::ExternalStream { - stdout, - stderr: Some(raw_stream), - exit_code, - span, - metadata, - trim_end_newline, + let stderr = stderr + .map(|stderr| { + let iter = tee(stderr.stream, with_stream) + .map_err(|e| e.into_spanned(call.head))?; + Ok::<_, ShellError>(RawStream::new( + Box::new(iter.map(flatten_result)), + stderr.ctrlc, + stderr.span, + stderr.known_size, + )) }) - } else { - // Throw an error if the stream doesn't have stderr. This is probably the - // user's mistake (e.g., forgetting to use `do`) - Err(ShellError::GenericError { - error: "Stream passed to `tee --stderr` does not have stderr".into(), - msg: "this stream does not contain stderr".into(), - span: Some(span), - help: Some( - "if this is an external command, you probably need to wrap \ - it in `do { ... }`" - .into(), - ), - inner: vec![], - }) - } + .transpose()?; + Ok(PipelineData::ExternalStream { + stdout, + stderr, + exit_code, + span, + metadata, + trim_end_newline, + }) } else { let stdout = stdout .map(|stdout| { @@ -203,8 +191,6 @@ use it in your pipeline."# &mut closure_stack, closure_engine_state.get_block(block_id), input_from_channel, - false, - false, ); // Make sure to drain any iterator produced to avoid unexpected behavior result.and_then(|data| data.drain()) @@ -217,6 +203,10 @@ use it in your pipeline."# } } } + + fn stdio_redirect(&self) -> (Option, Option) { + (Some(IoStream::Capture), Some(IoStream::Capture)) + } } fn panic_error() -> ShellError { diff --git a/crates/nu-command/src/filters/update.rs b/crates/nu-command/src/filters/update.rs index 02300bf5fe..6b7c482974 100644 --- a/crates/nu-command/src/filters/update.rs +++ b/crates/nu-command/src/filters/update.rs @@ -112,9 +112,6 @@ fn update( let cell_path: CellPath = call.req(engine_state, stack, 0)?; let replacement: Value = call.req(engine_state, stack, 1)?; - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; - let ctrlc = engine_state.ctrlc.clone(); let eval_block = get_eval_block(engine_state); @@ -135,8 +132,6 @@ fn update( span, engine_state, &mut stack, - redirect_stdout, - redirect_stderr, block, &cell_path.members, false, @@ -150,8 +145,6 @@ fn update( replacement, engine_state, stack, - redirect_stdout, - redirect_stderr, &cell_path.members, matches!(first, Some(PathMember::Int { .. })), eval_block, @@ -197,8 +190,6 @@ fn update( replacement, engine_state, stack, - redirect_stdout, - redirect_stderr, path, true, eval_block, @@ -229,8 +220,6 @@ fn update( replacement_span, &engine_state, &mut stack, - redirect_stdout, - redirect_stderr, &block, &cell_path.members, false, @@ -275,8 +264,6 @@ fn update_value_by_closure( span: Span, engine_state: &EngineState, stack: &mut Stack, - redirect_stdout: bool, - redirect_stderr: bool, block: &Block, cell_path: &[PathMember], first_path_member_int: bool, @@ -302,8 +289,6 @@ fn update_value_by_closure( stack, block, input_at_path.into_pipeline_data(), - redirect_stdout, - redirect_stderr, )?; value.update_data_at_cell_path(cell_path, output.into_value(span)) @@ -315,8 +300,6 @@ fn update_single_value_by_closure( replacement: Value, engine_state: &EngineState, stack: &mut Stack, - redirect_stdout: bool, - redirect_stderr: bool, cell_path: &[PathMember], first_path_member_int: bool, eval_block_fn: EvalBlockFn, @@ -331,8 +314,6 @@ fn update_single_value_by_closure( span, engine_state, &mut stack, - redirect_stdout, - redirect_stderr, block, cell_path, first_path_member_int, diff --git a/crates/nu-command/src/filters/upsert.rs b/crates/nu-command/src/filters/upsert.rs index 4ca48f4fd6..6b1e81a058 100644 --- a/crates/nu-command/src/filters/upsert.rs +++ b/crates/nu-command/src/filters/upsert.rs @@ -156,9 +156,6 @@ fn upsert( let cell_path: CellPath = call.req(engine_state, stack, 0)?; let replacement: Value = call.req(engine_state, stack, 1)?; - - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; let eval_block = get_eval_block(engine_state); let ctrlc = engine_state.ctrlc.clone(); @@ -179,8 +176,6 @@ fn upsert( span, engine_state, &mut stack, - redirect_stdout, - redirect_stderr, block, &cell_path.members, false, @@ -194,8 +189,6 @@ fn upsert( replacement, engine_state, stack, - redirect_stdout, - redirect_stderr, &cell_path.members, matches!(first, Some(PathMember::Int { .. })), eval_block, @@ -249,8 +242,6 @@ fn upsert( &mut stack, block, value.clone().into_pipeline_data(), - redirect_stdout, - redirect_stderr, )?; pre_elems.push(output.into_value(span)); @@ -264,8 +255,6 @@ fn upsert( replacement, engine_state, stack, - redirect_stdout, - redirect_stderr, path, true, eval_block, @@ -303,8 +292,6 @@ fn upsert( replacement_span, &engine_state, &mut stack, - redirect_stdout, - redirect_stderr, &block, &cell_path.members, false, @@ -349,8 +336,6 @@ fn upsert_value_by_closure( span: Span, engine_state: &EngineState, stack: &mut Stack, - redirect_stdout: bool, - redirect_stderr: bool, block: &Block, cell_path: &[PathMember], first_path_member_int: bool, @@ -375,14 +360,7 @@ fn upsert_value_by_closure( .map(IntoPipelineData::into_pipeline_data) .unwrap_or(PipelineData::Empty); - let output = eval_block_fn( - engine_state, - stack, - block, - input_at_path, - redirect_stdout, - redirect_stderr, - )?; + let output = eval_block_fn(engine_state, stack, block, input_at_path)?; value.upsert_data_at_cell_path(cell_path, output.into_value(span)) } @@ -393,8 +371,6 @@ fn upsert_single_value_by_closure( replacement: Value, engine_state: &EngineState, stack: &mut Stack, - redirect_stdout: bool, - redirect_stderr: bool, cell_path: &[PathMember], first_path_member_int: bool, eval_block_fn: EvalBlockFn, @@ -409,8 +385,6 @@ fn upsert_single_value_by_closure( span, engine_state, &mut stack, - redirect_stdout, - redirect_stderr, block, cell_path, first_path_member_int, diff --git a/crates/nu-command/src/filters/utils.rs b/crates/nu-command/src/filters/utils.rs index 0ce21deedd..687ec1b684 100644 --- a/crates/nu-command/src/filters/utils.rs +++ b/crates/nu-command/src/filters/utils.rs @@ -53,14 +53,7 @@ pub fn boolean_fold( stack.add_var(var_id, value.clone()); } - let eval = eval_block( - engine_state, - &mut stack, - block, - value.into_pipeline_data(), - call.redirect_stdout, - call.redirect_stderr, - ); + let eval = eval_block(engine_state, &mut stack, block, value.into_pipeline_data()); match eval { Err(e) => { return Err(e); diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs index d523518c9d..766c04feb8 100644 --- a/crates/nu-command/src/filters/where_.rs +++ b/crates/nu-command/src/filters/where_.rs @@ -69,9 +69,6 @@ not supported."# let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; - let eval_block = get_eval_block(&engine_state); Ok(input @@ -91,8 +88,6 @@ not supported."# &block, // clone() is used here because x is given to Ok() below. value.clone().into_pipeline_data(), - redirect_stdout, - redirect_stderr, ); match result { diff --git a/crates/nu-command/src/filters/zip.rs b/crates/nu-command/src/filters/zip.rs index 033a1ca4bb..22ea6499ac 100644 --- a/crates/nu-command/src/filters/zip.rs +++ b/crates/nu-command/src/filters/zip.rs @@ -109,14 +109,7 @@ impl Command for Zip { Value::Closure { val, .. } => { let block = engine_state.get_block(val.block_id); let mut stack = stack.captures_to_stack(val.captures); - eval_block_with_early_return( - engine_state, - &mut stack, - block, - PipelineData::Empty, - true, - false, - )? + eval_block_with_early_return(engine_state, &mut stack, block, PipelineData::Empty)? } // If any other value, use it as-is. val => val.into_pipeline_data(), diff --git a/crates/nu-command/src/formats/from/nuon.rs b/crates/nu-command/src/formats/from/nuon.rs index efa771f5fd..249efcb842 100644 --- a/crates/nu-command/src/formats/from/nuon.rs +++ b/crates/nu-command/src/formats/from/nuon.rs @@ -1,4 +1,4 @@ -use nu_protocol::ast::{Call, Expr, Expression, PipelineElement, RecordItem}; +use nu_protocol::ast::{Call, Expr, Expression, RecordItem}; use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet}; use nu_protocol::{ record, Category, Example, IntoPipelineData, PipelineData, Range, Record, ShellError, @@ -69,7 +69,7 @@ impl Command for FromNuon { src: string_input, error: "error when loading".into(), msg: "excess values when loading".into(), - span: element.span(), + span: element.expr.span, }], }); } else { @@ -109,7 +109,7 @@ impl Command for FromNuon { src: string_input, error: "error when loading".into(), msg: "detected a pipeline in nuon file".into(), - span: expr.span(), + span: expr.expr.span, }], }); } @@ -122,22 +122,7 @@ impl Command for FromNuon { ty: Type::Nothing, } } else { - match pipeline.elements.remove(0) { - PipelineElement::Expression(_, expression) - | PipelineElement::ErrPipedExpression(_, expression) - | PipelineElement::OutErrPipedExpression(_, expression) - | PipelineElement::Redirection(_, _, expression, _) - | PipelineElement::And(_, expression) - | PipelineElement::Or(_, expression) - | PipelineElement::SameTargetRedirection { - cmd: (_, expression), - .. - } - | PipelineElement::SeparateRedirection { - out: (_, expression, _), - .. - } => expression, - } + pipeline.elements.remove(0).expr } }; diff --git a/crates/nu-command/src/generators/generate.rs b/crates/nu-command/src/generators/generate.rs index 2cada09802..c0ab554115 100644 --- a/crates/nu-command/src/generators/generate.rs +++ b/crates/nu-command/src/generators/generate.rs @@ -106,8 +106,6 @@ used as the next argument to the closure, otherwise generation stops. let mut stack = stack.captures_to_stack(capture_block.item.captures); let orig_env_vars = stack.env_vars.clone(); let orig_env_hidden = stack.env_hidden.clone(); - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; let eval_block_with_early_return = get_eval_block_with_early_return(&engine_state); // A type of Option is used to represent state. Invocation @@ -135,8 +133,6 @@ used as the next argument to the closure, otherwise generation stops. &mut stack, &block, arg.into_pipeline_data(), - redirect_stdout, - redirect_stderr, ) { // no data -> output nothing and stop. Ok(PipelineData::Empty) => (None, None), diff --git a/crates/nu-command/src/misc/source.rs b/crates/nu-command/src/misc/source.rs index 140dd51c39..73933b6253 100644 --- a/crates/nu-command/src/misc/source.rs +++ b/crates/nu-command/src/misc/source.rs @@ -51,14 +51,7 @@ impl Command for Source { let eval_block_with_early_return = get_eval_block_with_early_return(engine_state); - eval_block_with_early_return( - engine_state, - stack, - &block, - input, - call.redirect_stdout, - call.redirect_stderr, - ) + eval_block_with_early_return(engine_state, stack, &block, input) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/system/complete.rs b/crates/nu-command/src/system/complete.rs index 86dad78859..5768ba5977 100644 --- a/crates/nu-command/src/system/complete.rs +++ b/crates/nu-command/src/system/complete.rs @@ -1,8 +1,8 @@ use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, IntoPipelineData, IntoSpanned, PipelineData, Record, ShellError, Signature, - Type, Value, + Category, Example, IntoPipelineData, IntoSpanned, IoStream, PipelineData, Record, ShellError, + Signature, Type, Value, }; use std::thread; @@ -115,19 +115,15 @@ impl Command for Complete { } fn examples(&self) -> Vec { - vec![ - Example { - description: - "Run the external command to completion, capturing stdout and exit_code", - example: "^external arg1 | complete", - result: None, - }, - Example { - description: - "Run external command to completion, capturing, stdout, stderr and exit_code", - example: "do { ^external arg1 } | complete", - result: None, - }, - ] + vec![Example { + description: + "Run the external command to completion, capturing stdout, stderr, and exit_code", + example: "^external arg1 | complete", + result: None, + }] + } + + fn stdio_redirect(&self) -> (Option, Option) { + (Some(IoStream::Capture), Some(IoStream::Capture)) } } diff --git a/crates/nu-command/src/system/exec.rs b/crates/nu-command/src/system/exec.rs index 68015b1a73..95cb8d221a 100644 --- a/crates/nu-command/src/system/exec.rs +++ b/crates/nu-command/src/system/exec.rs @@ -3,7 +3,7 @@ use nu_engine::current_dir; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, + Category, Example, IoStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, }; #[derive(Clone)] @@ -62,8 +62,9 @@ fn exec( stack: &mut Stack, call: &Call, ) -> Result { - let external_command = - create_external_command(engine_state, stack, call, false, false, false, false)?; + let mut external_command = create_external_command(engine_state, stack, call)?; + external_command.out = IoStream::Inherit; + external_command.err = IoStream::Inherit; let cwd = current_dir(engine_state, stack)?; let mut command = external_command.spawn_simple_command(&cwd.to_string_lossy())?; diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 90533f0964..0547d356d2 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -2,14 +2,12 @@ use nu_cmd_base::hook::eval_hook; use nu_engine::env_to_strings; use nu_engine::get_eval_expression; use nu_engine::CallExt; -use nu_protocol::IntoSpanned; -use nu_protocol::NuGlob; use nu_protocol::{ ast::{Call, Expr}, did_you_mean, engine::{Command, EngineState, Stack}, - Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, Span, Spanned, - SyntaxShape, Type, Value, + Category, Example, IntoSpanned, IoStream, ListStream, NuGlob, PipelineData, RawStream, + ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, }; use nu_system::ForegroundChild; use nu_utils::IgnoreCaseExt; @@ -19,14 +17,9 @@ use std::collections::HashMap; use std::io::{BufRead, BufReader, Read, Write}; use std::path::{Path, PathBuf}; use std::process::{Command as CommandSys, Stdio}; -use std::sync::atomic::AtomicBool; -use std::sync::mpsc::{self, SyncSender}; -use std::sync::Arc; +use std::sync::mpsc; use std::thread; -const OUTPUT_BUFFER_SIZE: usize = 1024; -const OUTPUT_BUFFERS_IN_FLIGHT: usize = 3; - #[derive(Clone)] pub struct External; @@ -76,15 +69,61 @@ impl Command for External { }); } - let command = create_external_command( - engine_state, - stack, - call, - redirect_stdout, - redirect_stderr, - redirect_combine, - trim_end_newline, - )?; + if trim_end_newline { + nu_protocol::report_error_new( + engine_state, + &ShellError::GenericError { + error: "Deprecated flag".into(), + msg: "`--trim-end-newline` is deprecated".into(), + span: Some(call.arguments_span()), + help: Some( + "trailing new lines are now removed by default when collecting into a value" + .into(), + ), + inner: vec![], + }, + ); + } + + if redirect_combine { + nu_protocol::report_error_new( + engine_state, + &ShellError::GenericError { + error: "Deprecated flag".into(), + msg: "`--redirect-combine` is deprecated".into(), + span: Some(call.arguments_span()), + help: Some("use the `o+e>|` pipe redirection instead".into()), + inner: vec![], + }, + ); + } else if redirect_stdout { + nu_protocol::report_error_new( + engine_state, + &ShellError::GenericError { + error: "Deprecated flag".into(), + msg: "`--redirect-stdout` is deprecated".into(), + span: Some(call.arguments_span()), + help: Some( + "`run-external` will now always redirect stdout if there is a pipe `|` afterwards" + .into(), + ), + inner: vec![], + }, + ); + } else if redirect_stderr { + nu_protocol::report_error_new( + engine_state, + &ShellError::GenericError { + error: "Deprecated flag".into(), + msg: "`--redirect-stderr` is deprecated".into(), + span: Some(call.arguments_span()), + help: Some("use the `e>|` stderr pipe redirection instead".into()), + inner: vec![], + }, + ); + } + + let command = create_external_command(engine_state, stack, call)?; command.run_with_input(engine_state, stack, input, false) } @@ -98,7 +137,12 @@ impl Command for External { }, Example { description: "Redirect stdout from an external command into the pipeline", - example: r#"run-external --redirect-stdout "echo" "-n" "hello" | split chars"#, + example: r#"run-external "echo" "-n" "hello" | split chars"#, + result: None, + }, + Example { + description: "Redirect stderr from an external command into the pipeline", + example: r#"run-external "nu" "-c" "print -e hello" e>| split chars"#, result: None, }, ] @@ -110,10 +154,6 @@ pub fn create_external_command( engine_state: &EngineState, stack: &mut Stack, call: &Call, - redirect_stdout: bool, - redirect_stderr: bool, - redirect_combine: bool, - trim_end_newline: bool, ) -> Result { let name: Spanned = call.req(engine_state, stack, 0)?; @@ -180,11 +220,9 @@ pub fn create_external_command( name, args: spanned_args, arg_keep_raw, - redirect_stdout, - redirect_stderr, - redirect_combine, + out: stack.stdout().clone(), + err: stack.stderr().clone(), env_vars: env_vars_str, - trim_end_newline, }) } @@ -193,11 +231,9 @@ pub struct ExternalCommand { pub name: Spanned, pub args: Vec>, pub arg_keep_raw: Vec, - pub redirect_stdout: bool, - pub redirect_stderr: bool, - pub redirect_combine: bool, + pub out: IoStream, + pub err: IoStream, pub env_vars: HashMap, - pub trim_end_newline: bool, } impl ExternalCommand { @@ -364,6 +400,7 @@ impl ExternalCommand { let mut engine_state = engine_state.clone(); if let Some(hook) = engine_state.config.hooks.command_not_found.clone() { + let stack = &mut stack.start_capture(); if let Ok(PipelineData::Value(Value::String { val, .. }, ..)) = eval_hook( &mut engine_state, @@ -412,6 +449,7 @@ impl ExternalCommand { thread::Builder::new() .name("external stdin worker".to_string()) .spawn(move || { + let stack = &mut stack.start_capture(); // Attempt to render the input as a table before piping it to the external. // This is important for pagers like `less`; // they need to get Nu data rendered for display to users. @@ -421,7 +459,7 @@ impl ExternalCommand { let input = crate::Table::run( &crate::Table, &engine_state, - &mut stack, + stack, &Call::new(head), input, ); @@ -447,63 +485,66 @@ impl ExternalCommand { #[cfg(unix)] let commandname = self.name.item.clone(); - let redirect_stdout = self.redirect_stdout; - let redirect_stderr = self.redirect_stderr; - let redirect_combine = self.redirect_combine; let span = self.name.span; - let output_ctrlc = ctrlc.clone(); - let stderr_ctrlc = ctrlc.clone(); - let (stdout_tx, stdout_rx) = mpsc::sync_channel(OUTPUT_BUFFERS_IN_FLIGHT); let (exit_code_tx, exit_code_rx) = mpsc::channel(); - let stdout = child.as_mut().stdout.take(); - let stderr = child.as_mut().stderr.take(); + let (stdout, stderr) = if let Some(combined) = reader { + ( + Some(RawStream::new( + Box::new(ByteLines::new(combined)), + ctrlc.clone(), + head, + None, + )), + None, + ) + } else { + let stdout = child.as_mut().stdout.take().map(|out| { + RawStream::new(Box::new(ByteLines::new(out)), ctrlc.clone(), head, None) + }); - // If this external is not the last expression, then its output is piped to a channel - // and we create a ListStream that can be consumed + let stderr = child.as_mut().stderr.take().map(|err| { + RawStream::new(Box::new(ByteLines::new(err)), ctrlc.clone(), head, None) + }); - // First create a thread to redirect the external's stdout and wait for an exit code. + if matches!(self.err, IoStream::Pipe) { + (stderr, stdout) + } else { + (stdout, stderr) + } + }; + + // Create a thread to wait for an exit code. thread::Builder::new() - .name("stdout redirector + exit code waiter".to_string()) + .name("exit code waiter".into()) .spawn(move || { - if redirect_stdout { - let stdout = stdout.ok_or_else(|| { - ShellError::ExternalCommand { label: "Error taking stdout from external".to_string(), help: "Redirects need access to stdout of an external command" - .to_string(), span } - })?; + match child.as_mut().wait() { + Err(err) => Err(ShellError::ExternalCommand { + label: "External command exited with error".into(), + help: err.to_string(), + span + }), + Ok(x) => { + #[cfg(unix)] + { + use nu_ansi_term::{Color, Style}; + use std::ffi::CStr; + use std::os::unix::process::ExitStatusExt; - read_and_redirect_message(stdout, stdout_tx, ctrlc) - } else if redirect_combine { - let stdout = reader.ok_or_else(|| { - ShellError::ExternalCommand { label: "Error taking combined stdout and stderr from external".to_string(), help: "Combined redirects need access to reader pipe of an external command" - .to_string(), span } - })?; - read_and_redirect_message(stdout, stdout_tx, ctrlc) - } + if x.core_dumped() { + let cause = x.signal().and_then(|sig| unsafe { + // SAFETY: We should be the first to call `char * strsignal(int sig)` + let sigstr_ptr = libc::strsignal(sig); + if sigstr_ptr.is_null() { + return None; + } - match child.as_mut().wait() { - Err(err) => Err(ShellError::ExternalCommand { label: "External command exited with error".into(), help: err.to_string(), span }), - Ok(x) => { - #[cfg(unix)] - { - use nu_ansi_term::{Color, Style}; - use std::ffi::CStr; - use std::os::unix::process::ExitStatusExt; + // SAFETY: The pointer points to a valid non-null string + let sigstr = CStr::from_ptr(sigstr_ptr); + sigstr.to_str().map(String::from).ok() + }); - if x.core_dumped() { - let cause = x.signal().and_then(|sig| unsafe { - // SAFETY: We should be the first to call `char * strsignal(int sig)` - let sigstr_ptr = libc::strsignal(sig); - if sigstr_ptr.is_null() { - return None; - } - - // SAFETY: The pointer points to a valid non-null string - let sigstr = CStr::from_ptr(sigstr_ptr); - sigstr.to_str().map(String::from).ok() - }); - - let cause = cause.as_deref().unwrap_or("Something went wrong"); + let cause = cause.as_deref().unwrap_or("Something went wrong"); let style = Style::new().bold().on(Color::Red); eprintln!( @@ -531,56 +572,18 @@ impl ExternalCommand { } }).map_err(|e| e.into_spanned(head))?; - let (stderr_tx, stderr_rx) = mpsc::sync_channel(OUTPUT_BUFFERS_IN_FLIGHT); - if redirect_stderr { - thread::Builder::new() - .name("stderr redirector".to_string()) - .spawn(move || { - let stderr = stderr.ok_or_else(|| ShellError::ExternalCommand { - label: "Error taking stderr from external".to_string(), - help: "Redirects need access to stderr of an external command" - .to_string(), - span, - })?; - - read_and_redirect_message(stderr, stderr_tx, stderr_ctrlc); - Ok::<(), ShellError>(()) - }) - .map_err(|e| e.into_spanned(head))?; - } - - let stdout_receiver = ChannelReceiver::new(stdout_rx); - let stderr_receiver = ChannelReceiver::new(stderr_rx); let exit_code_receiver = ValueReceiver::new(exit_code_rx); Ok(PipelineData::ExternalStream { - stdout: if redirect_stdout || redirect_combine { - Some(RawStream::new( - Box::new(stdout_receiver), - output_ctrlc.clone(), - head, - None, - )) - } else { - None - }, - stderr: if redirect_stderr { - Some(RawStream::new( - Box::new(stderr_receiver), - output_ctrlc.clone(), - head, - None, - )) - } else { - None - }, + stdout, + stderr, exit_code: Some(ListStream::from_stream( Box::new(exit_code_receiver), - output_ctrlc, + ctrlc.clone(), )), span: head, metadata: None, - trim_end_newline: self.trim_end_newline, + trim_end_newline: true, }) } } @@ -621,20 +624,15 @@ impl ExternalCommand { // If the external is not the last command, its output will get piped // either as a string or binary - let reader = if self.redirect_combine { + let reader = if matches!(self.out, IoStream::Pipe) && matches!(self.err, IoStream::Pipe) { let (reader, writer) = os_pipe::pipe()?; let writer_clone = writer.try_clone()?; process.stdout(writer); process.stderr(writer_clone); Some(reader) } else { - if self.redirect_stdout { - process.stdout(Stdio::piped()); - } - - if self.redirect_stderr { - process.stderr(Stdio::piped()); - } + process.stdout(Stdio::try_from(&self.out)?); + process.stderr(Stdio::try_from(&self.err)?); None }; @@ -824,63 +822,27 @@ fn remove_quotes(input: String) -> String { } } -// read message from given `reader`, and send out through `sender`. -// -// `ctrlc` is used to control the process, if ctrl-c is pressed, the read and redirect -// process will be breaked. -fn read_and_redirect_message( - reader: R, - sender: SyncSender>, - ctrlc: Option>, -) where - R: Read, -{ - // read using the BufferReader. It will do so until there is an - // error or there are no more bytes to read - let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader); - while let Ok(bytes) = buf_read.fill_buf() { - if bytes.is_empty() { - break; - } +struct ByteLines(BufReader); - // The Cow generated from the function represents the conversion - // from bytes to String. If no replacements are required, then the - // borrowed value is a proper UTF-8 string. The Owned option represents - // a string where the values had to be replaced, thus marking it as bytes - let bytes = bytes.to_vec(); - let length = bytes.len(); - buf_read.consume(length); - - if nu_utils::ctrl_c::was_pressed(&ctrlc) { - break; - } - - match sender.send(bytes) { - Ok(_) => continue, - Err(_) => break, - } +impl ByteLines { + fn new(read: R) -> Self { + Self(BufReader::new(read)) } } -// Receiver used for the RawStream -// It implements iterator so it can be used as a RawStream -struct ChannelReceiver { - rx: mpsc::Receiver>, -} - -impl ChannelReceiver { - pub fn new(rx: mpsc::Receiver>) -> Self { - Self { rx } - } -} - -impl Iterator for ChannelReceiver { +impl Iterator for ByteLines { type Item = Result, ShellError>; fn next(&mut self) -> Option { - match self.rx.recv() { - Ok(v) => Some(Ok(v)), - Err(_) => None, + let mut buf = Vec::new(); + // `read_until` will never stop reading unless `\n` or EOF is encountered, + // so let's limit the number of bytes using `take` as the Rust docs suggest. + let capacity = self.0.capacity() as u64; + let mut reader = (&mut self.0).take(capacity); + match reader.read_until(b'\n', &mut buf) { + Ok(0) => None, + Ok(_) => Some(Ok(buf)), + Err(e) => Some(Err(e.into())), } } } diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index eabf9b33f0..2f45f841ce 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -9,10 +9,10 @@ use nu_engine::{env::get_config, env_to_string, CallExt}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Config, DataSource, Example, IntoPipelineData, ListStream, PipelineData, - PipelineMetadata, RawStream, Record, ShellError, Signature, Span, SyntaxShape, Type, Value, + record, Category, Config, DataSource, Example, IntoPipelineData, IoStream, ListStream, + PipelineData, PipelineMetadata, RawStream, Record, ShellError, Signature, Span, SyntaxShape, + TableMode, Type, Value, }; -use nu_protocol::{record, TableMode}; use nu_table::common::create_nu_table_config; use nu_table::{ CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, StringResult, TableOpts, @@ -370,7 +370,10 @@ fn handle_table_command( match input.data { PipelineData::ExternalStream { .. } => Ok(input.data), PipelineData::Value(Value::Binary { val, .. }, ..) => { - let stream_list = if input.call.redirect_stdout { + let stream_list = if matches!( + input.stack.stdout(), + IoStream::Pipe | IoStream::Capture | IoStream::Null + ) { vec![Ok(val)] } else { let hex = format!("{}\n", nu_pretty_hex::pretty_hex(&val)) diff --git a/crates/nu-command/tests/commands/complete.rs b/crates/nu-command/tests/commands/complete.rs index af47698e1e..c68845ff24 100644 --- a/crates/nu-command/tests/commands/complete.rs +++ b/crates/nu-command/tests/commands/complete.rs @@ -23,7 +23,72 @@ fn basic_exit_code() { #[test] fn error() { - let actual = nu!("do { not-found } | complete"); - + let actual = nu!("not-found | complete"); assert!(actual.err.contains("executable was not found")); } + +#[test] +#[cfg(not(windows))] +fn capture_error_with_too_much_stderr_not_hang_nushell() { + use nu_test_support::fs::Stub::FileWithContent; + use nu_test_support::playground::Playground; + Playground::setup("external with many stderr message", |dirs, sandbox| { + let bytes: usize = 81920; + let mut large_file_body = String::with_capacity(bytes); + for _ in 0..bytes { + large_file_body.push('a'); + } + sandbox.with_files(vec![FileWithContent("a_large_file.txt", &large_file_body)]); + + let actual = + nu!(cwd: dirs.test(), "sh -c 'cat a_large_file.txt 1>&2' | complete | get stderr"); + + assert_eq!(actual.out, large_file_body); + }) +} + +#[test] +#[cfg(not(windows))] +fn capture_error_with_too_much_stdout_not_hang_nushell() { + use nu_test_support::fs::Stub::FileWithContent; + use nu_test_support::playground::Playground; + Playground::setup("external with many stdout message", |dirs, sandbox| { + let bytes: usize = 81920; + let mut large_file_body = String::with_capacity(bytes); + for _ in 0..bytes { + large_file_body.push('a'); + } + sandbox.with_files(vec![FileWithContent("a_large_file.txt", &large_file_body)]); + + let actual = nu!(cwd: dirs.test(), "sh -c 'cat a_large_file.txt' | complete | get stdout"); + + assert_eq!(actual.out, large_file_body); + }) +} + +#[test] +#[cfg(not(windows))] +fn capture_error_with_both_stdout_stderr_messages_not_hang_nushell() { + use nu_test_support::fs::Stub::FileWithContent; + use nu_test_support::playground::Playground; + Playground::setup( + "external with many stdout and stderr messages", + |dirs, sandbox| { + let script_body = r#" + x=$(printf '=%.0s' $(seq 40960)) + echo $x + echo $x 1>&2 + "#; + let expect_body = "=".repeat(40960); + + sandbox.with_files(vec![FileWithContent("test.sh", script_body)]); + + // check for stdout + let actual = nu!(cwd: dirs.test(), "sh test.sh | complete | get stdout | str trim"); + assert_eq!(actual.out, expect_body); + // check for stderr + let actual = nu!(cwd: dirs.test(), "sh test.sh | complete | get stderr | str trim"); + assert_eq!(actual.out, expect_body); + }, + ) +} diff --git a/crates/nu-command/tests/commands/do_.rs b/crates/nu-command/tests/commands/do_.rs index e1b982b256..e7ffd3ae3d 100644 --- a/crates/nu-command/tests/commands/do_.rs +++ b/crates/nu-command/tests/commands/do_.rs @@ -1,6 +1,4 @@ use nu_test_support::nu; -#[cfg(not(windows))] -use nu_test_support::pipeline; #[test] fn capture_errors_works() { @@ -63,89 +61,6 @@ fn ignore_error_should_work_for_external_command() { assert_eq!(actual.out, "post"); } -#[test] -#[cfg(not(windows))] -fn capture_error_with_too_much_stderr_not_hang_nushell() { - use nu_test_support::fs::Stub::FileWithContent; - use nu_test_support::pipeline; - use nu_test_support::playground::Playground; - Playground::setup("external with many stderr message", |dirs, sandbox| { - let bytes: usize = 81920; - let mut large_file_body = String::with_capacity(bytes); - for _ in 0..bytes { - large_file_body.push('a'); - } - sandbox.with_files(vec![FileWithContent("a_large_file.txt", &large_file_body)]); - - let actual = nu!( - cwd: dirs.test(), pipeline( - r#" - do -c {sh -c "cat a_large_file.txt 1>&2"} | complete | get stderr - "#, - )); - - assert_eq!(actual.out, large_file_body); - }) -} - -#[test] -#[cfg(not(windows))] -fn capture_error_with_too_much_stdout_not_hang_nushell() { - use nu_test_support::fs::Stub::FileWithContent; - use nu_test_support::pipeline; - use nu_test_support::playground::Playground; - Playground::setup("external with many stdout message", |dirs, sandbox| { - let bytes: usize = 81920; - let mut large_file_body = String::with_capacity(bytes); - for _ in 0..bytes { - large_file_body.push('a'); - } - sandbox.with_files(vec![FileWithContent("a_large_file.txt", &large_file_body)]); - - let actual = nu!( - cwd: dirs.test(), pipeline( - r#" - do -c {sh -c "cat a_large_file.txt"} | complete | get stdout - "#, - )); - - assert_eq!(actual.out, large_file_body); - }) -} - -#[test] -#[cfg(not(windows))] -fn capture_error_with_both_stdout_stderr_messages_not_hang_nushell() { - use nu_test_support::fs::Stub::FileWithContent; - use nu_test_support::playground::Playground; - Playground::setup( - "external with many stdout and stderr messages", - |dirs, sandbox| { - let script_body = r#" - x=$(printf '=%.0s' $(seq 40960)) - echo $x - echo $x 1>&2 - "#; - let expect_body = "=".repeat(40960); - - sandbox.with_files(vec![FileWithContent("test.sh", script_body)]); - - // check for stdout - let actual = nu!( - cwd: dirs.test(), pipeline( - "do -c {sh test.sh} | complete | get stdout | str trim", - )); - assert_eq!(actual.out, expect_body); - // check for stderr - let actual = nu!( - cwd: dirs.test(), pipeline( - "do -c {sh test.sh} | complete | get stderr | str trim", - )); - assert_eq!(actual.out, expect_body); - }, - ) -} - #[test] fn ignore_error_works_with_list_stream() { let actual = nu!(r#"do -i { ["a", null, "b"] | ansi strip }"#); diff --git a/crates/nu-command/tests/commands/let_.rs b/crates/nu-command/tests/commands/let_.rs index e780fe022c..94fb19315d 100644 --- a/crates/nu-command/tests/commands/let_.rs +++ b/crates/nu-command/tests/commands/let_.rs @@ -65,9 +65,7 @@ fn let_err_pipeline_redirects_externals() { let actual = nu!( r#"let x = with-env [FOO "foo"] {nu --testbin echo_env_stderr FOO e>| str length}; $x"# ); - - // have an extra \n, so length is 4. - assert_eq!(actual.out, "4"); + assert_eq!(actual.out, "3"); } #[test] @@ -75,9 +73,7 @@ fn let_outerr_pipeline_redirects_externals() { let actual = nu!( r#"let x = with-env [FOO "foo"] {nu --testbin echo_env_stderr FOO o+e>| str length}; $x"# ); - - // have an extra \n, so length is 4. - assert_eq!(actual.out, "4"); + assert_eq!(actual.out, "3"); } #[ignore] diff --git a/crates/nu-command/tests/commands/redirection.rs b/crates/nu-command/tests/commands/redirection.rs index f26180eff4..7f12f982ee 100644 --- a/crates/nu-command/tests/commands/redirection.rs +++ b/crates/nu-command/tests/commands/redirection.rs @@ -164,7 +164,7 @@ fn redirection_keep_exit_codes() { Playground::setup("redirection preserves exit code", |dirs, _| { let out = nu!( cwd: dirs.test(), - "do -i { nu --testbin fail e> a.txt } | complete | get exit_code" + "nu --testbin fail e> a.txt | complete | get exit_code" ); // needs to use contains "1", because it complete will output `Some(RawStream)`. assert!(out.out.contains('1')); @@ -358,7 +358,7 @@ fn redirection_with_out_pipe() { r#"$env.BAZ = "message"; nu --testbin echo_env_mixed out-err BAZ BAZ err> tmp_file | str length"#, ); - assert_eq!(actual.out, "8"); + assert_eq!(actual.out, "7"); // check for stderr redirection file. let expected_out_file = dirs.test().join("tmp_file"); let actual_len = file_contents(expected_out_file).len(); @@ -376,7 +376,7 @@ fn redirection_with_err_pipe() { r#"$env.BAZ = "message"; nu --testbin echo_env_mixed out-err BAZ BAZ out> tmp_file e>| str length"#, ); - assert_eq!(actual.out, "8"); + assert_eq!(actual.out, "7"); // check for stdout redirection file. let expected_out_file = dirs.test().join("tmp_file"); let actual_len = file_contents(expected_out_file).len(); @@ -392,7 +392,7 @@ fn no_redirection_with_outerr_pipe() { cwd: dirs.test(), &format!("echo 3 {redirect_type} a.txt o+e>| str length") ); - assert!(actual.err.contains("not allowed to use with redirection")); + assert!(actual.err.contains("Multiple redirections provided")); assert!( !dirs.test().join("a.txt").exists(), "No file should be created on error" @@ -404,7 +404,7 @@ fn no_redirection_with_outerr_pipe() { cwd: dirs.test(), "echo 3 o> a.txt e> b.txt o+e>| str length" ); - assert!(actual.err.contains("not allowed to use with redirection")); + assert!(actual.err.contains("Multiple redirections provided")); assert!( !dirs.test().join("a.txt").exists(), "No file should be created on error" @@ -423,7 +423,7 @@ fn no_duplicate_redirection() { cwd: dirs.test(), "echo 3 o> a.txt o> a.txt" ); - assert!(actual.err.contains("Redirection can be set only once")); + assert!(actual.err.contains("Multiple redirections provided")); assert!( !dirs.test().join("a.txt").exists(), "No file should be created on error" @@ -432,7 +432,7 @@ fn no_duplicate_redirection() { cwd: dirs.test(), "echo 3 e> a.txt e> a.txt" ); - assert!(actual.err.contains("Redirection can be set only once")); + assert!(actual.err.contains("Multiple redirections provided")); assert!( !dirs.test().join("a.txt").exists(), "No file should be created on error" diff --git a/crates/nu-command/tests/commands/run_external.rs b/crates/nu-command/tests/commands/run_external.rs index 69ebd0473d..9c2f0968c7 100644 --- a/crates/nu-command/tests/commands/run_external.rs +++ b/crates/nu-command/tests/commands/run_external.rs @@ -325,7 +325,7 @@ fn redirect_combine() { let actual = nu!( cwd: dirs.test(), pipeline( r#" - run-external --redirect-combine sh ...[-c 'echo Foo; echo >&2 Bar'] + run-external sh ...[-c 'echo Foo; echo >&2 Bar'] o+e>| print "# )); diff --git a/crates/nu-engine/src/call_ext.rs b/crates/nu-engine/src/call_ext.rs index f705f19d2e..3b320d8936 100644 --- a/crates/nu-engine/src/call_ext.rs +++ b/crates/nu-engine/src/call_ext.rs @@ -70,6 +70,7 @@ impl CallExt for Call { if flag_name == name.0.item { return if let Some(expr) = &name.2 { // Check --flag=false + let stack = &mut stack.use_call_arg_stdio(); let result = eval_expression::(engine_state, stack, expr)?; match result { Value::Bool { val, .. } => Ok(val), @@ -96,6 +97,7 @@ impl CallExt for Call { name: &str, ) -> Result, ShellError> { if let Some(expr) = self.get_flag_expr(name) { + let stack = &mut stack.use_call_arg_stdio(); let result = eval_expression::(engine_state, stack, expr)?; FromValue::from_value(result).map(Some) } else { @@ -109,6 +111,7 @@ impl CallExt for Call { stack: &mut Stack, starting_pos: usize, ) -> Result, ShellError> { + let stack = &mut stack.use_call_arg_stdio(); let mut output = vec![]; for result in self.rest_iter_flattened(starting_pos, |expr| { @@ -127,6 +130,7 @@ impl CallExt for Call { pos: usize, ) -> Result, ShellError> { if let Some(expr) = self.positional_nth(pos) { + let stack = &mut stack.use_call_arg_stdio(); let result = eval_expression::(engine_state, stack, expr)?; FromValue::from_value(result).map(Some) } else { @@ -154,6 +158,7 @@ impl CallExt for Call { pos: usize, ) -> Result { if let Some(expr) = self.positional_nth(pos) { + let stack = &mut stack.use_call_arg_stdio(); let result = eval_expression::(engine_state, stack, expr)?; FromValue::from_value(result) } else if self.positional_len() == 0 { @@ -173,6 +178,7 @@ impl CallExt for Call { name: &str, ) -> Result { if let Some(expr) = self.get_parser_info(name) { + let stack = &mut stack.use_call_arg_stdio(); let result = eval_expression::(engine_state, stack, expr)?; FromValue::from_value(result) } else if self.parser_info.is_empty() { diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 159810ec2b..b3dfbaefba 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -23,6 +23,9 @@ pub fn get_full_help( no_color: !config.use_ansi_coloring, brief: false, }; + + let stack = &mut stack.start_capture(); + get_documentation( sig, examples, @@ -235,16 +238,14 @@ fn get_documentation( )); } - let mut caller_stack = Stack::new(); + let caller_stack = &mut Stack::new().capture(); if let Ok(result) = eval_call::( engine_state, - &mut caller_stack, + caller_stack, &Call { decl_id, head: span, arguments: vec![], - redirect_stdout: true, - redirect_stderr: true, parser_info: HashMap::new(), }, PipelineData::Value(Value::list(vals, span), None), @@ -340,7 +341,7 @@ fn get_ansi_color_for_component_or_default( default: &str, ) -> String { if let Some(color) = &engine_state.get_config().color_config.get(theme_component) { - let mut caller_stack = Stack::new(); + let caller_stack = &mut Stack::new().capture(); let span = Span::unknown(); let argument_opt = get_argument_for_color_value(engine_state, color, span); @@ -350,13 +351,11 @@ fn get_ansi_color_for_component_or_default( if let Some(decl_id) = engine_state.find_decl(b"ansi", &[]) { if let Ok(result) = eval_call::( engine_state, - &mut caller_stack, + caller_stack, &Call { decl_id, head: span, arguments: vec![argument], - redirect_stdout: true, - redirect_stderr: true, parser_info: HashMap::new(), }, PipelineData::Empty, diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index 592c2615f5..9e39ea2786 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -379,7 +379,7 @@ fn get_converted_value( let block = engine_state.get_block(val.block_id); if let Some(var) = block.signature.get_positional(0) { - let mut stack = stack.gather_captures(engine_state, &block.captures); + let mut stack = stack.captures_to_stack(val.captures.clone()); if let Some(var_id) = &var.var_id { stack.add_var(*var_id, orig_val.clone()); } @@ -391,8 +391,6 @@ fn get_converted_value( &mut stack, block, PipelineData::new_with_metadata(None, val_span), - true, - true, ); match result { diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 3fecf54884..a1fea67bbf 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,20 +1,19 @@ -use crate::{current_dir_str, get_config, get_full_help}; +use std::{borrow::Cow, fs::OpenOptions, path::PathBuf}; + +use crate::{current_dir, current_dir_str, get_config, get_full_help}; use nu_path::expand_path_with; -use nu_protocol::debugger::{DebugContext, WithoutDebug}; +use nu_protocol::debugger::DebugContext; use nu_protocol::{ ast::{ - Argument, Assignment, Block, Call, Expr, Expression, ExternalArgument, PathMember, - PipelineElement, Redirection, + Assignment, Block, Call, Expr, Expression, ExternalArgument, PathMember, PipelineElement, + PipelineRedirection, RedirectionSource, RedirectionTarget, }, - engine::{Closure, EngineState, Stack}, + engine::{Closure, EngineState, Redirection, Stack}, eval_base::Eval, - Config, DeclId, IntoPipelineData, IntoSpanned, PipelineData, RawStream, ShellError, Span, - Spanned, Type, Value, VarId, ENV_VARIABLE_ID, + Config, FromValue, IntoPipelineData, IoStream, PipelineData, ShellError, Span, Spanned, Type, + Value, VarId, ENV_VARIABLE_ID, }; -use std::thread::{self, JoinHandle}; -use std::{borrow::Cow, collections::HashMap}; - pub fn eval_call( engine_state: &EngineState, caller_stack: &mut Stack, @@ -174,14 +173,8 @@ pub fn eval_call( } } - let result = eval_block_with_early_return::( - engine_state, - &mut callee_stack, - block, - input, - call.redirect_stdout, - call.redirect_stderr, - ); + let result = + eval_block_with_early_return::(engine_state, &mut callee_stack, block, input); if block.redirect_env { redirect_env(engine_state, caller_stack, &callee_stack); @@ -215,11 +208,6 @@ pub fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee } } -enum RedirectTarget { - Piped(bool, bool), - CombinedPipe, -} - #[allow(clippy::too_many_arguments)] fn eval_external( engine_state: &EngineState, @@ -227,8 +215,6 @@ fn eval_external( head: &Expression, args: &[ExternalArgument], input: PipelineData, - redirect_target: RedirectTarget, - is_subexpression: bool, ) -> Result { let decl_id = engine_state .find_decl("run-external".as_bytes(), &[]) @@ -247,51 +233,6 @@ fn eval_external( } } - match redirect_target { - RedirectTarget::Piped(redirect_stdout, redirect_stderr) => { - if redirect_stdout { - call.add_named(( - Spanned { - item: "redirect-stdout".into(), - span: head.span, - }, - None, - None, - )) - } - - if redirect_stderr { - call.add_named(( - Spanned { - item: "redirect-stderr".into(), - span: head.span, - }, - None, - None, - )) - } - } - RedirectTarget::CombinedPipe => call.add_named(( - Spanned { - item: "redirect-combine".into(), - span: head.span, - }, - None, - None, - )), - } - - if is_subexpression { - call.add_named(( - Spanned { - item: "trim-end-newline".into(), - span: head.span, - }, - None, - None, - )) - } - command.run(engine_state, stack, &call, input) } @@ -300,6 +241,7 @@ pub fn eval_expression( stack: &mut Stack, expr: &Expression, ) -> Result { + let stack = &mut stack.start_capture(); ::eval::(engine_state, stack, expr) } @@ -314,53 +256,22 @@ pub fn eval_expression_with_input( stack: &mut Stack, expr: &Expression, mut input: PipelineData, - redirect_stdout: bool, - redirect_stderr: bool, ) -> Result<(PipelineData, bool), ShellError> { - match expr { - Expression { - expr: Expr::Call(call), - .. - } => { - if !redirect_stdout || redirect_stderr { - // we're doing something different than the defaults - let mut call = call.clone(); - call.redirect_stdout = redirect_stdout; - call.redirect_stderr = redirect_stderr; - input = eval_call::(engine_state, stack, &call, input)?; - } else { - input = eval_call::(engine_state, stack, call, input)?; - } + match &expr.expr { + Expr::Call(call) => { + input = eval_call::(engine_state, stack, call, input)?; } - Expression { - expr: Expr::ExternalCall(head, args, is_subexpression), - .. - } => { - input = eval_external( - engine_state, - stack, - head, - args, - input, - RedirectTarget::Piped(redirect_stdout, redirect_stderr), - *is_subexpression, - )?; + Expr::ExternalCall(head, args) => { + input = eval_external(engine_state, stack, head, args, input)?; } - Expression { - expr: Expr::Subexpression(block_id), - .. - } => { + Expr::Subexpression(block_id) => { let block = engine_state.get_block(*block_id); - // FIXME: protect this collect with ctrl-c input = eval_subexpression::(engine_state, stack, block, input)?; } - elem @ Expression { - expr: Expr::FullCellPath(full_cell_path), - .. - } => match &full_cell_path.head { + Expr::FullCellPath(full_cell_path) => match &full_cell_path.head { Expression { expr: Expr::Subexpression(block_id), span, @@ -368,37 +279,36 @@ pub fn eval_expression_with_input( } => { let block = engine_state.get_block(*block_id); - // FIXME: protect this collect with ctrl-c - input = eval_subexpression::(engine_state, stack, block, input)?; - let value = input.into_value(*span); - input = value - .follow_cell_path(&full_cell_path.tail, false)? - .into_pipeline_data() + if !full_cell_path.tail.is_empty() { + let stack = &mut stack.start_capture(); + // FIXME: protect this collect with ctrl-c + input = eval_subexpression::(engine_state, stack, block, input)? + .into_value(*span) + .follow_cell_path(&full_cell_path.tail, false)? + .into_pipeline_data() + } else { + input = eval_subexpression::(engine_state, stack, block, input)?; + } } _ => { - input = eval_expression::(engine_state, stack, elem)?.into_pipeline_data(); + input = eval_expression::(engine_state, stack, expr)?.into_pipeline_data(); } }, - elem => { - input = eval_expression::(engine_state, stack, elem)?.into_pipeline_data(); + _ => { + input = eval_expression::(engine_state, stack, expr)?.into_pipeline_data(); } }; - // Given input is PipelineData::ExternalStream - // `might_consume_external_result` will consume `stderr` stream if `stdout` is empty. - // it's not intended if user want to redirect stderr message. - // - // e.g: - // 1. cargo check e>| less - // 2. cargo check e> result.txt - // - // In these two cases, stdout will be empty, but nushell shouldn't consume the `stderr` - // stream it needs be passed to next command. - if !redirect_stderr { - Ok(might_consume_external_result(input)) - } else { + // If input is PipelineData::ExternalStream, + // then `might_consume_external_result` will consume `stderr` if `stdout` is `None`. + // This should not happen if the user wants to capture stderr. + if !matches!(stack.stdout(), IoStream::Pipe | IoStream::Capture) + && matches!(stack.stderr(), IoStream::Capture) + { Ok((input, false)) + } else { + Ok(might_consume_external_result(input)) } } @@ -407,459 +317,161 @@ fn might_consume_external_result(input: PipelineData) -> (PipelineData, bool) { input.is_external_failed() } -#[allow(clippy::too_many_arguments)] +fn eval_redirection( + engine_state: &EngineState, + stack: &mut Stack, + target: &RedirectionTarget, + next_out: Option, +) -> Result { + match target { + RedirectionTarget::File { expr, append, .. } => { + let cwd = current_dir(engine_state, stack)?; + let value = eval_expression::(engine_state, stack, expr)?; + let path = Spanned::::from_value(value)?.item; + let path = expand_path_with(path, cwd); + + let mut options = OpenOptions::new(); + if *append { + options.append(true); + } else { + options.write(true).truncate(true); + } + Ok(Redirection::file(options.create(true).open(path)?)) + } + RedirectionTarget::Pipe { .. } => Ok(Redirection::Pipe(next_out.unwrap_or(IoStream::Pipe))), + } +} + +fn eval_element_redirection( + engine_state: &EngineState, + stack: &mut Stack, + element_redirection: Option<&PipelineRedirection>, + pipe_redirection: (Option, Option), +) -> Result<(Option, Option), ShellError> { + let (next_out, next_err) = pipe_redirection; + + if let Some(redirection) = element_redirection { + match redirection { + PipelineRedirection::Single { + source: RedirectionSource::Stdout, + target, + } => { + let stdout = eval_redirection::(engine_state, stack, target, next_out)?; + Ok((Some(stdout), next_err.map(Redirection::Pipe))) + } + PipelineRedirection::Single { + source: RedirectionSource::Stderr, + target, + } => { + let stderr = eval_redirection::(engine_state, stack, target, None)?; + if matches!(stderr, Redirection::Pipe(IoStream::Pipe)) { + // e>| redirection, don't override current stack `stdout` + Ok(( + None, + Some(next_out.map(Redirection::Pipe).unwrap_or(stderr)), + )) + } else { + Ok((next_out.map(Redirection::Pipe), Some(stderr))) + } + } + PipelineRedirection::Single { + source: RedirectionSource::StdoutAndStderr, + target, + } => { + let stream = eval_redirection::(engine_state, stack, target, next_out)?; + Ok((Some(stream.clone()), Some(stream))) + } + PipelineRedirection::Separate { out, err } => { + let stdout = eval_redirection::(engine_state, stack, out, None)?; // `out` cannot be `RedirectionTarget::Pipe` + let stderr = eval_redirection::(engine_state, stack, err, next_out)?; + Ok((Some(stdout), Some(stderr))) + } + } + } else { + Ok(( + next_out.map(Redirection::Pipe), + next_err.map(Redirection::Pipe), + )) + } +} + +fn eval_element_with_input_inner( + engine_state: &EngineState, + stack: &mut Stack, + element: &PipelineElement, + input: PipelineData, +) -> Result<(PipelineData, bool), ShellError> { + let (data, ok) = eval_expression_with_input::(engine_state, stack, &element.expr, input)?; + + if !matches!(data, PipelineData::ExternalStream { .. }) { + if let Some(redirection) = element.redirection.as_ref() { + match redirection { + &PipelineRedirection::Single { + source: RedirectionSource::Stderr, + target: RedirectionTarget::Pipe { span }, + } + | &PipelineRedirection::Separate { + err: RedirectionTarget::Pipe { span }, + .. + } => { + return Err(ShellError::GenericError { + error: "`e>|` only works with external streams".into(), + msg: "`e>|` only works on external streams".into(), + span: Some(span), + help: None, + inner: vec![], + }); + } + &PipelineRedirection::Single { + source: RedirectionSource::StdoutAndStderr, + target: RedirectionTarget::Pipe { span }, + } => { + return Err(ShellError::GenericError { + error: "`o+e>|` only works with external streams".into(), + msg: "`o+e>|` only works on external streams".into(), + span: Some(span), + help: None, + inner: vec![], + }); + } + _ => {} + } + } + } + + let data = match (data, stack.pipe_stdout()) { + ( + data @ (PipelineData::Value(..) | PipelineData::ListStream(..)), + Some(IoStream::File(_)), + ) => data.write_to_io_streams(engine_state, stack)?, + (data, _) => data, + }; + + Ok((data, ok)) +} + fn eval_element_with_input( engine_state: &EngineState, stack: &mut Stack, element: &PipelineElement, - mut input: PipelineData, - redirect_stdout: bool, - redirect_stderr: bool, - redirect_combine: bool, - stderr_writer_jobs: &mut Vec, + input: PipelineData, ) -> Result<(PipelineData, bool), ShellError> { D::enter_element(engine_state, element); - let result = match element { - PipelineElement::Expression(pipe_span, expr) - | PipelineElement::OutErrPipedExpression(pipe_span, expr) => { - if matches!(element, PipelineElement::OutErrPipedExpression(..)) - && !matches!(input, PipelineData::ExternalStream { .. }) - { - return Err(ShellError::GenericError { - error: "`o+e>|` only works with external streams".into(), - msg: "`o+e>|` only works on external streams".into(), - span: *pipe_span, - help: None, - inner: vec![], - }); - } - match expr { - Expression { - expr: Expr::ExternalCall(head, args, is_subexpression), - .. - } if redirect_combine => { - let result = eval_external( - engine_state, - stack, - head, - args, - input, - RedirectTarget::CombinedPipe, - *is_subexpression, - )?; - Ok(might_consume_external_result(result)) - } - _ => eval_expression_with_input::( - engine_state, - stack, - expr, - input, - redirect_stdout, - redirect_stderr, - ), - } - } - PipelineElement::ErrPipedExpression(pipe_span, expr) => { - let input = match input { - PipelineData::ExternalStream { - stdout, - stderr, - exit_code, - span, - metadata, - trim_end_newline, - } => PipelineData::ExternalStream { - stdout: stderr, // swap stderr and stdout to get stderr piped feature. - stderr: stdout, - exit_code, - span, - metadata, - trim_end_newline, - }, - _ => { - return Err(ShellError::GenericError { - error: "`e>|` only works with external streams".into(), - msg: "`e>|` only works on external streams".into(), - span: *pipe_span, - help: None, - inner: vec![], - }) - } - }; - eval_expression_with_input::( - engine_state, - stack, - expr, - input, - redirect_stdout, - redirect_stderr, - ) - } - PipelineElement::Redirection(span, redirection, expr, is_append_mode) => { - match &expr.expr { - Expr::String(_) - | Expr::FullCellPath(_) - | Expr::StringInterpolation(_) - | Expr::Filepath(_, _) => { - let exit_code = match &mut input { - PipelineData::ExternalStream { exit_code, .. } => exit_code.take(), - _ => None, - }; - - let (input, result_out_stream, result_is_out) = - adjust_stream_for_input_and_output(input, redirection); - - if let Some(save_command) = engine_state.find_decl(b"save", &[]) { - let save_call = gen_save_call( - save_command, - (*span, expr.clone(), *is_append_mode), - None, - ); - match result_out_stream { - None => { - eval_call::(engine_state, stack, &save_call, input).map(|_| { - // save is internal command, normally it exists with non-ExternalStream - // but here in redirection context, we make it returns ExternalStream - // So nu handles exit_code correctly - // - // Also, we don't want to run remaining commands if this command exits with non-zero - // exit code, so we need to consume and check exit_code too - might_consume_external_result(PipelineData::ExternalStream { - stdout: None, - stderr: None, - exit_code, - span: *span, - metadata: None, - trim_end_newline: false, - }) - }) - } - Some(result_out_stream) => { - // delegate to a different thread - // so nushell won't hang if external command generates both too much - // stderr and stdout message - let stderr_stack = stack.clone(); - let engine_state_clone = engine_state.clone(); - stderr_writer_jobs.push(DataSaveJob::spawn( - engine_state_clone, - stderr_stack, - save_call, - input, - )?); - let (result_out_stream, result_err_stream) = if result_is_out { - (result_out_stream, None) - } else { - // we need `stdout` to be an empty RawStream - // so nushell knows this result is not the last part of a command. - ( - Some(RawStream::new( - Box::new(std::iter::empty()), - None, - *span, - Some(0), - )), - result_out_stream, - ) - }; - Ok(might_consume_external_result( - PipelineData::ExternalStream { - stdout: result_out_stream, - stderr: result_err_stream, - exit_code, - span: *span, - metadata: None, - trim_end_newline: false, - }, - )) - } - } - } else { - Err(ShellError::CommandNotFound { span: *span }) - } - } - _ => Err(ShellError::CommandNotFound { span: *span }), - } - } - PipelineElement::SeparateRedirection { - out: (out_span, out_expr, out_append_mode), - err: (err_span, err_expr, err_append_mode), - } => match (&out_expr.expr, &err_expr.expr) { - ( - Expr::String(_) - | Expr::FullCellPath(_) - | Expr::StringInterpolation(_) - | Expr::Filepath(_, _), - Expr::String(_) - | Expr::FullCellPath(_) - | Expr::StringInterpolation(_) - | Expr::Filepath(_, _), - ) => { - if let Some(save_command) = engine_state.find_decl(b"save", &[]) { - let exit_code = match &mut input { - PipelineData::ExternalStream { exit_code, .. } => exit_code.take(), - _ => None, - }; - let save_call = gen_save_call( - save_command, - (*out_span, out_expr.clone(), *out_append_mode), - Some((*err_span, err_expr.clone(), *err_append_mode)), - ); - - eval_call::(engine_state, stack, &save_call, input).map(|_| { - // save is internal command, normally it exists with non-ExternalStream - // but here in redirection context, we make it returns ExternalStream - // So nu handles exit_code correctly - might_consume_external_result(PipelineData::ExternalStream { - stdout: None, - stderr: None, - exit_code, - span: *out_span, - metadata: None, - trim_end_newline: false, - }) - }) - } else { - Err(ShellError::CommandNotFound { span: *out_span }) - } - } - (_out_other, err_other) => { - if let Expr::String(_) = err_other { - Err(ShellError::CommandNotFound { span: *out_span }) - } else { - Err(ShellError::CommandNotFound { span: *err_span }) - } - } - }, - PipelineElement::SameTargetRedirection { - cmd: (cmd_span, cmd_exp), - redirection: (redirect_span, redirect_exp, is_append_mode), - } => { - // general idea: eval cmd and call save command to redirect stdout to result. - input = match &cmd_exp.expr { - Expr::ExternalCall(head, args, is_subexpression) => { - // if cmd's expression is ExternalStream, then invoke run-external with - // special --redirect-combine flag. - eval_external( - engine_state, - stack, - head, - args, - input, - RedirectTarget::CombinedPipe, - *is_subexpression, - )? - } - _ => { - // we need to redirect output, so the result can be saved and pass to `save` command. - eval_element_with_input::( - engine_state, - stack, - &PipelineElement::Expression(*cmd_span, cmd_exp.clone()), - input, - true, - redirect_stderr, - redirect_combine, - stderr_writer_jobs, - ) - .map(|x| x.0)? - } - }; - eval_element_with_input::( - engine_state, - stack, - &PipelineElement::Redirection( - *redirect_span, - Redirection::Stdout, - redirect_exp.clone(), - *is_append_mode, - ), - input, - redirect_stdout, - redirect_stderr, - redirect_combine, - stderr_writer_jobs, - ) - } - PipelineElement::And(_, expr) => eval_expression_with_input::( - engine_state, - stack, - expr, - input, - redirect_stdout, - redirect_stderr, - ), - PipelineElement::Or(_, expr) => eval_expression_with_input::( - engine_state, - stack, - expr, - input, - redirect_stdout, - redirect_stderr, - ), - }; + let result = eval_element_with_input_inner::(engine_state, stack, element, input); D::leave_element(engine_state, element, &result); result } -// In redirection context, if nushell gets an ExternalStream -// it might want to take a stream from `input`(if `input` is `PipelineData::ExternalStream`) -// so this stream can be handled by next command. -// -// -// 1. get a stderr redirection, we need to take `stdout` out of `input`. -// e.g: nu --testbin echo_env FOO e> /dev/null | str length -// 2. get a stdout redirection, we need to take `stderr` out of `input`. -// e.g: nu --testbin echo_env FOO o> /dev/null e>| str length -// -// Returns 3 values: -// 1. adjusted pipeline data -// 2. a result stream which is taken from `input`, it can be handled in next command -// 3. a boolean value indicates if result stream should be a stdout stream. -fn adjust_stream_for_input_and_output( - input: PipelineData, - redirection: &Redirection, -) -> (PipelineData, Option>, bool) { - match (redirection, input) { - ( - Redirection::Stderr, - PipelineData::ExternalStream { - stdout, - stderr, - exit_code, - span, - metadata, - trim_end_newline, - }, - ) => ( - PipelineData::ExternalStream { - stdout: stderr, - stderr: None, - exit_code, - span, - metadata, - trim_end_newline, - }, - Some(stdout), - true, - ), - ( - Redirection::Stdout, - PipelineData::ExternalStream { - stdout, - stderr, - exit_code, - span, - metadata, - trim_end_newline, - }, - ) => ( - PipelineData::ExternalStream { - stdout, - stderr: None, - exit_code, - span, - metadata, - trim_end_newline, - }, - Some(stderr), - false, - ), - (_, input) => (input, None, true), - } -} - -fn is_redirect_stderr_required(elements: &[PipelineElement], idx: usize) -> bool { - let elements_length = elements.len(); - if idx < elements_length - 1 { - let next_element = &elements[idx + 1]; - match next_element { - PipelineElement::Redirection(_, Redirection::Stderr, _, _) - | PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _, _) - | PipelineElement::SeparateRedirection { .. } - | PipelineElement::ErrPipedExpression(..) - | PipelineElement::OutErrPipedExpression(..) => return true, - PipelineElement::Redirection(_, Redirection::Stdout, _, _) => { - // a stderr redirection, but we still need to check for the next 2nd - // element, to handle for the following case: - // cat a.txt out> /dev/null e>| lines - // - // we only need to check the next 2nd element because we already make sure - // that we don't have duplicate err> like this: - // cat a.txt out> /dev/null err> /tmp/a - if idx < elements_length - 2 { - let next_2nd_element = &elements[idx + 2]; - if matches!(next_2nd_element, PipelineElement::ErrPipedExpression(..)) { - return true; - } - } - } - _ => {} - } - } - false -} - -fn is_redirect_stdout_required(elements: &[PipelineElement], idx: usize) -> bool { - let elements_length = elements.len(); - if idx < elements_length - 1 { - let next_element = &elements[idx + 1]; - match next_element { - // is next element a stdout relative redirection? - PipelineElement::Redirection(_, Redirection::Stdout, _, _) - | PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _, _) - | PipelineElement::SeparateRedirection { .. } - | PipelineElement::Expression(..) - | PipelineElement::OutErrPipedExpression(..) => return true, - - PipelineElement::Redirection(_, Redirection::Stderr, _, _) => { - // a stderr redirection, but we still need to check for the next 2nd - // element, to handle for the following case: - // cat a.txt err> /dev/null | lines - // - // we only need to check the next 2nd element because we already make sure - // that we don't have duplicate err> like this: - // cat a.txt err> /dev/null err> /tmp/a - if idx < elements_length - 2 { - let next_2nd_element = &elements[idx + 2]; - if matches!(next_2nd_element, PipelineElement::Expression(..)) { - return true; - } - } - } - _ => {} - } - } - false -} - -fn is_redirect_combine_required(elements: &[PipelineElement], idx: usize) -> bool { - let elements_length = elements.len(); - idx < elements_length - 1 - && matches!( - &elements[idx + 1], - PipelineElement::OutErrPipedExpression(..) - ) -} - pub fn eval_block_with_early_return( engine_state: &EngineState, stack: &mut Stack, block: &Block, input: PipelineData, - redirect_stdout: bool, - redirect_stderr: bool, ) -> Result { - match eval_block::( - engine_state, - stack, - block, - input, - redirect_stdout, - redirect_stderr, - ) { + match eval_block::(engine_state, stack, block, input) { Err(ShellError::Return { span: _, value }) => Ok(PipelineData::Value(*value, None)), x => x, } @@ -870,105 +482,85 @@ pub fn eval_block( stack: &mut Stack, block: &Block, mut input: PipelineData, - redirect_stdout: bool, - redirect_stderr: bool, ) -> Result { D::enter_block(engine_state, block); let num_pipelines = block.len(); for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() { - let mut stderr_writer_jobs = vec![]; - let elements = &pipeline.elements; - let elements_length = pipeline.elements.len(); - for (idx, element) in elements.iter().enumerate() { - let mut redirect_stdout = redirect_stdout; - let mut redirect_stderr = redirect_stderr; - if !redirect_stderr && is_redirect_stderr_required(elements, idx) { - redirect_stderr = true; - } + let last_pipeline = pipeline_idx >= num_pipelines - 1; - if !redirect_stdout { - if is_redirect_stdout_required(elements, idx) { - redirect_stdout = true; - } - } else if idx < elements_length - 1 - && matches!(elements[idx + 1], PipelineElement::ErrPipedExpression(..)) - { - redirect_stdout = false; - } + let Some((last, elements)) = pipeline.elements.split_last() else { + debug_assert!(false, "pipelines should have at least one element"); + continue; + }; - let redirect_combine = is_redirect_combine_required(elements, idx); - - // if eval internal command failed, it can just make early return with `Err(ShellError)`. - let eval_result = eval_element_with_input::( + for (i, element) in elements.iter().enumerate() { + let next = elements.get(i + 1).unwrap_or(last); + let (stdout, stderr) = eval_element_redirection::( engine_state, stack, - element, - input, - redirect_stdout, - redirect_stderr, - redirect_combine, - &mut stderr_writer_jobs, - ); - - match (eval_result, redirect_stderr) { - (Err(error), true) => { - input = PipelineData::Value( - Value::error( - error, - Span::unknown(), // FIXME: where does this span come from? - ), - None, - ) - } - (output, _) => { - let output = output?; - input = output.0; - // external command may runs to failed - // make early return so remaining commands will not be executed. - // don't return `Err(ShellError)`, so nushell wouldn't show extra error message. - if output.1 { - return Ok(input); - } - } + element.redirection.as_ref(), + next.stdio_redirect(engine_state), + )?; + let stack = &mut stack + .push_redirection(stdout.or(Some(Redirection::Pipe(IoStream::Pipe))), stderr); + let (output, failed) = + eval_element_with_input::(engine_state, stack, element, input)?; + if failed { + // External command failed. + // Don't return `Err(ShellError)`, so nushell won't show an extra error message. + return Ok(output); } + input = output; } - // `eval_element_with_input` may creates some threads - // to write stderr message to a file, here we need to wait and make sure that it's - // finished. - for h in stderr_writer_jobs { - let _ = h.join(); - } - if pipeline_idx < (num_pipelines) - 1 { - match input { - PipelineData::Value(Value::Nothing { .. }, ..) => {} - PipelineData::ExternalStream { - ref mut exit_code, .. - } => { - let exit_code = exit_code.take(); - - input.drain()?; - - if let Some(exit_code) = exit_code { - let mut v: Vec<_> = exit_code.collect(); - - if let Some(v) = v.pop() { - let break_loop = !matches!(v.as_i64(), Ok(0)); - - stack.add_env_var("LAST_EXIT_CODE".into(), v); - if break_loop { - input = PipelineData::empty(); - break; - } - } + if last_pipeline { + let (stdout, stderr) = eval_element_redirection::( + engine_state, + stack, + last.redirection.as_ref(), + (stack.pipe_stdout().cloned(), stack.pipe_stderr().cloned()), + )?; + let stack = &mut stack.push_redirection(stdout, stderr); + let (output, failed) = eval_element_with_input::(engine_state, stack, last, input)?; + if failed { + // External command failed. + // Don't return `Err(ShellError)`, so nushell won't show an extra error message. + return Ok(output); + } + input = output; + } else { + let (stdout, stderr) = eval_element_redirection::( + engine_state, + stack, + last.redirection.as_ref(), + (None, None), + )?; + let stack = &mut stack.push_redirection(stdout, stderr); + let (output, failed) = eval_element_with_input::(engine_state, stack, last, input)?; + if failed { + // External command failed. + // Don't return `Err(ShellError)`, so nushell won't show an extra error message. + return Ok(output); + } + input = PipelineData::Empty; + match output { + stream @ PipelineData::ExternalStream { .. } => { + let exit_code = stream.drain_with_exit_code()?; + stack.add_env_var( + "LAST_EXIT_CODE".into(), + Value::int(exit_code, last.expr.span), + ); + if exit_code != 0 { + break; } } - _ => input.drain()?, + PipelineData::ListStream(stream, _) => { + stream.drain()?; + } + PipelineData::Value(..) | PipelineData::Empty => {} } - - input = PipelineData::empty() } } @@ -983,7 +575,7 @@ pub fn eval_subexpression( block: &Block, input: PipelineData, ) -> Result { - eval_block::(engine_state, stack, block, input, true, false) + eval_block::(engine_state, stack, block, input) } pub fn eval_variable( @@ -1020,109 +612,6 @@ pub fn eval_variable( } } -fn gen_save_call( - save_decl_id: DeclId, - out_info: (Span, Expression, bool), - err_info: Option<(Span, Expression, bool)>, -) -> Call { - let (out_span, out_expr, out_append_mode) = out_info; - let mut call = Call { - decl_id: save_decl_id, - head: out_span, - arguments: vec![], - redirect_stdout: false, - redirect_stderr: false, - parser_info: HashMap::new(), - }; - - let mut args = vec![ - Argument::Positional(out_expr), - Argument::Named(( - Spanned { - item: "raw".into(), - span: out_span, - }, - None, - None, - )), - Argument::Named(( - Spanned { - item: "force".into(), - span: out_span, - }, - None, - None, - )), - ]; - if out_append_mode { - call.set_parser_info( - "out-append".to_string(), - Expression { - expr: Expr::Bool(true), - span: out_span, - ty: Type::Bool, - custom_completion: None, - }, - ); - } - if let Some((err_span, err_expr, err_append_mode)) = err_info { - args.push(Argument::Named(( - Spanned { - item: "stderr".into(), - span: err_span, - }, - None, - Some(err_expr), - ))); - if err_append_mode { - call.set_parser_info( - "err-append".to_string(), - Expression { - expr: Expr::Bool(true), - span: err_span, - ty: Type::Bool, - custom_completion: None, - }, - ); - } - } - - call.arguments.append(&mut args); - call -} - -/// A job which saves `PipelineData` to a file in a child thread. -struct DataSaveJob { - inner: JoinHandle<()>, -} - -impl DataSaveJob { - pub fn spawn( - engine_state: EngineState, - mut stack: Stack, - save_call: Call, - input: PipelineData, - ) -> Result { - let span = save_call.head; - Ok(Self { - inner: thread::Builder::new() - .name("stderr saver".to_string()) - .spawn(move || { - let result = - eval_call::(&engine_state, &mut stack, &save_call, input); - if let Err(err) = result { - eprintln!("WARNING: error occurred when redirect to stderr: {:?}", err); - } - }) - .map_err(|e| e.into_spanned(span))?, - }) - } - - pub fn join(self) -> thread::Result<()> { - self.inner.join() - } -} - struct EvalRuntime; impl Eval for EvalRuntime { @@ -1194,21 +683,11 @@ impl Eval for EvalRuntime { stack: &mut Stack, head: &Expression, args: &[ExternalArgument], - is_subexpression: bool, _: Span, ) -> Result { let span = head.span; // FIXME: protect this collect with ctrl-c - Ok(eval_external( - engine_state, - stack, - head, - args, - PipelineData::empty(), - RedirectTarget::Piped(false, false), - is_subexpression, - )? - .into_value(span)) + Ok(eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span)) } fn eval_subexpression( diff --git a/crates/nu-engine/src/eval_helpers.rs b/crates/nu-engine/src/eval_helpers.rs index b804142032..3c4781becf 100644 --- a/crates/nu-engine/src/eval_helpers.rs +++ b/crates/nu-engine/src/eval_helpers.rs @@ -8,24 +8,12 @@ use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{PipelineData, ShellError, Value}; /// Type of eval_block() function -pub type EvalBlockFn = fn( - &EngineState, - &mut Stack, - &Block, - PipelineData, - bool, - bool, -) -> Result; +pub type EvalBlockFn = + fn(&EngineState, &mut Stack, &Block, PipelineData) -> Result; /// Type of eval_block_with_early_return() function -pub type EvalBlockWithEarlyReturnFn = fn( - &EngineState, - &mut Stack, - &Block, - PipelineData, - bool, - bool, -) -> Result; +pub type EvalBlockWithEarlyReturnFn = + fn(&EngineState, &mut Stack, &Block, PipelineData) -> Result; /// Type of eval_expression() function pub type EvalExpressionFn = fn(&EngineState, &mut Stack, &Expression) -> Result; @@ -36,8 +24,6 @@ pub type EvalExpressionWithInputFn = fn( &mut Stack, &Expression, PipelineData, - bool, - bool, ) -> Result<(PipelineData, bool), ShellError>; /// Type of eval_subexpression() function diff --git a/crates/nu-explore/src/nu_common/command.rs b/crates/nu-explore/src/nu_common/command.rs index eb4c5a5191..adbcdb8874 100644 --- a/crates/nu-explore/src/nu_common/command.rs +++ b/crates/nu-explore/src/nu_common/command.rs @@ -2,8 +2,8 @@ use nu_engine::eval_block; use nu_parser::parse; use nu_protocol::debugger::WithoutDebug; use nu_protocol::{ - engine::{EngineState, Stack, StateWorkingSet}, - PipelineData, ShellError, Value, + engine::{EngineState, Redirection, Stack, StateWorkingSet}, + IoStream, PipelineData, ShellError, Value, }; pub fn run_command_with_value( @@ -91,5 +91,9 @@ fn eval_source2( block.pipelines.drain(..block.pipelines.len() - 1); } - eval_block::(engine_state, stack, &block, input, true, true) + let stack = &mut stack.push_redirection( + Some(Redirection::Pipe(IoStream::Capture)), + Some(Redirection::Pipe(IoStream::Capture)), + ); + eval_block::(engine_state, stack, &block, input) } diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index f89d0afc32..26d3329c8e 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -1,6 +1,6 @@ use nu_protocol::ast::{ Argument, Block, Expr, Expression, ExternalArgument, ImportPatternMember, MatchPattern, - PathMember, Pattern, Pipeline, PipelineElement, RecordItem, + PathMember, Pattern, Pipeline, PipelineElement, PipelineRedirection, RecordItem, }; use nu_protocol::{engine::StateWorkingSet, Span}; use nu_protocol::{DeclId, VarId}; @@ -223,7 +223,7 @@ pub fn flatten_expression( output.extend(args); output } - Expr::ExternalCall(head, args, _) => { + Expr::ExternalCall(head, args) => { let mut output = vec![]; match **head { @@ -559,59 +559,42 @@ pub fn flatten_pipeline_element( working_set: &StateWorkingSet, pipeline_element: &PipelineElement, ) -> Vec<(Span, FlatShape)> { - match pipeline_element { - PipelineElement::Expression(span, expr) - | PipelineElement::ErrPipedExpression(span, expr) - | PipelineElement::OutErrPipedExpression(span, expr) => { - if let Some(span) = span { - let mut output = vec![(*span, FlatShape::Pipe)]; - output.append(&mut flatten_expression(working_set, expr)); - output - } else { - flatten_expression(working_set, expr) + let mut output = if let Some(span) = pipeline_element.pipe { + let mut output = vec![(span, FlatShape::Pipe)]; + output.extend(flatten_expression(working_set, &pipeline_element.expr)); + output + } else { + flatten_expression(working_set, &pipeline_element.expr) + }; + + if let Some(redirection) = pipeline_element.redirection.as_ref() { + match redirection { + PipelineRedirection::Single { target, .. } => { + output.push((target.span(), FlatShape::Redirection)); + if let Some(expr) = target.expr() { + output.extend(flatten_expression(working_set, expr)); + } + } + PipelineRedirection::Separate { out, err } => { + let (out, err) = if out.span() <= err.span() { + (out, err) + } else { + (err, out) + }; + + output.push((out.span(), FlatShape::Redirection)); + if let Some(expr) = out.expr() { + output.extend(flatten_expression(working_set, expr)); + } + output.push((err.span(), FlatShape::Redirection)); + if let Some(expr) = err.expr() { + output.extend(flatten_expression(working_set, expr)); + } } } - PipelineElement::Redirection(span, _, expr, _) => { - let mut output = vec![(*span, FlatShape::Redirection)]; - output.append(&mut flatten_expression(working_set, expr)); - output - } - PipelineElement::SeparateRedirection { - out: (out_span, out_expr, _), - err: (err_span, err_expr, _), - } => { - let mut output = vec![(*out_span, FlatShape::Redirection)]; - output.append(&mut flatten_expression(working_set, out_expr)); - output.push((*err_span, FlatShape::Redirection)); - output.append(&mut flatten_expression(working_set, err_expr)); - output - } - PipelineElement::SameTargetRedirection { - cmd: (cmd_span, cmd_expr), - redirection: (redirect_span, redirect_expr, _), - } => { - let mut output = if let Some(span) = cmd_span { - let mut output = vec![(*span, FlatShape::Pipe)]; - output.append(&mut flatten_expression(working_set, cmd_expr)); - output - } else { - flatten_expression(working_set, cmd_expr) - }; - output.push((*redirect_span, FlatShape::Redirection)); - output.append(&mut flatten_expression(working_set, redirect_expr)); - output - } - PipelineElement::And(span, expr) => { - let mut output = vec![(*span, FlatShape::And)]; - output.append(&mut flatten_expression(working_set, expr)); - output - } - PipelineElement::Or(span, expr) => { - let mut output = vec![(*span, FlatShape::Or)]; - output.append(&mut flatten_expression(working_set, expr)); - output - } } + + output } pub fn flatten_pipeline( diff --git a/crates/nu-parser/src/known_external.rs b/crates/nu-parser/src/known_external.rs index d808d264a7..600c3d9852 100644 --- a/crates/nu-parser/src/known_external.rs +++ b/crates/nu-parser/src/known_external.rs @@ -4,7 +4,7 @@ use nu_protocol::{ engine::Command, ShellError, Signature, }; -use nu_protocol::{PipelineData, Spanned, Type}; +use nu_protocol::{PipelineData, Type}; #[derive(Clone)] pub struct KnownExternal { @@ -42,7 +42,6 @@ impl Command for KnownExternal { call: &Call, input: PipelineData, ) -> Result { - let call_span = call.span(); let head_span = call.head; let decl_id = engine_state .find_decl("run-external".as_bytes(), &[]) @@ -110,28 +109,6 @@ impl Command for KnownExternal { } } - if call.redirect_stdout { - extern_call.add_named(( - Spanned { - item: "redirect-stdout".into(), - span: call_span, - }, - None, - None, - )) - } - - if call.redirect_stderr { - extern_call.add_named(( - Spanned { - item: "redirect-stderr".into(), - span: call_span, - }, - None, - None, - )) - } - command.run(engine_state, stack, &extern_call, input) } } diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 9c8840d430..eeb6a590b8 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -17,7 +17,7 @@ pub use flatten::{ }; pub use known_external::KnownExternal; pub use lex::{lex, lex_signature, Token, TokenContents}; -pub use lite_parser::{lite_parse, LiteBlock, LiteElement}; +pub use lite_parser::{lite_parse, LiteBlock, LiteCommand}; pub use parse_keywords::*; pub use parser_path::*; diff --git a/crates/nu-parser/src/lite_parser.rs b/crates/nu-parser/src/lite_parser.rs index c27cc35f00..51b7f1e2ad 100644 --- a/crates/nu-parser/src/lite_parser.rs +++ b/crates/nu-parser/src/lite_parser.rs @@ -1,212 +1,135 @@ -/// Lite parsing converts a flat stream of tokens from the lexer to a syntax element structure that -/// can be parsed. +//! Lite parsing converts a flat stream of tokens from the lexer to a syntax element structure that +//! can be parsed. + +use std::mem; + use crate::{Token, TokenContents}; -use nu_protocol::{ast::Redirection, ParseError, Span}; +use nu_protocol::{ast::RedirectionSource, ParseError, Span}; -#[derive(Debug)] -pub struct LiteCommand { - pub comments: Vec, - pub parts: Vec, +#[derive(Debug, Clone, Copy)] +pub enum LiteRedirectionTarget { + File { + connector: Span, + file: Span, + append: bool, + }, + Pipe { + connector: Span, + }, } -impl Default for LiteCommand { - fn default() -> Self { - Self::new() +impl LiteRedirectionTarget { + pub fn connector(&self) -> Span { + match self { + LiteRedirectionTarget::File { connector, .. } + | LiteRedirectionTarget::Pipe { connector } => *connector, + } } } +#[derive(Debug, Clone)] +pub enum LiteRedirection { + Single { + source: RedirectionSource, + target: LiteRedirectionTarget, + }, + Separate { + out: LiteRedirectionTarget, + err: LiteRedirectionTarget, + }, +} + +#[derive(Debug, Clone, Default)] +pub struct LiteCommand { + pub pipe: Option, + pub comments: Vec, + pub parts: Vec, + pub redirection: Option, +} + impl LiteCommand { - pub fn new() -> Self { - Self { - comments: vec![], - parts: vec![], - } - } - - pub fn push(&mut self, span: Span) { + fn push(&mut self, span: Span) { self.parts.push(span); } - pub fn is_empty(&self) -> bool { - self.parts.is_empty() + fn try_add_redirection( + &mut self, + source: RedirectionSource, + target: LiteRedirectionTarget, + ) -> Result<(), ParseError> { + let redirection = match (self.redirection.take(), source) { + (None, source) => Ok(LiteRedirection::Single { source, target }), + ( + Some(LiteRedirection::Single { + source: RedirectionSource::Stdout, + target: out, + }), + RedirectionSource::Stderr, + ) => Ok(LiteRedirection::Separate { out, err: target }), + ( + Some(LiteRedirection::Single { + source: RedirectionSource::Stderr, + target: err, + }), + RedirectionSource::Stdout, + ) => Ok(LiteRedirection::Separate { out: target, err }), + ( + Some(LiteRedirection::Single { + source, + target: first, + }), + _, + ) => Err(ParseError::MultipleRedirections( + source, + first.connector(), + target.connector(), + )), + ( + Some(LiteRedirection::Separate { out, .. }), + RedirectionSource::Stdout | RedirectionSource::StdoutAndStderr, + ) => Err(ParseError::MultipleRedirections( + RedirectionSource::Stdout, + out.connector(), + target.connector(), + )), + (Some(LiteRedirection::Separate { err, .. }), RedirectionSource::Stderr) => { + Err(ParseError::MultipleRedirections( + RedirectionSource::Stderr, + err.connector(), + target.connector(), + )) + } + }?; + + self.redirection = Some(redirection); + + Ok(()) } } -// Note: the Span is the span of the connector not the whole element -#[derive(Debug)] -pub enum LiteElement { - Command(Option, LiteCommand), - // Similar to LiteElement::Command, except the previous command's output is stderr piped. - // e.g: `e>| cmd` - ErrPipedCommand(Option, LiteCommand), - // Similar to LiteElement::Command, except the previous command's output is stderr + stdout piped. - // e.g: `o+e>| cmd` - OutErrPipedCommand(Option, LiteCommand), - // final field indicates if it's in append mode - Redirection(Span, Redirection, LiteCommand, bool), - // SeparateRedirection variant can only be generated by two different Redirection variant - // final bool field indicates if it's in append mode - SeparateRedirection { - out: (Span, LiteCommand, bool), - err: (Span, LiteCommand, bool), - }, - // SameTargetRedirection variant can only be generated by Command with Redirection::OutAndErr - // redirection's final bool field indicates if it's in append mode - SameTargetRedirection { - cmd: (Option, LiteCommand), - redirection: (Span, LiteCommand, bool), - }, -} - -#[derive(Debug, Default)] +#[derive(Debug, Clone, Default)] pub struct LitePipeline { - pub commands: Vec, + pub commands: Vec, } impl LitePipeline { - pub fn new() -> Self { - Self { commands: vec![] } - } - - pub fn push(&mut self, element: LiteElement) { - self.commands.push(element); - } - - pub fn insert(&mut self, index: usize, element: LiteElement) { - self.commands.insert(index, element); - } - - pub fn is_empty(&self) -> bool { - self.commands.is_empty() - } - - pub fn exists(&self, new_target: &Redirection) -> bool { - for cmd in &self.commands { - if let LiteElement::Redirection(_, exists_target, _, _) = cmd { - if exists_target == new_target { - return true; - } - } + fn push(&mut self, element: &mut LiteCommand) { + if !element.parts.is_empty() || element.redirection.is_some() { + self.commands.push(mem::take(element)); } - false } } -#[derive(Debug)] +#[derive(Debug, Clone, Default)] pub struct LiteBlock { pub block: Vec, } -impl Default for LiteBlock { - fn default() -> Self { - Self::new() - } -} - impl LiteBlock { - pub fn new() -> Self { - Self { block: vec![] } - } - - pub fn push(&mut self, mut pipeline: LitePipeline) { - // once we push `pipeline` to our block - // the block takes ownership of `pipeline`, which means that - // our `pipeline` is complete on collecting commands. - self.merge_redirections(&mut pipeline); - self.merge_cmd_with_outerr_redirection(&mut pipeline); - - self.block.push(pipeline); - } - - pub fn is_empty(&self) -> bool { - self.block.is_empty() - } - - fn merge_cmd_with_outerr_redirection(&self, pipeline: &mut LitePipeline) { - let mut cmd_index = None; - let mut outerr_index = None; - for (index, cmd) in pipeline.commands.iter().enumerate() { - if let LiteElement::Command(..) = cmd { - cmd_index = Some(index); - } - if let LiteElement::Redirection( - _span, - Redirection::StdoutAndStderr, - _target_cmd, - _is_append_mode, - ) = cmd - { - outerr_index = Some(index); - break; - } - } - if let (Some(cmd_index), Some(outerr_index)) = (cmd_index, outerr_index) { - // we can make sure that cmd_index is less than outerr_index. - let outerr_redirect = pipeline.commands.remove(outerr_index); - let cmd = pipeline.commands.remove(cmd_index); - // `outerr_redirect` and `cmd` should always be `LiteElement::Command` and `LiteElement::Redirection` - if let ( - LiteElement::Command(cmd_span, lite_cmd), - LiteElement::Redirection(span, _, outerr_cmd, is_append_mode), - ) = (cmd, outerr_redirect) - { - pipeline.insert( - cmd_index, - LiteElement::SameTargetRedirection { - cmd: (cmd_span, lite_cmd), - redirection: (span, outerr_cmd, is_append_mode), - }, - ) - } - } - } - - fn merge_redirections(&self, pipeline: &mut LitePipeline) { - // In case our command may contains both stdout and stderr redirection. - // We pick them out and Combine them into one LiteElement::SeparateRedirection variant. - let mut stdout_index = None; - let mut stderr_index = None; - for (index, cmd) in pipeline.commands.iter().enumerate() { - if let LiteElement::Redirection(_span, redirection, _target_cmd, _is_append_mode) = cmd - { - match *redirection { - Redirection::Stderr => stderr_index = Some(index), - Redirection::Stdout => stdout_index = Some(index), - Redirection::StdoutAndStderr => {} - } - } - } - - if let (Some(out_indx), Some(err_indx)) = (stdout_index, stderr_index) { - let (out_redirect, err_redirect, new_indx) = { - // to avoid panic, we need to remove commands which have larger index first. - if out_indx > err_indx { - let out_redirect = pipeline.commands.remove(out_indx); - let err_redirect = pipeline.commands.remove(err_indx); - (out_redirect, err_redirect, err_indx) - } else { - let err_redirect = pipeline.commands.remove(err_indx); - let out_redirect = pipeline.commands.remove(out_indx); - (out_redirect, err_redirect, out_indx) - } - }; - // `out_redirect` and `err_redirect` should always be `LiteElement::Redirection` - if let ( - LiteElement::Redirection(out_span, _, out_command, out_append_mode), - LiteElement::Redirection(err_span, _, err_command, err_append_mode), - ) = (out_redirect, err_redirect) - { - // using insert with specific index to keep original - // pipeline commands order. - pipeline.insert( - new_indx, - LiteElement::SeparateRedirection { - out: (out_span, out_command, out_append_mode), - err: (err_span, err_command, err_append_mode), - }, - ) - } + fn push(&mut self, pipeline: &mut LitePipeline) { + if !pipeline.commands.is_empty() { + self.block.push(mem::take(pipeline)); } } } @@ -226,162 +149,230 @@ fn last_non_comment_token(tokens: &[Token], cur_idx: usize) -> Option (LiteBlock, Option) { - let mut block = LiteBlock::new(); - let mut curr_pipeline = LitePipeline::new(); - let mut curr_command = LiteCommand::new(); - - let mut last_token = TokenContents::Eol; - - let mut last_connector = TokenContents::Pipe; - let mut last_connector_span: Option = None; - if tokens.is_empty() { - return (LiteBlock::new(), None); + return (LiteBlock::default(), None); } - let mut curr_comment: Option> = None; + let mut block = LiteBlock::default(); + let mut pipeline = LitePipeline::default(); + let mut command = LiteCommand::default(); + let mut last_token = TokenContents::Eol; + let mut file_redirection = None; + let mut curr_comment: Option> = None; let mut error = None; for (idx, token) in tokens.iter().enumerate() { - match &token.contents { - TokenContents::PipePipe => { - error = error.or(Some(ParseError::ShellOrOr(token.span))); - curr_command.push(token.span); - last_token = TokenContents::Item; - } - TokenContents::Item => { - // If we have a comment, go ahead and attach it - if let Some(curr_comment) = curr_comment.take() { - curr_command.comments = curr_comment; - } - curr_command.push(token.span); - last_token = TokenContents::Item; - } - TokenContents::OutGreaterThan - | TokenContents::OutGreaterGreaterThan - | TokenContents::ErrGreaterThan - | TokenContents::ErrGreaterGreaterThan - | TokenContents::OutErrGreaterThan - | TokenContents::OutErrGreaterGreaterThan => { - if let Some(err) = push_command_to( - &mut curr_pipeline, - curr_command, - last_connector, - last_connector_span, - ) { - error = Some(err); - } + if let Some((source, append, span)) = file_redirection.take() { + if command.parts.is_empty() { + error = error.or(Some(ParseError::LabeledError( + "Redirection without command or expression".into(), + "there is nothing to redirect".into(), + span, + ))); - curr_command = LiteCommand::new(); - last_token = token.contents; - last_connector = token.contents; - last_connector_span = Some(token.span); - } - pipe_token @ (TokenContents::Pipe - | TokenContents::ErrGreaterPipe - | TokenContents::OutErrGreaterPipe) => { - if let Some(err) = push_command_to( - &mut curr_pipeline, - curr_command, - last_connector, - last_connector_span, - ) { - error = Some(err); - } + command.push(span); - curr_command = LiteCommand::new(); - last_token = *pipe_token; - last_connector = *pipe_token; - last_connector_span = Some(token.span); - } - TokenContents::Eol => { - // Handle `[Command] [Pipe] ([Comment] | [Eol])+ [Command]` - // - // `[Eol]` branch checks if previous token is `[Pipe]` to construct pipeline - // and so `[Comment] | [Eol]` should be ignore to make it work - let actual_token = last_non_comment_token(tokens, idx); - if actual_token != Some(TokenContents::Pipe) - && actual_token != Some(TokenContents::OutGreaterThan) - { - if let Some(err) = push_command_to( - &mut curr_pipeline, - curr_command, - last_connector, - last_connector_span, - ) { - error = Some(err); + match token.contents { + TokenContents::Comment => { + command.comments.push(token.span); + curr_comment = None; } - - curr_command = LiteCommand::new(); - if !curr_pipeline.is_empty() { - block.push(curr_pipeline); - - curr_pipeline = LitePipeline::new(); - last_connector = TokenContents::Pipe; - last_connector_span = None; + TokenContents::Pipe + | TokenContents::ErrGreaterPipe + | TokenContents::OutErrGreaterPipe => { + pipeline.push(&mut command); + command.pipe = Some(token.span); + } + TokenContents::Semicolon => { + pipeline.push(&mut command); + block.push(&mut pipeline); + } + TokenContents::Eol => { + pipeline.push(&mut command); + } + _ => command.push(token.span), + } + } else { + match &token.contents { + TokenContents::PipePipe => { + error = error.or(Some(ParseError::ShellOrOr(token.span))); + command.push(span); + command.push(token.span); + } + TokenContents::Item => { + let target = LiteRedirectionTarget::File { + connector: span, + file: token.span, + append, + }; + if let Err(err) = command.try_add_redirection(source, target) { + error = error.or(Some(err)); + command.push(span); + command.push(token.span) + } + } + TokenContents::OutGreaterThan + | TokenContents::OutGreaterGreaterThan + | TokenContents::ErrGreaterThan + | TokenContents::ErrGreaterGreaterThan + | TokenContents::OutErrGreaterThan + | TokenContents::OutErrGreaterGreaterThan => { + error = + error.or(Some(ParseError::Expected("redirection target", token.span))); + command.push(span); + command.push(token.span); + } + TokenContents::Pipe + | TokenContents::ErrGreaterPipe + | TokenContents::OutErrGreaterPipe => { + error = + error.or(Some(ParseError::Expected("redirection target", token.span))); + command.push(span); + pipeline.push(&mut command); + command.pipe = Some(token.span); + } + TokenContents::Eol => { + error = + error.or(Some(ParseError::Expected("redirection target", token.span))); + command.push(span); + pipeline.push(&mut command); + } + TokenContents::Semicolon => { + error = + error.or(Some(ParseError::Expected("redirection target", token.span))); + command.push(span); + pipeline.push(&mut command); + block.push(&mut pipeline); + } + TokenContents::Comment => { + error = error.or(Some(ParseError::Expected("redirection target", span))); + command.push(span); + command.comments.push(token.span); + curr_comment = None; } } - - if last_token == TokenContents::Eol { - // Clear out the comment as we're entering a new comment - curr_comment = None; - } - - last_token = TokenContents::Eol; } - TokenContents::Semicolon => { - if let Some(err) = push_command_to( - &mut curr_pipeline, - curr_command, - last_connector, - last_connector_span, - ) { - error = Some(err); + } else { + match &token.contents { + TokenContents::PipePipe => { + error = error.or(Some(ParseError::ShellOrOr(token.span))); + command.push(token.span); } + TokenContents::Item => { + // This is commented out to preserve old parser behavior, + // but we should probably error here. + // + // if element.redirection.is_some() { + // error = error.or(Some(ParseError::LabeledError( + // "Unexpected positional".into(), + // "cannot add positional arguments after output redirection".into(), + // token.span, + // ))); + // } + // + // For example, this is currently allowed: ^echo thing o> out.txt extra_arg - curr_command = LiteCommand::new(); - if !curr_pipeline.is_empty() { - block.push(curr_pipeline); - - curr_pipeline = LitePipeline::new(); - last_connector = TokenContents::Pipe; - last_connector_span = None; + // If we have a comment, go ahead and attach it + if let Some(curr_comment) = curr_comment.take() { + command.comments = curr_comment; + } + command.push(token.span); } + TokenContents::OutGreaterThan => { + file_redirection = Some((RedirectionSource::Stdout, false, token.span)); + } + TokenContents::OutGreaterGreaterThan => { + file_redirection = Some((RedirectionSource::Stdout, true, token.span)); + } + TokenContents::ErrGreaterThan => { + file_redirection = Some((RedirectionSource::Stderr, false, token.span)); + } + TokenContents::ErrGreaterGreaterThan => { + file_redirection = Some((RedirectionSource::Stderr, true, token.span)); + } + TokenContents::OutErrGreaterThan => { + file_redirection = + Some((RedirectionSource::StdoutAndStderr, false, token.span)); + } + TokenContents::OutErrGreaterGreaterThan => { + file_redirection = Some((RedirectionSource::StdoutAndStderr, true, token.span)); + } + TokenContents::ErrGreaterPipe => { + let target = LiteRedirectionTarget::Pipe { + connector: token.span, + }; + if let Err(err) = command.try_add_redirection(RedirectionSource::Stderr, target) + { + error = error.or(Some(err)); + } + pipeline.push(&mut command); + command.pipe = Some(token.span); + } + TokenContents::OutErrGreaterPipe => { + let target = LiteRedirectionTarget::Pipe { + connector: token.span, + }; + if let Err(err) = + command.try_add_redirection(RedirectionSource::StdoutAndStderr, target) + { + error = error.or(Some(err)); + } + pipeline.push(&mut command); + command.pipe = Some(token.span); + } + TokenContents::Pipe => { + pipeline.push(&mut command); + command.pipe = Some(token.span); + } + TokenContents::Eol => { + // Handle `[Command] [Pipe] ([Comment] | [Eol])+ [Command]` + // + // `[Eol]` branch checks if previous token is `[Pipe]` to construct pipeline + // and so `[Comment] | [Eol]` should be ignore to make it work + let actual_token = last_non_comment_token(tokens, idx); + if actual_token != Some(TokenContents::Pipe) { + pipeline.push(&mut command); + block.push(&mut pipeline); + } - last_token = TokenContents::Semicolon; - } - TokenContents::Comment => { - // Comment is beside something - if last_token != TokenContents::Eol { - curr_command.comments.push(token.span); - curr_comment = None; - } else { - // Comment precedes something - if let Some(curr_comment) = &mut curr_comment { - curr_comment.push(token.span); + if last_token == TokenContents::Eol { + // Clear out the comment as we're entering a new comment + curr_comment = None; + } + } + TokenContents::Semicolon => { + pipeline.push(&mut command); + block.push(&mut pipeline); + } + TokenContents::Comment => { + // Comment is beside something + if last_token != TokenContents::Eol { + command.comments.push(token.span); + curr_comment = None; } else { - curr_comment = Some(vec![token.span]); + // Comment precedes something + if let Some(curr_comment) = &mut curr_comment { + curr_comment.push(token.span); + } else { + curr_comment = Some(vec![token.span]); + } } } - - last_token = TokenContents::Comment; } } + + last_token = token.contents; } - if let Some(err) = push_command_to( - &mut curr_pipeline, - curr_command, - last_connector, - last_connector_span, - ) { - error = Some(err); - } - if !curr_pipeline.is_empty() { - block.push(curr_pipeline); + if let Some((_, _, span)) = file_redirection { + command.push(span); + error = error.or(Some(ParseError::Expected("redirection target", span))); } + pipeline.push(&mut command); + block.push(&mut pipeline); + if last_non_comment_token(tokens, tokens.len()) == Some(TokenContents::Pipe) { ( block, @@ -394,86 +385,3 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { (block, error) } } - -fn get_redirection(connector: TokenContents) -> Option<(Redirection, bool)> { - match connector { - TokenContents::OutGreaterThan => Some((Redirection::Stdout, false)), - TokenContents::OutGreaterGreaterThan => Some((Redirection::Stdout, true)), - TokenContents::ErrGreaterThan => Some((Redirection::Stderr, false)), - TokenContents::ErrGreaterGreaterThan => Some((Redirection::Stderr, true)), - TokenContents::OutErrGreaterThan => Some((Redirection::StdoutAndStderr, false)), - TokenContents::OutErrGreaterGreaterThan => Some((Redirection::StdoutAndStderr, true)), - _ => None, - } -} - -/// push a `command` to `pipeline` -/// -/// It will return Some(err) if `command` is empty and we want to push a -/// redirection command, or we have meet the same redirection in `pipeline`. -fn push_command_to( - pipeline: &mut LitePipeline, - command: LiteCommand, - last_connector: TokenContents, - last_connector_span: Option, -) -> Option { - if !command.is_empty() { - match get_redirection(last_connector) { - Some((redirect, is_append_mode)) => { - let span = last_connector_span - .expect("internal error: redirection missing span information"); - if pipeline.exists(&redirect) { - return Some(ParseError::LabeledError( - "Redirection can be set only once".into(), - "try to remove one".into(), - span, - )); - } - pipeline.push(LiteElement::Redirection( - last_connector_span - .expect("internal error: redirection missing span information"), - redirect, - command, - is_append_mode, - )) - } - None => { - if last_connector == TokenContents::ErrGreaterPipe { - pipeline.push(LiteElement::ErrPipedCommand(last_connector_span, command)) - } else if last_connector == TokenContents::OutErrGreaterPipe { - // Don't allow o+e>| along with redirection. - for cmd in &pipeline.commands { - if matches!( - cmd, - LiteElement::Redirection { .. } - | LiteElement::SameTargetRedirection { .. } - | LiteElement::SeparateRedirection { .. } - ) { - return Some(ParseError::LabeledError( - "`o+e>|` pipe is not allowed to use with redirection".into(), - "try to use different type of pipe, or remove redirection".into(), - last_connector_span - .expect("internal error: outerr pipe missing span information"), - )); - } - } - - pipeline.push(LiteElement::OutErrPipedCommand( - last_connector_span, - command, - )) - } else { - pipeline.push(LiteElement::Command(last_connector_span, command)) - } - } - } - None - } else if get_redirection(last_connector).is_some() { - Some(ParseError::Expected( - "redirection target", - last_connector_span.expect("internal error: redirection missing span information"), - )) - } else { - None - } -} diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 2b4426267a..cf53d7d53a 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -1,6 +1,7 @@ use crate::{ exportable::Exportable, parse_block, + parser::{parse_redirection, redirecting_builtin_error}, parser_path::ParserPath, type_check::{check_block_input_output, type_compatible}, }; @@ -28,7 +29,7 @@ use crate::{ is_math_expression_like, known_external::KnownExternal, lex, - lite_parser::{lite_parse, LiteCommand, LiteElement}, + lite_parser::{lite_parse, LiteCommand}, parser::{ check_call, check_name, garbage, garbage_pipeline, parse, parse_call, parse_expression, parse_full_signature, parse_import_pattern, parse_internal_call, parse_multispan_value, @@ -88,17 +89,8 @@ pub fn is_unaliasable_parser_keyword(working_set: &StateWorkingSet, spans: &[Spa /// This is a new more compact method of calling parse_xxx() functions without repeating the /// parse_call() in each function. Remaining keywords can be moved here. -pub fn parse_keyword( - working_set: &mut StateWorkingSet, - lite_command: &LiteCommand, - is_subexpression: bool, -) -> Pipeline { - let call_expr = parse_call( - working_set, - &lite_command.parts, - lite_command.parts[0], - is_subexpression, - ); +pub fn parse_keyword(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline { + let call_expr = parse_call(working_set, &lite_command.parts, lite_command.parts[0]); // if err.is_some() { // return (Pipeline::from_vec(vec![call_expr]), err); @@ -246,7 +238,8 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) { } } -pub fn parse_for(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression { +pub fn parse_for(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Expression { + let spans = &lite_command.parts; // Checking that the function is used with the correct name // Maybe this is not necessary but it is a sanity check if working_set.get_span_contents(spans[0]) != b"for" { @@ -256,6 +249,10 @@ pub fn parse_for(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expressio )); return garbage(spans[0]); } + if let Some(redirection) = lite_command.redirection.as_ref() { + working_set.error(redirecting_builtin_error("for", redirection)); + return garbage(spans[0]); + } // Parsing the spans and checking that they match the register signature // Using a parsed call makes more sense than checking for how many spans are in the call @@ -393,6 +390,10 @@ pub fn parse_def( )); return (garbage_pipeline(spans), None); } + if let Some(redirection) = lite_command.redirection.as_ref() { + working_set.error(redirecting_builtin_error("def", redirection)); + return (garbage_pipeline(spans), None); + } // Parsing the spans and checking that they match the register signature // Using a parsed call makes more sense than checking for how many spans are in the call @@ -667,6 +668,10 @@ pub fn parse_extern( )); return garbage_pipeline(spans); } + if let Some(redirection) = lite_command.redirection.as_ref() { + working_set.error(redirecting_builtin_error("extern", redirection)); + return garbage_pipeline(spans); + } // Parsing the spans and checking that they match the register signature // Using a parsed call makes more sense than checking for how many spans are in the call @@ -818,6 +823,10 @@ pub fn parse_alias( )); return garbage_pipeline(spans); } + if let Some(redirection) = lite_command.redirection.as_ref() { + working_set.error(redirecting_builtin_error("alias", redirection)); + return garbage_pipeline(spans); + } if let Some(span) = check_name(working_set, spans) { return Pipeline::from_vec(vec![garbage(*span)]); @@ -907,7 +916,7 @@ pub fn parse_alias( { // TODO: Maybe we need to implement a Display trait for Expression? let starting_error_count = working_set.parse_errors.len(); - let expr = parse_expression(working_set, replacement_spans, false); + let expr = parse_expression(working_set, replacement_spans); working_set.parse_errors.truncate(starting_error_count); let msg = format!("{:?}", expr.expr); @@ -923,12 +932,7 @@ pub fn parse_alias( let starting_error_count = working_set.parse_errors.len(); working_set.search_predecls = false; - let expr = parse_call( - working_set, - replacement_spans, - replacement_spans[0], - false, // TODO: Should this be set properly??? - ); + let expr = parse_call(working_set, replacement_spans, replacement_spans[0]); working_set.search_predecls = true; @@ -1063,24 +1067,32 @@ pub fn parse_export_in_block( let full_name = if lite_command.parts.len() > 1 { let sub = working_set.get_span_contents(lite_command.parts[1]); match sub { - b"alias" | b"def" | b"extern" | b"use" | b"module" | b"const" => { - [b"export ", sub].concat() - } - _ => b"export".to_vec(), + b"alias" => "export alias", + b"def" => "export def", + b"extern" => "export extern", + b"use" => "export use", + b"module" => "export module", + b"const" => "export const", + _ => "export", } } else { - b"export".to_vec() + "export" }; - if let Some(decl_id) = working_set.find_decl(&full_name) { + if let Some(redirection) = lite_command.redirection.as_ref() { + working_set.error(redirecting_builtin_error(full_name, redirection)); + return garbage_pipeline(&lite_command.parts); + } + + if let Some(decl_id) = working_set.find_decl(full_name.as_bytes()) { let ParsedInternalCall { call, output, .. } = parse_internal_call( working_set, - if full_name == b"export" { + if full_name == "export" { lite_command.parts[0] } else { span(&lite_command.parts[0..2]) }, - if full_name == b"export" { + if full_name == "export" { &lite_command.parts[1..] } else { &lite_command.parts[2..] @@ -1107,16 +1119,13 @@ pub fn parse_export_in_block( } } else { working_set.error(ParseError::UnknownState( - format!( - "internal error: '{}' declaration not found", - String::from_utf8_lossy(&full_name) - ), + format!("internal error: '{full_name}' declaration not found",), span(&lite_command.parts), )); return garbage_pipeline(&lite_command.parts); }; - if &full_name == b"export" { + if full_name == "export" { // export by itself is meaningless working_set.error(ParseError::UnexpectedKeyword( "export".into(), @@ -1125,19 +1134,16 @@ pub fn parse_export_in_block( return garbage_pipeline(&lite_command.parts); } - match full_name.as_slice() { - b"export alias" => parse_alias(working_set, lite_command, None), - b"export def" => parse_def(working_set, lite_command, None).0, - b"export const" => parse_const(working_set, &lite_command.parts[1..]), - b"export use" => { - let (pipeline, _) = parse_use(working_set, &lite_command.parts); - pipeline - } - b"export module" => parse_module(working_set, lite_command, None).0, - b"export extern" => parse_extern(working_set, lite_command, None), + match full_name { + "export alias" => parse_alias(working_set, lite_command, None), + "export def" => parse_def(working_set, lite_command, None).0, + "export const" => parse_const(working_set, &lite_command.parts[1..]), + "export use" => parse_use(working_set, lite_command).0, + "export module" => parse_module(working_set, lite_command, None).0, + "export extern" => parse_extern(working_set, lite_command, None), _ => { working_set.error(ParseError::UnexpectedKeyword( - String::from_utf8_lossy(&full_name).to_string(), + full_name.into(), lite_command.parts[0], )); @@ -1186,8 +1192,6 @@ pub fn parse_export_in_module( head: spans[0], decl_id: export_decl_id, arguments: vec![], - redirect_stdout: true, - redirect_stderr: false, parser_info: HashMap::new(), }); @@ -1198,6 +1202,8 @@ pub fn parse_export_in_module( let lite_command = LiteCommand { comments: lite_command.comments.clone(), parts: spans[1..].to_vec(), + pipe: lite_command.pipe, + redirection: lite_command.redirection.clone(), }; let (pipeline, cmd_result) = parse_def(working_set, &lite_command, Some(module_name)); @@ -1222,16 +1228,9 @@ pub fn parse_export_in_module( }; // Trying to warp the 'def' call into the 'export def' in a very clumsy way - if let Some(PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(ref def_call), - .. - }, - )) = pipeline.elements.first() + if let Some(Expr::Call(def_call)) = pipeline.elements.first().map(|e| &e.expr.expr) { call = def_call.clone(); - call.head = span(&spans[0..=1]); call.decl_id = export_def_decl_id; } else { @@ -1247,6 +1246,8 @@ pub fn parse_export_in_module( let lite_command = LiteCommand { comments: lite_command.comments.clone(), parts: spans[1..].to_vec(), + pipe: lite_command.pipe, + redirection: lite_command.redirection.clone(), }; let extern_name = [b"export ", kw_name].concat(); @@ -1263,16 +1264,9 @@ pub fn parse_export_in_module( }; // Trying to warp the 'def' call into the 'export def' in a very clumsy way - if let Some(PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(ref def_call), - .. - }, - )) = pipeline.elements.first() + if let Some(Expr::Call(def_call)) = pipeline.elements.first().map(|e| &e.expr.expr) { call = def_call.clone(); - call.head = span(&spans[0..=1]); call.decl_id = export_def_decl_id; } else { @@ -1308,6 +1302,8 @@ pub fn parse_export_in_module( let lite_command = LiteCommand { comments: lite_command.comments.clone(), parts: spans[1..].to_vec(), + pipe: lite_command.pipe, + redirection: lite_command.redirection.clone(), }; let pipeline = parse_alias(working_set, &lite_command, Some(module_name)); @@ -1323,13 +1319,8 @@ pub fn parse_export_in_module( }; // Trying to warp the 'alias' call into the 'export alias' in a very clumsy way - if let Some(PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(ref alias_call), - .. - }, - )) = pipeline.elements.first() + if let Some(Expr::Call(alias_call)) = + pipeline.elements.first().map(|e| &e.expr.expr) { call = alias_call.clone(); @@ -1368,8 +1359,10 @@ pub fn parse_export_in_module( let lite_command = LiteCommand { comments: lite_command.comments.clone(), parts: spans[1..].to_vec(), + pipe: lite_command.pipe, + redirection: lite_command.redirection.clone(), }; - let (pipeline, exportables) = parse_use(working_set, &lite_command.parts); + let (pipeline, exportables) = parse_use(working_set, &lite_command); let export_use_decl_id = if let Some(id) = working_set.find_decl(b"export use") { id @@ -1382,13 +1375,7 @@ pub fn parse_export_in_module( }; // Trying to warp the 'use' call into the 'export use' in a very clumsy way - if let Some(PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(ref use_call), - .. - }, - )) = pipeline.elements.first() + if let Some(Expr::Call(use_call)) = pipeline.elements.first().map(|e| &e.expr.expr) { call = use_call.clone(); @@ -1419,13 +1406,8 @@ pub fn parse_export_in_module( }; // Trying to warp the 'module' call into the 'export module' in a very clumsy way - if let Some(PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(ref module_call), - .. - }, - )) = pipeline.elements.first() + if let Some(Expr::Call(module_call)) = + pipeline.elements.first().map(|e| &e.expr.expr) { call = module_call.clone(); @@ -1476,13 +1458,7 @@ pub fn parse_export_in_module( }; // Trying to warp the 'const' call into the 'export const' in a very clumsy way - if let Some(PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(ref def_call), - .. - }, - )) = pipeline.elements.first() + if let Some(Expr::Call(def_call)) = pipeline.elements.first().map(|e| &e.expr.expr) { call = def_call.clone(); @@ -1690,9 +1666,7 @@ pub fn parse_module_block( for pipeline in &output.block { if pipeline.commands.len() == 1 { - if let LiteElement::Command(_, command) = &pipeline.commands[0] { - parse_def_predecl(working_set, &command.parts); - } + parse_def_predecl(working_set, &pipeline.commands[0].parts); } } @@ -1702,186 +1676,146 @@ pub fn parse_module_block( for pipeline in output.block.iter() { if pipeline.commands.len() == 1 { - match &pipeline.commands[0] { - LiteElement::Command(_, command) - | LiteElement::ErrPipedCommand(_, command) - | LiteElement::OutErrPipedCommand(_, command) => { - let name = working_set.get_span_contents(command.parts[0]); + let command = &pipeline.commands[0]; - match name { - b"def" => { - block.pipelines.push( - parse_def( - working_set, - command, - None, // using commands named as the module locally is OK - ) - .0, - ) - } - b"const" => block - .pipelines - .push(parse_const(working_set, &command.parts)), - b"extern" => block - .pipelines - .push(parse_extern(working_set, command, None)), - b"alias" => { - block.pipelines.push(parse_alias( - working_set, - command, - None, // using aliases named as the module locally is OK - )) - } - b"use" => { - let (pipeline, _) = parse_use(working_set, &command.parts); + let name = working_set.get_span_contents(command.parts[0]); - block.pipelines.push(pipeline) - } - b"module" => { - let (pipeline, _) = parse_module( - working_set, - command, - None, // using modules named as the module locally is OK - ); + match name { + b"def" => { + block.pipelines.push( + parse_def( + working_set, + command, + None, // using commands named as the module locally is OK + ) + .0, + ) + } + b"const" => block + .pipelines + .push(parse_const(working_set, &command.parts)), + b"extern" => block + .pipelines + .push(parse_extern(working_set, command, None)), + b"alias" => { + block.pipelines.push(parse_alias( + working_set, + command, + None, // using aliases named as the module locally is OK + )) + } + b"use" => { + let (pipeline, _) = parse_use(working_set, command); - block.pipelines.push(pipeline) - } - b"export" => { - let (pipe, exportables) = - parse_export_in_module(working_set, command, module_name); + block.pipelines.push(pipeline) + } + b"module" => { + let (pipeline, _) = parse_module( + working_set, + command, + None, // using modules named as the module locally is OK + ); - for exportable in exportables { - match exportable { - Exportable::Decl { name, id } => { - if &name == b"main" { - if module.main.is_some() { - let err_span = if !pipe.elements.is_empty() { - if let PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) = &pipe.elements[0] - { - call.head - } else { - pipe.elements[0].span() - } - } else { - span - }; - working_set.error(ParseError::ModuleDoubleMain( - String::from_utf8_lossy(module_name) - .to_string(), - err_span, - )); + block.pipelines.push(pipeline) + } + b"export" => { + let (pipe, exportables) = + parse_export_in_module(working_set, command, module_name); + + for exportable in exportables { + match exportable { + Exportable::Decl { name, id } => { + if &name == b"main" { + if module.main.is_some() { + let err_span = if !pipe.elements.is_empty() { + if let Expr::Call(call) = &pipe.elements[0].expr.expr { + call.head } else { - module.main = Some(id); + pipe.elements[0].expr.span } } else { - module.add_decl(name, id); - } - } - Exportable::Module { name, id } => { - if &name == b"mod" { - let ( - submodule_main, - submodule_decls, - submodule_submodules, - ) = { - let submodule = working_set.get_module(id); - ( - submodule.main, - submodule.decls(), - submodule.submodules(), - ) - }; - - // Add submodule's decls to the parent module - for (decl_name, decl_id) in submodule_decls { - module.add_decl(decl_name, decl_id); - } - - // Add submodule's main command to the parent module - if let Some(main_decl_id) = submodule_main { - if module.main.is_some() { - let err_span = if !pipe.elements.is_empty() { - if let PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) = &pipe.elements[0] - { - call.head - } else { - pipe.elements[0].span() - } - } else { - span - }; - working_set.error( - ParseError::ModuleDoubleMain( - String::from_utf8_lossy(module_name) - .to_string(), - err_span, - ), - ); - } else { - module.main = Some(main_decl_id); - } - } - - // Add submodule's submodules to the parent module - for (submodule_name, submodule_id) in - submodule_submodules - { - module.add_submodule(submodule_name, submodule_id); - } - } else { - module.add_submodule(name, id); - } - } - Exportable::VarDecl { name, id } => { - module.add_variable(name, id); + span + }; + working_set.error(ParseError::ModuleDoubleMain( + String::from_utf8_lossy(module_name).to_string(), + err_span, + )); + } else { + module.main = Some(id); } + } else { + module.add_decl(name, id); } } + Exportable::Module { name, id } => { + if &name == b"mod" { + let (submodule_main, submodule_decls, submodule_submodules) = { + let submodule = working_set.get_module(id); + (submodule.main, submodule.decls(), submodule.submodules()) + }; - block.pipelines.push(pipe) - } - b"export-env" => { - let (pipe, maybe_env_block) = - parse_export_env(working_set, &command.parts); + // Add submodule's decls to the parent module + for (decl_name, decl_id) in submodule_decls { + module.add_decl(decl_name, decl_id); + } - if let Some(block_id) = maybe_env_block { - module.add_env_block(block_id); + // Add submodule's main command to the parent module + if let Some(main_decl_id) = submodule_main { + if module.main.is_some() { + let err_span = if !pipe.elements.is_empty() { + if let Expr::Call(call) = + &pipe.elements[0].expr.expr + { + call.head + } else { + pipe.elements[0].expr.span + } + } else { + span + }; + working_set.error(ParseError::ModuleDoubleMain( + String::from_utf8_lossy(module_name).to_string(), + err_span, + )); + } else { + module.main = Some(main_decl_id); + } + } + + // Add submodule's submodules to the parent module + for (submodule_name, submodule_id) in submodule_submodules { + module.add_submodule(submodule_name, submodule_id); + } + } else { + module.add_submodule(name, id); + } + } + Exportable::VarDecl { name, id } => { + module.add_variable(name, id); } - - block.pipelines.push(pipe) - } - _ => { - working_set.error(ParseError::ExpectedKeyword( - "def, const, extern, alias, use, module, export or export-env keyword".into(), - command.parts[0], - )); - - block.pipelines.push(garbage_pipeline(&command.parts)) } } + + block.pipelines.push(pipe) } - LiteElement::Redirection(_, _, command, _) => { + b"export-env" => { + let (pipe, maybe_env_block) = parse_export_env(working_set, &command.parts); + + if let Some(block_id) = maybe_env_block { + module.add_env_block(block_id); + } + + block.pipelines.push(pipe) + } + _ => { + working_set.error(ParseError::ExpectedKeyword( + "def, const, extern, alias, use, module, export or export-env keyword" + .into(), + command.parts[0], + )); + block.pipelines.push(garbage_pipeline(&command.parts)) } - LiteElement::SeparateRedirection { - out: (_, command, _), - .. - } => block.pipelines.push(garbage_pipeline(&command.parts)), - LiteElement::SameTargetRedirection { - cmd: (_, command), .. - } => block.pipelines.push(garbage_pipeline(&command.parts)), } } else { working_set.error(ParseError::Expected("not a pipeline", span)); @@ -2069,6 +2003,12 @@ pub fn parse_module( // visible and usable in this module's scope). We want to disable that for files. let spans = &lite_command.parts; + + if let Some(redirection) = lite_command.redirection.as_ref() { + working_set.error(redirecting_builtin_error("module", redirection)); + return (garbage_pipeline(spans), None); + } + let mut module_comments = lite_command.comments.clone(); let split_id = if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" { @@ -2236,8 +2176,6 @@ pub fn parse_module( Argument::Positional(module_name_or_path_expr), Argument::Positional(block_expr), ], - redirect_stdout: true, - redirect_stderr: false, parser_info: HashMap::new(), }); @@ -2252,7 +2190,12 @@ pub fn parse_module( ) } -pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline, Vec) { +pub fn parse_use( + working_set: &mut StateWorkingSet, + lite_command: &LiteCommand, +) -> (Pipeline, Vec) { + let spans = &lite_command.parts; + let (name_span, split_id) = if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" { (spans[1], 2) @@ -2277,6 +2220,11 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline return (garbage_pipeline(spans), vec![]); } + if let Some(redirection) = lite_command.redirection.as_ref() { + working_set.error(redirecting_builtin_error("use", redirection)); + return (garbage_pipeline(spans), vec![]); + } + let (call, call_span, args_spans) = match working_set.find_decl(b"use") { Some(decl_id) => { let (command_spans, rest_spans) = spans.split_at(split_id); @@ -2460,7 +2408,9 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline ) } -pub fn parse_hide(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline { +pub fn parse_hide(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline { + let spans = &lite_command.parts; + if working_set.get_span_contents(spans[0]) != b"hide" { working_set.error(ParseError::UnknownState( "internal error: Wrong call name for 'hide' command".into(), @@ -2468,6 +2418,10 @@ pub fn parse_hide(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline )); return garbage_pipeline(spans); } + if let Some(redirection) = lite_command.redirection.as_ref() { + working_set.error(redirecting_builtin_error("hide", redirection)); + return garbage_pipeline(spans); + } let (call, args_spans) = match working_set.find_decl(b"hide") { Some(decl_id) => { @@ -3054,8 +3008,6 @@ pub fn parse_let(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline decl_id, head: spans[0], arguments: vec![Argument::Positional(lvalue), Argument::Positional(rvalue)], - redirect_stdout: true, - redirect_stderr: false, parser_info: HashMap::new(), }); @@ -3198,8 +3150,6 @@ pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipelin decl_id, head: spans[0], arguments: vec![Argument::Positional(lvalue), Argument::Positional(rvalue)], - redirect_stdout: true, - redirect_stderr: false, parser_info: HashMap::new(), }); @@ -3315,8 +3265,6 @@ pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline decl_id, head: spans[0], arguments: vec![Argument::Positional(lvalue), Argument::Positional(rvalue)], - redirect_stdout: true, - redirect_stderr: false, parser_info: HashMap::new(), }); @@ -3353,10 +3301,21 @@ pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline garbage_pipeline(spans) } -pub fn parse_source(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline { +pub fn parse_source(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline { + let spans = &lite_command.parts; let name = working_set.get_span_contents(spans[0]); if name == b"source" || name == b"source-env" { + if let Some(redirection) = lite_command.redirection.as_ref() { + let name = if name == b"source" { + "source" + } else { + "source-env" + }; + working_set.error(redirecting_builtin_error(name, redirection)); + return garbage_pipeline(spans); + } + let scoped = name == b"source-env"; if let Some(decl_id) = working_set.find_decl(name) { @@ -3537,13 +3496,26 @@ pub fn parse_where_expr(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex } } -pub fn parse_where(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline { - let expression = parse_where_expr(working_set, spans); - Pipeline::from_vec(vec![expression]) +pub fn parse_where(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline { + let expr = parse_where_expr(working_set, &lite_command.parts); + let redirection = lite_command + .redirection + .as_ref() + .map(|r| parse_redirection(working_set, r)); + + let element = PipelineElement { + pipe: None, + expr, + redirection, + }; + + Pipeline { + elements: vec![element], + } } #[cfg(feature = "plugin")] -pub fn parse_register(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline { +pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline { use std::sync::Arc; use nu_plugin::{get_signature, PersistentPlugin, PluginDeclaration}; @@ -3551,6 +3523,8 @@ pub fn parse_register(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipe engine::Stack, IntoSpanned, PluginIdentity, PluginSignature, RegisteredPlugin, }; + let spans = &lite_command.parts; + let cwd = working_set.get_cwd(); // Checking that the function is used with the correct name @@ -3562,6 +3536,10 @@ pub fn parse_register(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipe )); return garbage_pipeline(spans); } + if let Some(redirection) = lite_command.redirection.as_ref() { + working_set.error(redirecting_builtin_error("register", redirection)); + return garbage_pipeline(spans); + } // Parsing the spans and checking that they match the register signature // Using a parsed call makes more sense than checking for how many spans are in the call @@ -3677,7 +3655,7 @@ pub fn parse_register(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipe // We need the current environment variables for `python` based plugins // Or we'll likely have a problem when a plugin is implemented in a virtual Python environment. let get_envs = || { - let stack = Stack::new(); + let stack = Stack::new().capture(); nu_engine::env::env_to_strings(working_set.permanent_state, &stack) }; diff --git a/crates/nu-parser/src/parse_patterns.rs b/crates/nu-parser/src/parse_patterns.rs index 5fe3120681..e93e22bae2 100644 --- a/crates/nu-parser/src/parse_patterns.rs +++ b/crates/nu-parser/src/parse_patterns.rs @@ -7,7 +7,6 @@ use nu_protocol::{ use crate::{ lex, lite_parse, parser::{is_variable, parse_value}, - LiteElement, }; pub fn garbage(span: Span) -> MatchPattern { @@ -108,48 +107,46 @@ pub fn parse_list_pattern(working_set: &mut StateWorkingSet, span: Span) -> Matc let mut args = vec![]; if !output.block.is_empty() { - for arg in &output.block[0].commands { + for command in &output.block[0].commands { let mut spans_idx = 0; - if let LiteElement::Command(_, command) = arg { - while spans_idx < command.parts.len() { - let contents = working_set.get_span_contents(command.parts[spans_idx]); - if contents == b".." { + while spans_idx < command.parts.len() { + let contents = working_set.get_span_contents(command.parts[spans_idx]); + if contents == b".." { + args.push(MatchPattern { + pattern: Pattern::IgnoreRest, + guard: None, + span: command.parts[spans_idx], + }); + break; + } else if contents.starts_with(b"..$") { + if let Some(var_id) = parse_variable_pattern_helper( + working_set, + Span::new( + command.parts[spans_idx].start + 2, + command.parts[spans_idx].end, + ), + ) { args.push(MatchPattern { - pattern: Pattern::IgnoreRest, + pattern: Pattern::Rest(var_id), guard: None, span: command.parts[spans_idx], }); break; - } else if contents.starts_with(b"..$") { - if let Some(var_id) = parse_variable_pattern_helper( - working_set, - Span::new( - command.parts[spans_idx].start + 2, - command.parts[spans_idx].end, - ), - ) { - args.push(MatchPattern { - pattern: Pattern::Rest(var_id), - guard: None, - span: command.parts[spans_idx], - }); - break; - } else { - args.push(garbage(command.parts[spans_idx])); - working_set.error(ParseError::Expected( - "valid variable name", - command.parts[spans_idx], - )); - } } else { - let arg = parse_pattern(working_set, command.parts[spans_idx]); + args.push(garbage(command.parts[spans_idx])); + working_set.error(ParseError::Expected( + "valid variable name", + command.parts[spans_idx], + )); + } + } else { + let arg = parse_pattern(working_set, command.parts[spans_idx]); - args.push(arg); - }; + args.push(arg); + }; - spans_idx += 1; - } + spans_idx += 1; } } } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 6b67f30828..71b66c4306 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1,6 +1,6 @@ use crate::{ lex::{lex, lex_signature}, - lite_parser::{lite_parse, LiteCommand, LiteElement, LitePipeline}, + lite_parser::{lite_parse, LiteCommand, LitePipeline, LiteRedirection, LiteRedirectionTarget}, parse_mut, parse_patterns::parse_pattern, parse_shape_specs::{parse_shape_name, parse_type, ShapeDescriptorUse}, @@ -14,7 +14,7 @@ use nu_protocol::{ Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression, ExternalArgument, FullCellPath, ImportPattern, ImportPatternHead, ImportPatternMember, MatchPattern, Math, Operator, PathMember, Pattern, Pipeline, PipelineElement, - RangeInclusion, RangeOperator, RecordItem, + PipelineRedirection, RangeInclusion, RangeOperator, RecordItem, RedirectionTarget, }, engine::StateWorkingSet, eval_const::eval_constant, @@ -303,11 +303,7 @@ fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> External } } -pub fn parse_external_call( - working_set: &mut StateWorkingSet, - spans: &[Span], - is_subexpression: bool, -) -> Expression { +pub fn parse_external_call(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression { trace!("parse external"); let mut args = vec![]; @@ -324,7 +320,7 @@ pub fn parse_external_call( let head = if head_contents.starts_with(b"$") || head_contents.starts_with(b"(") { // the expression is inside external_call, so it's a subexpression - let arg = parse_expression(working_set, &[head_span], true); + let arg = parse_expression(working_set, &[head_span]); Box::new(arg) } else { let (contents, err) = unescape_unquote_string(&head_contents, head_span); @@ -346,7 +342,7 @@ pub fn parse_external_call( } Expression { - expr: Expr::ExternalCall(head, args, is_subexpression), + expr: Expr::ExternalCall(head, args), span: span(spans), ty: Type::Any, custom_completion: None, @@ -708,7 +704,7 @@ pub fn parse_multispan_value( // is it subexpression? // Not sure, but let's make it not, so the behavior is the same as previous version of nushell. - let arg = parse_expression(working_set, &spans[*spans_idx..], false); + let arg = parse_expression(working_set, &spans[*spans_idx..]); *spans_idx = spans.len() - 1; arg @@ -1095,12 +1091,7 @@ pub fn parse_internal_call( } } -pub fn parse_call( - working_set: &mut StateWorkingSet, - spans: &[Span], - head: Span, - is_subexpression: bool, -) -> Expression { +pub fn parse_call(working_set: &mut StateWorkingSet, spans: &[Span], head: Span) -> Expression { trace!("parsing: call"); if spans.is_empty() { @@ -1179,7 +1170,7 @@ pub fn parse_call( let parsed_call = if let Some(alias) = decl.as_alias() { if let Expression { - expr: Expr::ExternalCall(head, args, is_subexpression), + expr: Expr::ExternalCall(head, args), span: _, ty, custom_completion, @@ -1198,7 +1189,7 @@ pub fn parse_call( head.span = spans[0]; // replacing the spans preserves syntax highlighting return Expression { - expr: Expr::ExternalCall(head, final_args, *is_subexpression), + expr: Expr::ExternalCall(head, final_args), span: span(spans), ty: ty.clone(), custom_completion: *custom_completion, @@ -1246,7 +1237,7 @@ pub fn parse_call( trace!("parsing: external call"); // Otherwise, try external command - parse_external_call(working_set, spans, is_subexpression) + parse_external_call(working_set, spans) } } @@ -3139,9 +3130,11 @@ pub fn parse_row_condition(working_set: &mut StateWorkingSet, spans: &[Span]) -> // We have an expression, so let's convert this into a block. let mut block = Block::new(); let mut pipeline = Pipeline::new(); - pipeline - .elements - .push(PipelineElement::Expression(None, expression)); + pipeline.elements.push(PipelineElement { + pipe: None, + expr: expression, + redirection: None, + }); block.pipelines.push(pipeline); @@ -3846,61 +3839,59 @@ pub fn parse_list_expression( let mut contained_type: Option = None; if !output.block.is_empty() { - for arg in output.block.remove(0).commands { + for mut command in output.block.remove(0).commands { let mut spans_idx = 0; - if let LiteElement::Command(_, mut command) = arg { - while spans_idx < command.parts.len() { - let curr_span = command.parts[spans_idx]; - let curr_tok = working_set.get_span_contents(curr_span); - let (arg, ty) = if curr_tok.starts_with(b"...") - && curr_tok.len() > 3 - && (curr_tok[3] == b'$' || curr_tok[3] == b'[' || curr_tok[3] == b'(') - { - // Parse the spread operator - // Remove "..." before parsing argument to spread operator - command.parts[spans_idx] = Span::new(curr_span.start + 3, curr_span.end); - let spread_arg = parse_multispan_value( - working_set, - &command.parts, - &mut spans_idx, - &SyntaxShape::List(Box::new(element_shape.clone())), - ); - let elem_ty = match &spread_arg.ty { - Type::List(elem_ty) => *elem_ty.clone(), - _ => Type::Any, - }; - let span = Span::new(curr_span.start, spread_arg.span.end); - let spread_expr = Expression { - expr: Expr::Spread(Box::new(spread_arg)), - span, - ty: elem_ty.clone(), - custom_completion: None, - }; - (spread_expr, elem_ty) - } else { - let arg = parse_multispan_value( - working_set, - &command.parts, - &mut spans_idx, - element_shape, - ); - let ty = arg.ty.clone(); - (arg, ty) + while spans_idx < command.parts.len() { + let curr_span = command.parts[spans_idx]; + let curr_tok = working_set.get_span_contents(curr_span); + let (arg, ty) = if curr_tok.starts_with(b"...") + && curr_tok.len() > 3 + && (curr_tok[3] == b'$' || curr_tok[3] == b'[' || curr_tok[3] == b'(') + { + // Parse the spread operator + // Remove "..." before parsing argument to spread operator + command.parts[spans_idx] = Span::new(curr_span.start + 3, curr_span.end); + let spread_arg = parse_multispan_value( + working_set, + &command.parts, + &mut spans_idx, + &SyntaxShape::List(Box::new(element_shape.clone())), + ); + let elem_ty = match &spread_arg.ty { + Type::List(elem_ty) => *elem_ty.clone(), + _ => Type::Any, }; + let span = Span::new(curr_span.start, spread_arg.span.end); + let spread_expr = Expression { + expr: Expr::Spread(Box::new(spread_arg)), + span, + ty: elem_ty.clone(), + custom_completion: None, + }; + (spread_expr, elem_ty) + } else { + let arg = parse_multispan_value( + working_set, + &command.parts, + &mut spans_idx, + element_shape, + ); + let ty = arg.ty.clone(); + (arg, ty) + }; - if let Some(ref ctype) = contained_type { - if *ctype != ty { - contained_type = Some(Type::Any); - } - } else { - contained_type = Some(ty); + if let Some(ref ctype) = contained_type { + if *ctype != ty { + contained_type = Some(Type::Any); } - - args.push(arg); - - spans_idx += 1; + } else { + contained_type = Some(ty); } + + args.push(arg); + + spans_idx += 1; } } } @@ -4861,7 +4852,7 @@ pub fn parse_math_expression( if first_span == b"if" || first_span == b"match" { // If expression if spans.len() > 1 { - return parse_call(working_set, spans, spans[0], false); + return parse_call(working_set, spans, spans[0]); } else { working_set.error(ParseError::Expected( "expression", @@ -4936,7 +4927,7 @@ pub fn parse_math_expression( // allow `if` to be a special value for assignment. if content == b"if" || content == b"match" { - let rhs = parse_call(working_set, &spans[idx..], spans[0], false); + let rhs = parse_call(working_set, &spans[idx..], spans[0]); expr_stack.push(op); expr_stack.push(rhs); break; @@ -5055,11 +5046,7 @@ pub fn parse_math_expression( .expect("internal error: expression stack empty") } -pub fn parse_expression( - working_set: &mut StateWorkingSet, - spans: &[Span], - is_subexpression: bool, -) -> Expression { +pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression { trace!("parsing: expression"); let mut pos = 0; @@ -5134,7 +5121,7 @@ pub fn parse_expression( spans[0], )); - parse_call(working_set, &spans[pos..], spans[0], is_subexpression) + parse_call(working_set, &spans[pos..], spans[0]) } b"let" | b"const" | b"mut" => { working_set.error(ParseError::AssignInPipeline( @@ -5152,19 +5139,19 @@ pub fn parse_expression( .to_string(), spans[0], )); - parse_call(working_set, &spans[pos..], spans[0], is_subexpression) + parse_call(working_set, &spans[pos..], spans[0]) } b"overlay" => { if spans.len() > 1 && working_set.get_span_contents(spans[1]) == b"list" { // whitelist 'overlay list' - parse_call(working_set, &spans[pos..], spans[0], is_subexpression) + parse_call(working_set, &spans[pos..], spans[0]) } else { working_set.error(ParseError::BuiltinCommandInPipeline( "overlay".into(), spans[0], )); - parse_call(working_set, &spans[pos..], spans[0], is_subexpression) + parse_call(working_set, &spans[pos..], spans[0]) } } b"where" => parse_where_expr(working_set, &spans[pos..]), @@ -5175,10 +5162,10 @@ pub fn parse_expression( spans[0], )); - parse_call(working_set, &spans[pos..], spans[0], is_subexpression) + parse_call(working_set, &spans[pos..], spans[0]) } - _ => parse_call(working_set, &spans[pos..], spans[0], is_subexpression), + _ => parse_call(working_set, &spans[pos..], spans[0]), } }; @@ -5217,8 +5204,6 @@ pub fn parse_expression( head: Span::unknown(), decl_id, arguments, - redirect_stdout: true, - redirect_stderr: false, parser_info: HashMap::new(), })); @@ -5251,7 +5236,6 @@ pub fn parse_variable(working_set: &mut StateWorkingSet, span: Span) -> Option Pipeline { trace!("parsing: builtin commands"); if !is_math_expression_like(working_set, lite_command.parts[0]) @@ -5264,12 +5248,7 @@ pub fn parse_builtin_commands( if cmd.is_alias() { // Parse keywords that can be aliased. Note that we check for "unaliasable" keywords // because alias can have any name, therefore, we can't check for "aliasable" keywords. - let call_expr = parse_call( - working_set, - &lite_command.parts, - lite_command.parts[0], - is_subexpression, - ); + let call_expr = parse_call(working_set, &lite_command.parts, lite_command.parts[0]); if let Expression { expr: Expr::Call(call), @@ -5299,26 +5278,31 @@ pub fn parse_builtin_commands( b"const" => parse_const(working_set, &lite_command.parts), b"mut" => parse_mut(working_set, &lite_command.parts), b"for" => { - let expr = parse_for(working_set, &lite_command.parts); + let expr = parse_for(working_set, lite_command); Pipeline::from_vec(vec![expr]) } b"alias" => parse_alias(working_set, lite_command, None), b"module" => parse_module(working_set, lite_command, None).0, - b"use" => { - let (pipeline, _) = parse_use(working_set, &lite_command.parts); - pipeline + b"use" => parse_use(working_set, lite_command).0, + b"overlay" => { + if let Some(redirection) = lite_command.redirection.as_ref() { + working_set.error(redirecting_builtin_error("overlay", redirection)); + return garbage_pipeline(&lite_command.parts); + } + parse_keyword(working_set, lite_command) } - b"overlay" => parse_keyword(working_set, lite_command, is_subexpression), - b"source" | b"source-env" => parse_source(working_set, &lite_command.parts), + b"source" | b"source-env" => parse_source(working_set, lite_command), b"export" => parse_export_in_block(working_set, lite_command), - b"hide" => parse_hide(working_set, &lite_command.parts), - b"where" => parse_where(working_set, &lite_command.parts), + b"hide" => parse_hide(working_set, lite_command), + b"where" => parse_where(working_set, lite_command), #[cfg(feature = "plugin")] - b"register" => parse_register(working_set, &lite_command.parts), + b"register" => parse_register(working_set, lite_command), _ => { - let expr = parse_expression(working_set, &lite_command.parts, is_subexpression); + let element = parse_pipeline_element(working_set, lite_command); - Pipeline::from_vec(vec![expr]) + Pipeline { + elements: vec![element], + } } } } @@ -5459,6 +5443,76 @@ pub fn parse_record(working_set: &mut StateWorkingSet, span: Span) -> Expression } } +fn parse_redirection_target( + working_set: &mut StateWorkingSet, + target: &LiteRedirectionTarget, +) -> RedirectionTarget { + match target { + LiteRedirectionTarget::File { + connector, + file, + append, + } => RedirectionTarget::File { + expr: parse_value(working_set, *file, &SyntaxShape::Any), + append: *append, + span: *connector, + }, + LiteRedirectionTarget::Pipe { connector } => RedirectionTarget::Pipe { span: *connector }, + } +} + +pub(crate) fn parse_redirection( + working_set: &mut StateWorkingSet, + target: &LiteRedirection, +) -> PipelineRedirection { + match target { + LiteRedirection::Single { source, target } => PipelineRedirection::Single { + source: *source, + target: parse_redirection_target(working_set, target), + }, + LiteRedirection::Separate { out, err } => PipelineRedirection::Separate { + out: parse_redirection_target(working_set, out), + err: parse_redirection_target(working_set, err), + }, + } +} + +fn parse_pipeline_element( + working_set: &mut StateWorkingSet, + command: &LiteCommand, +) -> PipelineElement { + trace!("parsing: pipeline element"); + + let expr = parse_expression(working_set, &command.parts); + + let redirection = command + .redirection + .as_ref() + .map(|r| parse_redirection(working_set, r)); + + PipelineElement { + pipe: command.pipe, + expr, + redirection, + } +} + +pub(crate) fn redirecting_builtin_error( + name: &'static str, + redirection: &LiteRedirection, +) -> ParseError { + match redirection { + LiteRedirection::Single { target, .. } => { + ParseError::RedirectingBuiltinCommand(name, target.connector(), None) + } + LiteRedirection::Separate { out, err } => ParseError::RedirectingBuiltinCommand( + name, + out.connector().min(err.connector()), + Some(out.connector().max(err.connector())), + ), + } +} + pub fn parse_pipeline( working_set: &mut StateWorkingSet, pipeline: &LitePipeline, @@ -5467,271 +5521,161 @@ pub fn parse_pipeline( ) -> Pipeline { if pipeline.commands.len() > 1 { // Special case: allow `let` and `mut` to consume the whole pipeline, eg) `let abc = "foo" | str length` - match &pipeline.commands[0] { - LiteElement::Command(_, command) if !command.parts.is_empty() => { - if working_set.get_span_contents(command.parts[0]) == b"let" - || working_set.get_span_contents(command.parts[0]) == b"mut" - { - let mut new_command = LiteCommand { - comments: vec![], - parts: command.parts.clone(), - }; + if let Some(&first) = pipeline.commands[0].parts.first() { + let first = working_set.get_span_contents(first); + if first == b"let" || first == b"mut" { + let name = if first == b"let" { "let" } else { "mut" }; + let mut new_command = LiteCommand { + comments: vec![], + parts: pipeline.commands[0].parts.clone(), + pipe: None, + redirection: None, + }; - for command in &pipeline.commands[1..] { - match command { - LiteElement::Command(Some(pipe_span), command) - | LiteElement::ErrPipedCommand(Some(pipe_span), command) - | LiteElement::OutErrPipedCommand(Some(pipe_span), command) => { - new_command.parts.push(*pipe_span); + if let Some(redirection) = pipeline.commands[0].redirection.as_ref() { + working_set.error(redirecting_builtin_error(name, redirection)); + } - new_command.comments.extend_from_slice(&command.comments); - new_command.parts.extend_from_slice(&command.parts); - } - LiteElement::Redirection(span, ..) => { - working_set.error(ParseError::RedirectionInLetMut(*span, None)) - } - LiteElement::SeparateRedirection { out, err } => { - working_set.error(ParseError::RedirectionInLetMut( - out.0.min(err.0), - Some(out.0.max(err.0)), - )) - } - LiteElement::SameTargetRedirection { redirection, .. } => working_set - .error(ParseError::RedirectionInLetMut(redirection.0, None)), - _ => panic!("unsupported"), - } + for element in &pipeline.commands[1..] { + if let Some(redirection) = pipeline.commands[0].redirection.as_ref() { + working_set.error(redirecting_builtin_error(name, redirection)); + } else { + new_command.parts.push(element.pipe.expect("pipe span")); + new_command.comments.extend_from_slice(&element.comments); + new_command.parts.extend_from_slice(&element.parts); } + } - // if the 'let' is complete enough, use it, if not, fall through for now - if new_command.parts.len() > 3 { - let rhs_span = nu_protocol::span(&new_command.parts[3..]); + // if the 'let' is complete enough, use it, if not, fall through for now + if new_command.parts.len() > 3 { + let rhs_span = nu_protocol::span(&new_command.parts[3..]); - new_command.parts.truncate(3); - new_command.parts.push(rhs_span); + new_command.parts.truncate(3); + new_command.parts.push(rhs_span); - let mut pipeline = - parse_builtin_commands(working_set, &new_command, is_subexpression); + let mut pipeline = parse_builtin_commands(working_set, &new_command); - if pipeline_index == 0 { - let let_decl_id = working_set.find_decl(b"let"); - let mut_decl_id = working_set.find_decl(b"mut"); - for element in pipeline.elements.iter_mut() { - if let PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) = element + if pipeline_index == 0 { + let let_decl_id = working_set.find_decl(b"let"); + let mut_decl_id = working_set.find_decl(b"mut"); + for element in pipeline.elements.iter_mut() { + if let Expr::Call(call) = &element.expr.expr { + if Some(call.decl_id) == let_decl_id + || Some(call.decl_id) == mut_decl_id { - if Some(call.decl_id) == let_decl_id - || Some(call.decl_id) == mut_decl_id + // Do an expansion + if let Some(Expression { + expr: Expr::Block(block_id), + .. + }) = call.positional_iter().nth(1) { - // Do an expansion - if let Some(Expression { - expr: Expr::Block(block_id), - .. - }) = call.positional_iter_mut().nth(1) + let block = working_set.get_block(*block_id); + + if let Some(element) = block + .pipelines + .first() + .and_then(|p| p.elements.first()) + .cloned() { - let block = working_set.get_block(*block_id); - - if let Some(PipelineElement::Expression( - prepend, - expr, - )) = block - .pipelines - .first() - .and_then(|p| p.elements.first()) - .cloned() - { - if expr.has_in_variable(working_set) { - let new_expr = PipelineElement::Expression( - prepend, - wrap_expr_with_collect(working_set, &expr), - ); - - let block = - working_set.get_block_mut(*block_id); - block.pipelines[0].elements[0] = new_expr; - } + if element.has_in_variable(working_set) { + let element = wrap_element_with_collect( + working_set, + &element, + ); + let block = working_set.get_block_mut(*block_id); + block.pipelines[0].elements[0] = element; } } - continue; - } else if element.has_in_variable(working_set) - && !is_subexpression - { - *element = wrap_element_with_collect(working_set, element); } + continue; } else if element.has_in_variable(working_set) && !is_subexpression { *element = wrap_element_with_collect(working_set, element); } + } else if element.has_in_variable(working_set) && !is_subexpression { + *element = wrap_element_with_collect(working_set, element); } } - - return pipeline; } + + return pipeline; } } - _ => {} - }; + } - let mut output = pipeline + let mut elements = pipeline .commands .iter() - .map(|command| match command { - LiteElement::Command(span, command) => { - trace!("parsing: pipeline element: command"); - let expr = parse_expression(working_set, &command.parts, is_subexpression); - - PipelineElement::Expression(*span, expr) - } - LiteElement::ErrPipedCommand(span, command) => { - trace!("parsing: pipeline element: err piped command"); - let expr = parse_expression(working_set, &command.parts, is_subexpression); - - PipelineElement::ErrPipedExpression(*span, expr) - } - LiteElement::OutErrPipedCommand(span, command) => { - trace!("parsing: pipeline element: err piped command"); - let expr = parse_expression(working_set, &command.parts, is_subexpression); - - PipelineElement::OutErrPipedExpression(*span, expr) - } - LiteElement::Redirection(span, redirection, command, is_append_mode) => { - let expr = parse_value(working_set, command.parts[0], &SyntaxShape::Any); - - PipelineElement::Redirection(*span, redirection.clone(), expr, *is_append_mode) - } - LiteElement::SeparateRedirection { - out: (out_span, out_command, out_append_mode), - err: (err_span, err_command, err_append_mode), - } => { - trace!("parsing: pipeline element: separate redirection"); - let out_expr = - parse_value(working_set, out_command.parts[0], &SyntaxShape::Any); - - let err_expr = - parse_value(working_set, err_command.parts[0], &SyntaxShape::Any); - - PipelineElement::SeparateRedirection { - out: (*out_span, out_expr, *out_append_mode), - err: (*err_span, err_expr, *err_append_mode), - } - } - LiteElement::SameTargetRedirection { - cmd: (cmd_span, command), - redirection: (redirect_span, redirect_command, is_append_mode), - } => { - trace!("parsing: pipeline element: same target redirection"); - let expr = parse_expression(working_set, &command.parts, is_subexpression); - let redirect_expr = - parse_value(working_set, redirect_command.parts[0], &SyntaxShape::Any); - PipelineElement::SameTargetRedirection { - cmd: (*cmd_span, expr), - redirection: (*redirect_span, redirect_expr, *is_append_mode), - } - } - }) - .collect::>(); + .map(|element| parse_pipeline_element(working_set, element)) + .collect::>(); if is_subexpression { - for element in output.iter_mut().skip(1) { + for element in elements.iter_mut().skip(1) { if element.has_in_variable(working_set) { *element = wrap_element_with_collect(working_set, element); } } } else { - for element in output.iter_mut() { + for element in elements.iter_mut() { if element.has_in_variable(working_set) { *element = wrap_element_with_collect(working_set, element); } } } - Pipeline { elements: output } + Pipeline { elements } } else { - match &pipeline.commands[0] { - LiteElement::Command(_, command) - | LiteElement::ErrPipedCommand(_, command) - | LiteElement::OutErrPipedCommand(_, command) - | LiteElement::Redirection(_, _, command, _) - | LiteElement::SeparateRedirection { - out: (_, command, _), - .. - } => { - let mut pipeline = parse_builtin_commands(working_set, command, is_subexpression); - - let let_decl_id = working_set.find_decl(b"let"); - let mut_decl_id = working_set.find_decl(b"mut"); - - if pipeline_index == 0 { - for element in pipeline.elements.iter_mut() { - if let PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) = element - { - if Some(call.decl_id) == let_decl_id - || Some(call.decl_id) == mut_decl_id - { - // Do an expansion - if let Some(Expression { - expr: Expr::Block(block_id), - .. - }) = call.positional_iter_mut().nth(1) - { - let block = working_set.get_block(*block_id); - - if let Some(PipelineElement::Expression(prepend, expr)) = block - .pipelines - .first() - .and_then(|p| p.elements.first()) - .cloned() - { - if expr.has_in_variable(working_set) { - let new_expr = PipelineElement::Expression( - prepend, - wrap_expr_with_collect(working_set, &expr), - ); - - let block = working_set.get_block_mut(*block_id); - block.pipelines[0].elements[0] = new_expr; - } - } - } - continue; - } else if element.has_in_variable(working_set) && !is_subexpression { - *element = wrap_element_with_collect(working_set, element); - } - } else if element.has_in_variable(working_set) && !is_subexpression { - *element = wrap_element_with_collect(working_set, element); - } - } - } - pipeline - } - LiteElement::SameTargetRedirection { - cmd: (span, command), - redirection: (redirect_span, redirect_cmd, is_append_mode), - } => { - trace!("parsing: pipeline element: same target redirection"); - let expr = parse_expression(working_set, &command.parts, is_subexpression); - - let redirect_expr = - parse_value(working_set, redirect_cmd.parts[0], &SyntaxShape::Any); - - Pipeline { - elements: vec![PipelineElement::SameTargetRedirection { - cmd: (*span, expr), - redirection: (*redirect_span, redirect_expr, *is_append_mode), - }], + if let Some(&first) = pipeline.commands[0].parts.first() { + let first = working_set.get_span_contents(first); + if first == b"let" || first == b"mut" { + if let Some(redirection) = pipeline.commands[0].redirection.as_ref() { + let name = if first == b"let" { "let" } else { "mut" }; + working_set.error(redirecting_builtin_error(name, redirection)); } } } + + let mut pipeline = parse_builtin_commands(working_set, &pipeline.commands[0]); + + let let_decl_id = working_set.find_decl(b"let"); + let mut_decl_id = working_set.find_decl(b"mut"); + + if pipeline_index == 0 { + for element in pipeline.elements.iter_mut() { + if let Expr::Call(call) = &element.expr.expr { + if Some(call.decl_id) == let_decl_id || Some(call.decl_id) == mut_decl_id { + // Do an expansion + if let Some(Expression { + expr: Expr::Block(block_id), + .. + }) = call.positional_iter().nth(1) + { + let block = working_set.get_block(*block_id); + + if let Some(element) = block + .pipelines + .first() + .and_then(|p| p.elements.first()) + .cloned() + { + if element.has_in_variable(working_set) { + let element = wrap_element_with_collect(working_set, &element); + let block = working_set.get_block_mut(*block_id); + block.pipelines[0].elements[0] = element; + } + } + } + continue; + } else if element.has_in_variable(working_set) && !is_subexpression { + *element = wrap_element_with_collect(working_set, element); + } + } else if element.has_in_variable(working_set) && !is_subexpression { + *element = wrap_element_with_collect(working_set, element); + } + } + } + + pipeline } } @@ -5757,19 +5701,7 @@ pub fn parse_block( // that share the same block can see each other for pipeline in &lite_block.block { if pipeline.commands.len() == 1 { - match &pipeline.commands[0] { - LiteElement::Command(_, command) - | LiteElement::ErrPipedCommand(_, command) - | LiteElement::OutErrPipedCommand(_, command) - | LiteElement::Redirection(_, _, command, _) - | LiteElement::SeparateRedirection { - out: (_, command, _), - .. - } - | LiteElement::SameTargetRedirection { - cmd: (_, command), .. - } => parse_def_predecl(working_set, &command.parts), - } + parse_def_predecl(working_set, &pipeline.commands[0].parts) } } @@ -5852,32 +5784,27 @@ pub fn discover_captures_in_pipeline_element( seen_blocks: &mut HashMap>, output: &mut Vec<(VarId, Span)>, ) -> Result<(), ParseError> { - match element { - PipelineElement::Expression(_, expression) - | PipelineElement::ErrPipedExpression(_, expression) - | PipelineElement::OutErrPipedExpression(_, expression) - | PipelineElement::Redirection(_, _, expression, _) - | PipelineElement::And(_, expression) - | PipelineElement::Or(_, expression) => { - discover_captures_in_expr(working_set, expression, seen, seen_blocks, output) - } - PipelineElement::SeparateRedirection { - out: (_, out_expr, _), - err: (_, err_expr, _), - } => { - discover_captures_in_expr(working_set, out_expr, seen, seen_blocks, output)?; - discover_captures_in_expr(working_set, err_expr, seen, seen_blocks, output)?; - Ok(()) - } - PipelineElement::SameTargetRedirection { - cmd: (_, cmd_expr), - redirection: (_, redirect_expr, _), - } => { - discover_captures_in_expr(working_set, cmd_expr, seen, seen_blocks, output)?; - discover_captures_in_expr(working_set, redirect_expr, seen, seen_blocks, output)?; - Ok(()) + discover_captures_in_expr(working_set, &element.expr, seen, seen_blocks, output)?; + + if let Some(redirection) = element.redirection.as_ref() { + match redirection { + PipelineRedirection::Single { target, .. } => { + if let Some(expr) = target.expr() { + discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?; + } + } + PipelineRedirection::Separate { out, err } => { + if let Some(expr) = out.expr() { + discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?; + } + if let Some(expr) = err.expr() { + discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?; + } + } } } + + Ok(()) } pub fn discover_captures_in_pattern(pattern: &MatchPattern, seen: &mut Vec) { @@ -6043,7 +5970,7 @@ pub fn discover_captures_in_expr( } Expr::CellPath(_) => {} Expr::DateTime(_) => {} - Expr::ExternalCall(head, args, _) => { + Expr::ExternalCall(head, args) => { discover_captures_in_expr(working_set, head, seen, seen_blocks, output)?; for ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) in args { @@ -6193,66 +6120,37 @@ pub fn discover_captures_in_expr( Ok(()) } +fn wrap_redirection_with_collect( + working_set: &mut StateWorkingSet, + target: &RedirectionTarget, +) -> RedirectionTarget { + match target { + RedirectionTarget::File { expr, append, span } => RedirectionTarget::File { + expr: wrap_expr_with_collect(working_set, expr), + span: *span, + append: *append, + }, + RedirectionTarget::Pipe { span } => RedirectionTarget::Pipe { span: *span }, + } +} + fn wrap_element_with_collect( working_set: &mut StateWorkingSet, element: &PipelineElement, ) -> PipelineElement { - match element { - PipelineElement::Expression(span, expression) => { - PipelineElement::Expression(*span, wrap_expr_with_collect(working_set, expression)) - } - PipelineElement::ErrPipedExpression(span, expression) => { - PipelineElement::ErrPipedExpression( - *span, - wrap_expr_with_collect(working_set, expression), - ) - } - PipelineElement::OutErrPipedExpression(span, expression) => { - PipelineElement::OutErrPipedExpression( - *span, - wrap_expr_with_collect(working_set, expression), - ) - } - PipelineElement::Redirection(span, redirection, expression, is_append_mode) => { - PipelineElement::Redirection( - *span, - redirection.clone(), - wrap_expr_with_collect(working_set, expression), - *is_append_mode, - ) - } - PipelineElement::SeparateRedirection { - out: (out_span, out_exp, out_append_mode), - err: (err_span, err_exp, err_append_mode), - } => PipelineElement::SeparateRedirection { - out: ( - *out_span, - wrap_expr_with_collect(working_set, out_exp), - *out_append_mode, - ), - err: ( - *err_span, - wrap_expr_with_collect(working_set, err_exp), - *err_append_mode, - ), - }, - PipelineElement::SameTargetRedirection { - cmd: (cmd_span, cmd_exp), - redirection: (redirect_span, redirect_exp, is_append_mode), - } => PipelineElement::SameTargetRedirection { - cmd: (*cmd_span, wrap_expr_with_collect(working_set, cmd_exp)), - redirection: ( - *redirect_span, - wrap_expr_with_collect(working_set, redirect_exp), - *is_append_mode, - ), - }, - PipelineElement::And(span, expression) => { - PipelineElement::And(*span, wrap_expr_with_collect(working_set, expression)) - } - PipelineElement::Or(span, expression) => { - PipelineElement::Or(*span, wrap_expr_with_collect(working_set, expression)) - } + PipelineElement { + pipe: element.pipe, + expr: wrap_expr_with_collect(working_set, &element.expr), + redirection: element.redirection.as_ref().map(|r| match r { + PipelineRedirection::Single { source, target } => PipelineRedirection::Single { + source: *source, + target: wrap_redirection_with_collect(working_set, target), + }, + PipelineRedirection::Separate { out, err } => PipelineRedirection::Separate { + out: wrap_redirection_with_collect(working_set, out), + err: wrap_redirection_with_collect(working_set, err), + }, + }), } } @@ -6304,8 +6202,6 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) head: Span::new(0, 0), arguments: output, decl_id, - redirect_stdout: true, - redirect_stderr: false, parser_info: HashMap::new(), })), span, diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index ce0c4b4c56..9643f74caf 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -1,7 +1,6 @@ use nu_protocol::{ ast::{ Assignment, Bits, Block, Boolean, Comparison, Expr, Expression, Math, Operator, Pipeline, - PipelineElement, }, engine::StateWorkingSet, ParseError, Type, @@ -917,62 +916,51 @@ pub fn check_pipeline_type( let mut output_errors: Option> = None; 'elem: for elem in &pipeline.elements { - match elem { - PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) => { - let decl = working_set.get_decl(call.decl_id); + if elem.redirection.is_some() { + current_type = Type::Any; + } else if let Expr::Call(call) = &elem.expr.expr { + let decl = working_set.get_decl(call.decl_id); - if current_type == Type::Any { - let mut new_current_type = None; - for (_, call_output) in decl.signature().input_output_types { - if let Some(inner_current_type) = &new_current_type { - if inner_current_type == &Type::Any { - break; - } else if inner_current_type != &call_output { - // Union unequal types to Any for now - new_current_type = Some(Type::Any) - } - } else { - new_current_type = Some(call_output.clone()) + if current_type == Type::Any { + let mut new_current_type = None; + for (_, call_output) in decl.signature().input_output_types { + if let Some(inner_current_type) = &new_current_type { + if inner_current_type == &Type::Any { + break; + } else if inner_current_type != &call_output { + // Union unequal types to Any for now + new_current_type = Some(Type::Any) } - } - - if let Some(new_current_type) = new_current_type { - current_type = new_current_type } else { - current_type = Type::Any; + new_current_type = Some(call_output.clone()) } - continue 'elem; + } + + if let Some(new_current_type) = new_current_type { + current_type = new_current_type } else { - for (call_input, call_output) in decl.signature().input_output_types { - if type_compatible(&call_input, ¤t_type) { - current_type = call_output.clone(); - continue 'elem; - } + current_type = Type::Any; + } + continue 'elem; + } else { + for (call_input, call_output) in decl.signature().input_output_types { + if type_compatible(&call_input, ¤t_type) { + current_type = call_output.clone(); + continue 'elem; } } + } - if !decl.signature().input_output_types.is_empty() { - if let Some(output_errors) = &mut output_errors { - output_errors.push(ParseError::InputMismatch(current_type, call.head)) - } else { - output_errors = - Some(vec![ParseError::InputMismatch(current_type, call.head)]); - } + if !decl.signature().input_output_types.is_empty() { + if let Some(output_errors) = &mut output_errors { + output_errors.push(ParseError::InputMismatch(current_type, call.head)) + } else { + output_errors = Some(vec![ParseError::InputMismatch(current_type, call.head)]); } - current_type = Type::Any; - } - PipelineElement::Expression(_, Expression { ty, .. }) => { - current_type = ty.clone(); - } - _ => { - current_type = Type::Any; } + current_type = Type::Any; + } else { + current_type = elem.expr.ty.clone(); } } @@ -1015,7 +1003,8 @@ pub fn check_block_input_output(working_set: &StateWorkingSet, block: &Block) -> .elements .last() .expect("internal error: we should have elements") - .span() + .expr + .span }; output_errors.push(ParseError::OutputMismatch(output_type.clone(), span)) diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index a4f2aa454f..44dfae5db5 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -1,10 +1,8 @@ use nu_parser::*; -use nu_protocol::ast::{Argument, Call, PathMember}; -use nu_protocol::Span; use nu_protocol::{ - ast::{Expr, Expression, PipelineElement}, + ast::{Argument, Call, Expr, PathMember}, engine::{Command, EngineState, Stack, StateWorkingSet}, - ParseError, PipelineData, ShellError, Signature, SyntaxShape, + ParseError, PipelineData, ShellError, Signature, Span, SyntaxShape, }; use rstest::rstest; @@ -73,21 +71,15 @@ fn test_int( } else { assert!(err.is_none(), "{test_tag}: unexpected error {err:#?}"); assert_eq!(block.len(), 1, "{test_tag}: result block length > 1"); - let expressions = &block.pipelines[0]; + let pipeline = &block.pipelines[0]; assert_eq!( - expressions.len(), + pipeline.len(), 1, "{test_tag}: got multiple result expressions, expected 1" ); - if let PipelineElement::Expression( - _, - Expression { - expr: observed_val, .. - }, - ) = &expressions.elements[0] - { - compare_rhs_binary_op(test_tag, &expected_val, observed_val); - } + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + compare_rhs_binary_op(test_tag, &expected_val, &element.expr.expr); } } @@ -112,7 +104,7 @@ fn compare_rhs_binary_op( "{test_tag}: Expected: {expected:#?}, observed: {observed:#?}" ) } - Expr::ExternalCall(e, _, _) => { + Expr::ExternalCall(e, _) => { let observed_expr = &e.expr; assert_eq!( expected, observed_expr, @@ -259,6 +251,7 @@ pub fn multi_test_parse_number() { test_int(test.0, test.1, test.2, test.3); } } + #[ignore] #[test] fn test_parse_any() { @@ -277,6 +270,7 @@ fn test_parse_any() { } } } + #[test] pub fn parse_int() { let engine_state = EngineState::new(); @@ -286,18 +280,11 @@ pub fn parse_int() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - assert!(matches!( - expressions.elements[0], - PipelineElement::Expression( - _, - Expression { - expr: Expr::Int(3), - .. - } - ) - )) + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert_eq!(element.expr.expr, Expr::Int(3)); } #[test] @@ -309,18 +296,11 @@ pub fn parse_int_with_underscores() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - assert!(matches!( - expressions.elements[0], - PipelineElement::Expression( - _, - Expression { - expr: Expr::Int(420692023), - .. - } - ) - )) + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert_eq!(element.expr.expr, Expr::Int(420692023)); } #[test] @@ -339,41 +319,32 @@ pub fn parse_cell_path() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); - // hoo boy this pattern matching is a pain - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - if let Expr::FullCellPath(b) = &expr.expr { - assert!(matches!( - b.head, - Expression { - expr: Expr::Var(_), - .. - } - )); - if let [a, b] = &b.tail[..] { - if let PathMember::String { val, optional, .. } = a { - assert_eq!(val, "bar"); - assert_eq!(optional, &false); - } else { - panic!("wrong type") - } - - if let PathMember::String { val, optional, .. } = b { - assert_eq!(val, "baz"); - assert_eq!(optional, &false); - } else { - panic!("wrong type") - } + if let Expr::FullCellPath(b) = &element.expr.expr { + assert!(matches!(b.head.expr, Expr::Var(_))); + if let [a, b] = &b.tail[..] { + if let PathMember::String { val, optional, .. } = a { + assert_eq!(val, "bar"); + assert_eq!(optional, &false); } else { - panic!("cell path tail is unexpected") + panic!("wrong type") + } + + if let PathMember::String { val, optional, .. } = b { + assert_eq!(val, "baz"); + assert_eq!(optional, &false); + } else { + panic!("wrong type") } } else { - panic!("Not a cell path"); + panic!("cell path tail is unexpected") } } else { - panic!("Not an expression") + panic!("Not a cell path"); } } @@ -394,41 +365,32 @@ pub fn parse_cell_path_optional() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); - // hoo boy this pattern matching is a pain - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - if let Expr::FullCellPath(b) = &expr.expr { - assert!(matches!( - b.head, - Expression { - expr: Expr::Var(_), - .. - } - )); - if let [a, b] = &b.tail[..] { - if let PathMember::String { val, optional, .. } = a { - assert_eq!(val, "bar"); - assert_eq!(optional, &true); - } else { - panic!("wrong type") - } - - if let PathMember::String { val, optional, .. } = b { - assert_eq!(val, "baz"); - assert_eq!(optional, &false); - } else { - panic!("wrong type") - } + if let Expr::FullCellPath(b) = &element.expr.expr { + assert!(matches!(b.head.expr, Expr::Var(_))); + if let [a, b] = &b.tail[..] { + if let PathMember::String { val, optional, .. } = a { + assert_eq!(val, "bar"); + assert_eq!(optional, &true); } else { - panic!("cell path tail is unexpected") + panic!("wrong type") + } + + if let PathMember::String { val, optional, .. } = b { + assert_eq!(val, "baz"); + assert_eq!(optional, &false); + } else { + panic!("wrong type") } } else { - panic!("Not a cell path"); + panic!("cell path tail is unexpected") } } else { - panic!("Not an expression") + panic!("Not a cell path"); } } @@ -441,13 +403,11 @@ pub fn parse_binary_with_hex_format() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - assert_eq!(expr.expr, Expr::Binary(vec![0x13])) - } else { - panic!("Not an expression") - } + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert_eq!(element.expr.expr, Expr::Binary(vec![0x13])); } #[test] @@ -459,13 +419,11 @@ pub fn parse_binary_with_incomplete_hex_format() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - assert_eq!(expr.expr, Expr::Binary(vec![0x03])) - } else { - panic!("Not an expression") - } + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert_eq!(element.expr.expr, Expr::Binary(vec![0x03])); } #[test] @@ -477,13 +435,11 @@ pub fn parse_binary_with_binary_format() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - assert_eq!(expr.expr, Expr::Binary(vec![0b10101000])) - } else { - panic!("Not an expression") - } + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert_eq!(element.expr.expr, Expr::Binary(vec![0b10101000])); } #[test] @@ -495,13 +451,11 @@ pub fn parse_binary_with_incomplete_binary_format() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - assert_eq!(expr.expr, Expr::Binary(vec![0b00000010])) - } else { - panic!("Not an expression") - } + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert_eq!(element.expr.expr, Expr::Binary(vec![0b00000010])); } #[test] @@ -513,13 +467,11 @@ pub fn parse_binary_with_octal_format() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - assert_eq!(expr.expr, Expr::Binary(vec![0o250])) - } else { - panic!("Not an expression") - } + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert_eq!(element.expr.expr, Expr::Binary(vec![0o250])); } #[test] @@ -531,13 +483,11 @@ pub fn parse_binary_with_incomplete_octal_format() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - assert_eq!(expr.expr, Expr::Binary(vec![0o2])) - } else { - panic!("Not an expression") - } + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert_eq!(element.expr.expr, Expr::Binary(vec![0o2])); } #[test] @@ -549,13 +499,11 @@ pub fn parse_binary_with_invalid_octal_format() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - assert!(!matches!(&expr.expr, Expr::Binary(_))) - } else { - panic!("Not an expression") - } + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert!(!matches!(element.expr.expr, Expr::Binary(_))); } #[test] @@ -569,13 +517,11 @@ pub fn parse_binary_with_multi_byte_char() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - assert!(!matches!(&expr.expr, Expr::Binary(_))) - } else { - panic!("Not an expression") - } + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert!(!matches!(element.expr.expr, Expr::Binary(_))) } #[test] @@ -591,17 +537,12 @@ pub fn parse_call() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); - if let PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) = &expressions.elements[0] - { + if let Expr::Call(call) = &element.expr.expr { assert_eq!(call.decl_id, 0); } } @@ -650,17 +591,12 @@ pub fn parse_call_short_flag_batch_arg_allowed() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); - if let PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) = &expressions.elements[0] - { + if let Expr::Call(call) = &element.expr.expr { assert_eq!(call.decl_id, 0); assert_eq!(call.arguments.len(), 2); matches!(call.arguments[0], Argument::Named((_, None, None))); @@ -767,42 +703,28 @@ fn test_nothing_comparison_eq() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - assert!(matches!( - &expressions.elements[0], - PipelineElement::Expression( - _, - Expression { - expr: Expr::BinaryOp(..), - .. - } - ) - )) + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert!(matches!(&element.expr.expr, Expr::BinaryOp(..))); } + #[rstest] -#[case(b"let a = 1 err> /dev/null", "RedirectionInLetMut")] -#[case(b"let a = 1 out> /dev/null", "RedirectionInLetMut")] -#[case(b"mut a = 1 err> /dev/null", "RedirectionInLetMut")] -#[case(b"mut a = 1 out> /dev/null", "RedirectionInLetMut")] -// This two cases cause AssignInPipeline instead of RedirectionInLetMut -#[case(b"let a = 1 out+err> /dev/null", "AssignInPipeline")] -#[case(b"mut a = 1 out+err> /dev/null", "AssignInPipeline")] -fn test_redirection_with_letmut(#[case] phase: &[u8], #[case] expected: &str) { +#[case(b"let a = 1 err> /dev/null")] +#[case(b"let a = 1 out> /dev/null")] +#[case(b"mut a = 1 err> /dev/null")] +#[case(b"mut a = 1 out> /dev/null")] +#[case(b"let a = 1 out+err> /dev/null")] +#[case(b"mut a = 1 out+err> /dev/null")] +fn test_redirection_with_letmut(#[case] phase: &[u8]) { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); let _block = parse(&mut working_set, None, phase, true); - match expected { - "RedirectionInLetMut" => assert!(matches!( - working_set.parse_errors.first(), - Some(ParseError::RedirectionInLetMut(_, _)) - )), - "AssignInPipeline" => assert!(matches!( - working_set.parse_errors.first(), - Some(ParseError::AssignInPipeline(_, _, _, _)) - )), - _ => panic!("unexpected pattern"), - } + assert!(matches!( + working_set.parse_errors.first(), + Some(ParseError::RedirectingBuiltinCommand(_, _, _)) + )); } #[test] @@ -814,18 +736,11 @@ fn test_nothing_comparison_neq() { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - assert!(matches!( - &expressions.elements[0], - PipelineElement::Expression( - _, - Expression { - expr: Expr::BinaryOp(..), - .. - } - ) - )) + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert!(matches!(&element.expr.expr, Expr::BinaryOp(..))); } mod string { @@ -840,13 +755,11 @@ mod string { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - assert_eq!(expr.expr, Expr::String("hello nushell".to_string())) - } else { - panic!("Not an expression") - } + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert_eq!(element.expr.expr, Expr::String("hello nushell".to_string())) } mod interpolation { @@ -864,26 +777,23 @@ mod string { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - let subexprs: Vec<&Expr> = match expr { - Expression { - expr: Expr::StringInterpolation(expressions), - .. - } => expressions.iter().map(|e| &e.expr).collect(), - _ => panic!("Expected an `Expr::StringInterpolation`"), - }; + let subexprs: Vec<&Expr> = match &element.expr.expr { + Expr::StringInterpolation(expressions) => { + expressions.iter().map(|e| &e.expr).collect() + } + _ => panic!("Expected an `Expr::StringInterpolation`"), + }; - assert_eq!(subexprs.len(), 2); + assert_eq!(subexprs.len(), 2); - assert_eq!(subexprs[0], &Expr::String("hello ".to_string())); + assert_eq!(subexprs[0], &Expr::String("hello ".to_string())); - assert!(matches!(subexprs[1], &Expr::FullCellPath(..))); - } else { - panic!("Not an expression") - } + assert!(matches!(subexprs[1], &Expr::FullCellPath(..))); } #[test] @@ -896,25 +806,21 @@ mod string { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); - assert_eq!(expressions.len(), 1); + let subexprs: Vec<&Expr> = match &element.expr.expr { + Expr::StringInterpolation(expressions) => { + expressions.iter().map(|e| &e.expr).collect() + } + _ => panic!("Expected an `Expr::StringInterpolation`"), + }; - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - let subexprs: Vec<&Expr> = match expr { - Expression { - expr: Expr::StringInterpolation(expressions), - .. - } => expressions.iter().map(|e| &e.expr).collect(), - _ => panic!("Expected an `Expr::StringInterpolation`"), - }; + assert_eq!(subexprs.len(), 1); - assert_eq!(subexprs.len(), 1); - - assert_eq!(subexprs[0], &Expr::String("hello (39 + 3)".to_string())); - } else { - panic!("Not an expression") - } + assert_eq!(subexprs[0], &Expr::String("hello (39 + 3)".to_string())); } #[test] @@ -927,27 +833,23 @@ mod string { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); - assert_eq!(expressions.len(), 1); + let subexprs: Vec<&Expr> = match &element.expr.expr { + Expr::StringInterpolation(expressions) => { + expressions.iter().map(|e| &e.expr).collect() + } + _ => panic!("Expected an `Expr::StringInterpolation`"), + }; - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - let subexprs: Vec<&Expr> = match expr { - Expression { - expr: Expr::StringInterpolation(expressions), - .. - } => expressions.iter().map(|e| &e.expr).collect(), - _ => panic!("Expected an `Expr::StringInterpolation`"), - }; + assert_eq!(subexprs.len(), 2); - assert_eq!(subexprs.len(), 2); + assert_eq!(subexprs[0], &Expr::String("hello \\".to_string())); - assert_eq!(subexprs[0], &Expr::String("hello \\".to_string())); - - assert!(matches!(subexprs[1], &Expr::FullCellPath(..))); - } else { - panic!("Not an expression") - } + assert!(matches!(subexprs[1], &Expr::FullCellPath(..))); } #[test] @@ -960,24 +862,20 @@ mod string { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); - assert_eq!(expressions.len(), 1); + let subexprs: Vec<&Expr> = match &element.expr.expr { + Expr::StringInterpolation(expressions) => { + expressions.iter().map(|e| &e.expr).collect() + } + _ => panic!("Expected an `Expr::StringInterpolation`"), + }; - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - let subexprs: Vec<&Expr> = match expr { - Expression { - expr: Expr::StringInterpolation(expressions), - .. - } => expressions.iter().map(|e| &e.expr).collect(), - _ => panic!("Expected an `Expr::StringInterpolation`"), - }; - - assert_eq!(subexprs.len(), 1); - assert_eq!(subexprs[0], &Expr::String("(1 + 3)(7 - 5)".to_string())); - } else { - panic!("Not an expression") - } + assert_eq!(subexprs.len(), 1); + assert_eq!(subexprs[0], &Expr::String("(1 + 3)(7 - 5)".to_string())); } #[test] @@ -1084,24 +982,19 @@ mod range { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1, "{tag}: block length"); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1, "{tag}: expression length"); - if let PipelineElement::Expression( - _, - Expression { - expr: - Expr::Range( - Some(_), - None, - Some(_), - RangeOperator { - inclusion: the_inclusion, - .. - }, - ), + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1, "{tag}: expression length"); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + if let Expr::Range( + Some(_), + None, + Some(_), + RangeOperator { + inclusion: the_inclusion, .. }, - ) = expressions.elements[0] + ) = element.expr.expr { assert_eq!( the_inclusion, inclusion, @@ -1143,24 +1036,19 @@ mod range { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 2, "{tag} block len 2"); - let expressions = &block.pipelines[1]; - assert_eq!(expressions.len(), 1, "{tag}: expression length 1"); - if let PipelineElement::Expression( - _, - Expression { - expr: - Expr::Range( - Some(_), - None, - Some(_), - RangeOperator { - inclusion: the_inclusion, - .. - }, - ), + let pipeline = &block.pipelines[1]; + assert_eq!(pipeline.len(), 1, "{tag}: expression length 1"); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + if let Expr::Range( + Some(_), + None, + Some(_), + RangeOperator { + inclusion: the_inclusion, .. }, - ) = expressions.elements[0] + ) = element.expr.expr { assert_eq!( the_inclusion, inclusion, @@ -1189,24 +1077,19 @@ mod range { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1, "{tag}: block len 1"); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1, "{tag}: expression length 1"); - if let PipelineElement::Expression( - _, - Expression { - expr: - Expr::Range( - Some(_), - None, - None, - RangeOperator { - inclusion: the_inclusion, - .. - }, - ), + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1, "{tag}: expression length"); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + if let Expr::Range( + Some(_), + None, + None, + RangeOperator { + inclusion: the_inclusion, .. }, - ) = expressions.elements[0] + ) = element.expr.expr { assert_eq!( the_inclusion, inclusion, @@ -1235,24 +1118,19 @@ mod range { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1, "{tag}: block len 1"); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1, "{tag}: expression length 1"); - if let PipelineElement::Expression( - _, - Expression { - expr: - Expr::Range( - None, - None, - Some(_), - RangeOperator { - inclusion: the_inclusion, - .. - }, - ), + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1, "{tag}: expression length"); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + if let Expr::Range( + None, + None, + Some(_), + RangeOperator { + inclusion: the_inclusion, .. }, - ) = expressions.elements[0] + ) = element.expr.expr { assert_eq!( the_inclusion, inclusion, @@ -1281,24 +1159,19 @@ mod range { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1, "{tag}: block length 1"); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1, "{tag}: expression length 1"); - if let PipelineElement::Expression( - _, - Expression { - expr: - Expr::Range( - Some(_), - Some(_), - Some(_), - RangeOperator { - inclusion: the_inclusion, - .. - }, - ), + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1, "{tag}: expression length"); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + if let Expr::Range( + Some(_), + Some(_), + Some(_), + RangeOperator { + inclusion: the_inclusion, .. }, - ) = expressions.elements[0] + ) = element.expr.expr { assert_eq!( the_inclusion, inclusion, @@ -1671,31 +1544,21 @@ mod input_types { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 2); + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 2); + assert!(pipeline.elements[0].redirection.is_none()); + assert!(pipeline.elements[1].redirection.is_none()); - match &expressions.elements[0] { - PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) => { + match &pipeline.elements[0].expr.expr { + Expr::Call(call) => { let expected_id = working_set.find_decl(b"ls").unwrap(); assert_eq!(call.decl_id, expected_id) } _ => panic!("Expected expression Call not found"), } - match &expressions.elements[1] { - PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) => { + match &pipeline.elements[1].expr.expr { + Expr::Call(call) => { let expected_id = working_set.find_decl(b"group-by").unwrap(); assert_eq!(call.decl_id, expected_id) } @@ -1718,15 +1581,10 @@ mod input_types { engine_state.merge_delta(delta).unwrap(); - let expressions = &block.pipelines[0]; - match &expressions.elements[3] { - PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) => { + let pipeline = &block.pipelines[0]; + assert!(pipeline.elements[3].redirection.is_none()); + match &pipeline.elements[3].expr.expr { + Expr::Call(call) => { let arg = &call.arguments[0]; match arg { Argument::Positional(a) => match &a.expr { @@ -1734,17 +1592,12 @@ mod input_types { Expr::Subexpression(id) => { let block = engine_state.get_block(*id); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 2); + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 2); + assert!(pipeline.elements[1].redirection.is_none()); - match &expressions.elements[1] { - PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) => { + match &pipeline.elements[1].expr.expr { + Expr::Call(call) => { let working_set = StateWorkingSet::new(&engine_state); let expected_id = working_set.find_decl(b"min").unwrap(); assert_eq!(call.decl_id, expected_id) @@ -1776,29 +1629,20 @@ mod input_types { assert!(working_set.parse_errors.is_empty()); assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - match &expressions.elements[2] { - PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) => { + let pipeline = &block.pipelines[0]; + assert!(pipeline.elements[2].redirection.is_none()); + assert!(pipeline.elements[3].redirection.is_none()); + + match &pipeline.elements[2].expr.expr { + Expr::Call(call) => { let expected_id = working_set.find_decl(b"with-column").unwrap(); assert_eq!(call.decl_id, expected_id) } _ => panic!("Expected expression Call not found"), } - match &expressions.elements[3] { - PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - ) => { + match &pipeline.elements[3].expr.expr { + Expr::Call(call) => { let expected_id = working_set.find_decl(b"collect").unwrap(); assert_eq!(call.decl_id, expected_id) } diff --git a/crates/nu-parser/tests/test_parser_unicode_escapes.rs b/crates/nu-parser/tests/test_parser_unicode_escapes.rs index a4e923fd17..29db3714be 100644 --- a/crates/nu-parser/tests/test_parser_unicode_escapes.rs +++ b/crates/nu-parser/tests/test_parser_unicode_escapes.rs @@ -1,13 +1,9 @@ #![cfg(test)] -//use nu_parser::ParseError; use nu_parser::*; use nu_protocol::{ - //ast::{Expr, Expression, PipelineElement}, - ast::{Expr, PipelineElement}, - //engine::{Command, EngineState, Stack, StateWorkingSet}, + ast::Expr, engine::{EngineState, StateWorkingSet}, - //Signature, SyntaxShape, }; pub fn do_test(test: &[u8], expected: &str, error_contains: Option<&str>) { @@ -19,13 +15,11 @@ pub fn do_test(test: &[u8], expected: &str, error_contains: Option<&str>) { match working_set.parse_errors.first() { None => { assert_eq!(block.len(), 1); - let expressions = &block.pipelines[0]; - assert_eq!(expressions.len(), 1); - if let PipelineElement::Expression(_, expr) = &expressions.elements[0] { - assert_eq!(expr.expr, Expr::String(expected.to_string())) - } else { - panic!("Not an expression") - } + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + assert_eq!(element.expr.expr, Expr::String(expected.to_string())); } Some(pev) => match error_contains { None => { diff --git a/crates/nu-plugin/src/plugin/context.rs b/crates/nu-plugin/src/plugin/context.rs index 00c0f5091c..0874252263 100644 --- a/crates/nu-plugin/src/plugin/context.rs +++ b/crates/nu-plugin/src/plugin/context.rs @@ -6,8 +6,8 @@ use std::{ use nu_engine::get_eval_block_with_early_return; use nu_protocol::{ ast::Call, - engine::{Closure, EngineState, Stack}, - Config, IntoSpanned, PipelineData, PluginIdentity, ShellError, Span, Spanned, Value, + engine::{Closure, EngineState, Redirection, Stack}, + Config, IntoSpanned, IoStream, PipelineData, PluginIdentity, ShellError, Span, Spanned, Value, }; /// Object safe trait for abstracting operations required of the plugin context. @@ -107,8 +107,6 @@ impl PluginExecutionContext for PluginExecutionCommandContext { &mut stack, &block, input, - false, - false, ) { Ok(v) => v.into_value(span), Err(e) => Value::error(e, self.call.head), @@ -155,7 +153,24 @@ impl PluginExecutionContext for PluginExecutionCommandContext { inner: vec![], })?; - let mut stack = self.stack.captures_to_stack(closure.item.captures); + let mut stack = self + .stack + .captures_to_stack(closure.item.captures) + .reset_pipes(); + + let stdout = if redirect_stdout { + Some(Redirection::Pipe(IoStream::Capture)) + } else { + None + }; + + let stderr = if redirect_stderr { + Some(Redirection::Pipe(IoStream::Capture)) + } else { + None + }; + + let stack = &mut stack.push_redirection(stdout, stderr); // Set up the positional arguments for (idx, value) in positional.into_iter().enumerate() { @@ -174,14 +189,7 @@ impl PluginExecutionContext for PluginExecutionCommandContext { let eval_block_with_early_return = get_eval_block_with_early_return(&self.engine_state); - eval_block_with_early_return( - &self.engine_state, - &mut stack, - block, - input, - redirect_stdout, - redirect_stderr, - ) + eval_block_with_early_return(&self.engine_state, stack, block, input) } } diff --git a/crates/nu-plugin/src/plugin/declaration.rs b/crates/nu-plugin/src/plugin/declaration.rs index 727fc59879..b1f66545f6 100644 --- a/crates/nu-plugin/src/plugin/declaration.rs +++ b/crates/nu-plugin/src/plugin/declaration.rs @@ -92,6 +92,7 @@ impl Command for PluginDeclaration { // We need the current environment variables for `python` based plugins. Or // we'll likely have a problem when a plugin is implemented in a virtual Python // environment. + let stack = &mut stack.start_capture(); nu_engine::env::env_to_strings(engine_state, stack) }) }) diff --git a/crates/nu-protocol/src/alias.rs b/crates/nu-protocol/src/alias.rs index 712a017956..47b7e0fd9e 100644 --- a/crates/nu-protocol/src/alias.rs +++ b/crates/nu-protocol/src/alias.rs @@ -1,9 +1,7 @@ -use crate::engine::{EngineState, Stack}; -use crate::PipelineData; use crate::{ ast::{Call, Expression}, - engine::Command, - ShellError, Signature, + engine::{Command, EngineState, Stack}, + PipelineData, ShellError, Signature, }; #[derive(Clone)] diff --git a/crates/nu-protocol/src/ast/block.rs b/crates/nu-protocol/src/ast/block.rs index 363f8169ac..c5714963b7 100644 --- a/crates/nu-protocol/src/ast/block.rs +++ b/crates/nu-protocol/src/ast/block.rs @@ -1,5 +1,5 @@ use super::Pipeline; -use crate::{ast::PipelineElement, Signature, Span, Type, VarId}; +use crate::{engine::EngineState, IoStream, Signature, Span, Type, VarId}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -19,6 +19,17 @@ impl Block { pub fn is_empty(&self) -> bool { self.pipelines.is_empty() } + + pub fn stdio_redirect( + &self, + engine_state: &EngineState, + ) -> (Option, Option) { + if let Some(first) = self.pipelines.first() { + first.stdio_redirect(engine_state) + } else { + (None, None) + } + } } impl Default for Block { @@ -51,15 +62,10 @@ impl Block { pub fn output_type(&self) -> Type { if let Some(last) = self.pipelines.last() { if let Some(last) = last.elements.last() { - match last { - PipelineElement::Expression(_, expr) => expr.ty.clone(), - PipelineElement::ErrPipedExpression(_, expr) => expr.ty.clone(), - PipelineElement::OutErrPipedExpression(_, expr) => expr.ty.clone(), - PipelineElement::Redirection(_, _, _, _) => Type::Any, - PipelineElement::SeparateRedirection { .. } => Type::Any, - PipelineElement::SameTargetRedirection { .. } => Type::Any, - PipelineElement::And(_, expr) => expr.ty.clone(), - PipelineElement::Or(_, expr) => expr.ty.clone(), + if last.redirection.is_some() { + Type::Any + } else { + last.expr.ty.clone() } } else { Type::Nothing diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index db2250bccf..0dc1a70c53 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -2,10 +2,9 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; -use super::Expression; use crate::{ - engine::StateWorkingSet, eval_const::eval_constant, DeclId, FromValue, ShellError, Span, - Spanned, Value, + ast::Expression, engine::StateWorkingSet, eval_const::eval_constant, DeclId, FromValue, + ShellError, Span, Spanned, Value, }; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -51,8 +50,6 @@ pub struct Call { pub decl_id: DeclId, pub head: Span, pub arguments: Vec, - pub redirect_stdout: bool, - pub redirect_stderr: bool, /// this field is used by the parser to pass additional command-specific information pub parser_info: HashMap, } @@ -63,8 +60,6 @@ impl Call { decl_id: 0, head, arguments: vec![], - redirect_stdout: true, - redirect_stderr: false, parser_info: HashMap::new(), } } diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 0fff16a27a..4b2973a02d 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -5,7 +5,10 @@ use super::{ Call, CellPath, Expression, ExternalArgument, FullCellPath, MatchPattern, Operator, RangeOperator, }; -use crate::{ast::ImportPattern, ast::Unit, BlockId, Signature, Span, Spanned, VarId}; +use crate::{ + ast::ImportPattern, ast::Unit, engine::EngineState, BlockId, IoStream, Signature, Span, + Spanned, VarId, +}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Expr { @@ -22,7 +25,7 @@ pub enum Expr { Var(VarId), VarDecl(VarId), Call(Box), - ExternalCall(Box, Vec, bool), // head, args, is_subexpression + ExternalCall(Box, Vec), // head, args Operator(Operator), RowCondition(BlockId), UnaryNot(Box), @@ -52,6 +55,73 @@ pub enum Expr { Garbage, } +impl Expr { + pub fn stdio_redirect( + &self, + engine_state: &EngineState, + ) -> (Option, Option) { + // Usages of `$in` will be wrapped by a `collect` call by the parser, + // so we do not have to worry about that when considering + // which of the expressions below may consume pipeline output. + match self { + Expr::Call(call) => engine_state.get_decl(call.decl_id).stdio_redirect(), + Expr::Subexpression(block_id) | Expr::Block(block_id) => engine_state + .get_block(*block_id) + .stdio_redirect(engine_state), + Expr::FullCellPath(cell_path) => cell_path.head.expr.stdio_redirect(engine_state), + Expr::Bool(_) + | Expr::Int(_) + | Expr::Float(_) + | Expr::Binary(_) + | Expr::Range(_, _, _, _) + | Expr::Var(_) + | Expr::UnaryNot(_) + | Expr::BinaryOp(_, _, _) + | Expr::Closure(_) // piping into a closure value, not into a closure call + | Expr::List(_) + | Expr::Table(_, _) + | Expr::Record(_) + | Expr::ValueWithUnit(_, _) + | Expr::DateTime(_) + | Expr::String(_) + | Expr::CellPath(_) + | Expr::StringInterpolation(_) + | Expr::Nothing => { + // These expressions do not use the output of the pipeline in any meaningful way, + // so we can discard the previous output by redirecting it to `Null`. + (Some(IoStream::Null), None) + } + Expr::VarDecl(_) + | Expr::Operator(_) + | Expr::Filepath(_, _) + | Expr::Directory(_, _) + | Expr::GlobPattern(_, _) + | Expr::ImportPattern(_) + | Expr::Overlay(_) + | Expr::Signature(_) + | Expr::Spread(_) + | Expr::Garbage => { + // These should be impossible to pipe to, + // but even it is, the pipeline output is not used in any way. + (Some(IoStream::Null), None) + } + Expr::RowCondition(_) | Expr::MatchBlock(_) => { + // These should be impossible to pipe to, + // but if they are, then the pipeline output could be used. + (None, None) + } + Expr::ExternalCall(_, _) => { + // No override necessary, pipes will always be created in eval + (None, None) + } + Expr::Keyword(_, _, _) => { + // Not sure about this; let's return no redirection override for now. + (None, None) + } + } + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum RecordItem { /// A key: val mapping diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index c1f4c617e1..90a4a5aa42 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -184,7 +184,7 @@ impl Expression { } Expr::CellPath(_) => false, Expr::DateTime(_) => false, - Expr::ExternalCall(head, args, _) => { + Expr::ExternalCall(head, args) => { if head.has_in_variable(working_set) { return true; } @@ -369,7 +369,7 @@ impl Expression { } Expr::CellPath(_) => {} Expr::DateTime(_) => {} - Expr::ExternalCall(head, args, _) => { + Expr::ExternalCall(head, args) => { head.replace_span(working_set, replaced, new_span); for ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) in args { expr.replace_span(working_set, replaced, new_span); diff --git a/crates/nu-protocol/src/ast/pipeline.rs b/crates/nu-protocol/src/ast/pipeline.rs index a479dc2270..a1d4fea325 100644 --- a/crates/nu-protocol/src/ast/pipeline.rs +++ b/crates/nu-protocol/src/ast/pipeline.rs @@ -1,100 +1,56 @@ -use crate::{ast::Expression, engine::StateWorkingSet, Span}; +use crate::{ + ast::Expression, + engine::{EngineState, StateWorkingSet}, + IoStream, Span, +}; use serde::{Deserialize, Serialize}; +use std::fmt::Display; -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] -pub enum Redirection { +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)] +pub enum RedirectionSource { Stdout, Stderr, StdoutAndStderr, } -// Note: Span in the below is for the span of the connector not the whole element -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum PipelineElement { - Expression(Option, Expression), - ErrPipedExpression(Option, Expression), - OutErrPipedExpression(Option, Expression), - // final field indicates if it's in append mode - Redirection(Span, Redirection, Expression, bool), - // final bool field indicates if it's in append mode - SeparateRedirection { - out: (Span, Expression, bool), - err: (Span, Expression, bool), - }, - // redirection's final bool field indicates if it's in append mode - SameTargetRedirection { - cmd: (Option, Expression), - redirection: (Span, Expression, bool), - }, - And(Span, Expression), - Or(Span, Expression), +impl Display for RedirectionSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + RedirectionSource::Stdout => "stdout", + RedirectionSource::Stderr => "stderr", + RedirectionSource::StdoutAndStderr => "stdout and stderr", + }) + } } -impl PipelineElement { - pub fn expression(&self) -> &Expression { +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum RedirectionTarget { + File { + expr: Expression, + append: bool, + span: Span, + }, + Pipe { + span: Span, + }, +} + +impl RedirectionTarget { + pub fn span(&self) -> Span { match self { - PipelineElement::Expression(_, expression) - | PipelineElement::ErrPipedExpression(_, expression) - | PipelineElement::OutErrPipedExpression(_, expression) => expression, - PipelineElement::Redirection(_, _, expression, _) => expression, - PipelineElement::SeparateRedirection { - out: (_, expression, _), - .. - } => expression, - PipelineElement::SameTargetRedirection { - cmd: (_, expression), - .. - } => expression, - PipelineElement::And(_, expression) => expression, - PipelineElement::Or(_, expression) => expression, + RedirectionTarget::File { span, .. } | RedirectionTarget::Pipe { span } => *span, } } - pub fn span(&self) -> Span { + pub fn expr(&self) -> Option<&Expression> { match self { - PipelineElement::Expression(None, expression) - | PipelineElement::ErrPipedExpression(None, expression) - | PipelineElement::OutErrPipedExpression(None, expression) - | PipelineElement::SameTargetRedirection { - cmd: (None, expression), - .. - } => expression.span, - PipelineElement::Expression(Some(span), expression) - | PipelineElement::ErrPipedExpression(Some(span), expression) - | PipelineElement::OutErrPipedExpression(Some(span), expression) - | PipelineElement::Redirection(span, _, expression, _) - | PipelineElement::SeparateRedirection { - out: (span, expression, _), - .. - } - | PipelineElement::And(span, expression) - | PipelineElement::Or(span, expression) - | PipelineElement::SameTargetRedirection { - cmd: (Some(span), expression), - .. - } => Span { - start: span.start, - end: expression.span.end, - }, + RedirectionTarget::File { expr, .. } => Some(expr), + RedirectionTarget::Pipe { .. } => None, } } + pub fn has_in_variable(&self, working_set: &StateWorkingSet) -> bool { - match self { - PipelineElement::Expression(_, expression) - | PipelineElement::ErrPipedExpression(_, expression) - | PipelineElement::OutErrPipedExpression(_, expression) - | PipelineElement::Redirection(_, _, expression, _) - | PipelineElement::And(_, expression) - | PipelineElement::Or(_, expression) - | PipelineElement::SameTargetRedirection { - cmd: (_, expression), - .. - } => expression.has_in_variable(working_set), - PipelineElement::SeparateRedirection { - out: (_, out_expr, _), - err: (_, err_expr, _), - } => out_expr.has_in_variable(working_set) || err_expr.has_in_variable(working_set), - } + self.expr().is_some_and(|e| e.has_in_variable(working_set)) } pub fn replace_span( @@ -104,24 +60,72 @@ impl PipelineElement { new_span: Span, ) { match self { - PipelineElement::Expression(_, expression) - | PipelineElement::ErrPipedExpression(_, expression) - | PipelineElement::OutErrPipedExpression(_, expression) - | PipelineElement::Redirection(_, _, expression, _) - | PipelineElement::And(_, expression) - | PipelineElement::Or(_, expression) - | PipelineElement::SameTargetRedirection { - cmd: (_, expression), - .. + RedirectionTarget::File { expr, .. } => { + expr.replace_span(working_set, replaced, new_span) } - | PipelineElement::SeparateRedirection { - out: (_, expression, _), - .. - } => expression.replace_span(working_set, replaced, new_span), + RedirectionTarget::Pipe { .. } => {} } } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum PipelineRedirection { + Single { + source: RedirectionSource, + target: RedirectionTarget, + }, + Separate { + out: RedirectionTarget, + err: RedirectionTarget, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PipelineElement { + pub pipe: Option, + pub expr: Expression, + pub redirection: Option, +} + +impl PipelineElement { + pub fn has_in_variable(&self, working_set: &StateWorkingSet) -> bool { + self.expr.has_in_variable(working_set) + || self.redirection.as_ref().is_some_and(|r| match r { + PipelineRedirection::Single { target, .. } => target.has_in_variable(working_set), + PipelineRedirection::Separate { out, err } => { + out.has_in_variable(working_set) || err.has_in_variable(working_set) + } + }) + } + + pub fn replace_span( + &mut self, + working_set: &mut StateWorkingSet, + replaced: Span, + new_span: Span, + ) { + self.expr.replace_span(working_set, replaced, new_span); + if let Some(expr) = self.redirection.as_mut() { + match expr { + PipelineRedirection::Single { target, .. } => { + target.replace_span(working_set, replaced, new_span) + } + PipelineRedirection::Separate { out, err } => { + out.replace_span(working_set, replaced, new_span); + err.replace_span(working_set, replaced, new_span); + } + } + } + } + + pub fn stdio_redirect( + &self, + engine_state: &EngineState, + ) -> (Option, Option) { + self.expr.expr.stdio_redirect(engine_state) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Pipeline { pub elements: Vec, @@ -143,8 +147,10 @@ impl Pipeline { elements: expressions .into_iter() .enumerate() - .map(|(idx, x)| { - PipelineElement::Expression(if idx == 0 { None } else { Some(x.span) }, x) + .map(|(idx, expr)| PipelineElement { + pipe: if idx == 0 { None } else { Some(expr.span) }, + expr, + redirection: None, }) .collect(), } @@ -157,4 +163,15 @@ impl Pipeline { pub fn is_empty(&self) -> bool { self.elements.is_empty() } + + pub fn stdio_redirect( + &self, + engine_state: &EngineState, + ) -> (Option, Option) { + if let Some(first) = self.elements.first() { + first.stdio_redirect(engine_state) + } else { + (None, None) + } + } } diff --git a/crates/nu-protocol/src/debugger/profiler.rs b/crates/nu-protocol/src/debugger/profiler.rs index d52d8704e4..f7645db497 100644 --- a/crates/nu-protocol/src/debugger/profiler.rs +++ b/crates/nu-protocol/src/debugger/profiler.rs @@ -142,19 +142,14 @@ impl Debugger for Profiler { }; let expr_opt = if self.collect_exprs { - Some(match element { - PipelineElement::Expression(_, expression) => { - expr_to_string(engine_state, &expression.expr) - } - _ => "other".to_string(), - }) + Some(expr_to_string(engine_state, &element.expr.expr)) } else { None }; let new_id = ElementId(self.elements.len()); - let mut new_element = ElementInfo::new(self.depth, element.span()); + let mut new_element = ElementInfo::new(self.depth, element.expr.span); new_element.expr = expr_opt; self.elements.push(new_element); @@ -178,7 +173,7 @@ impl Debugger for Profiler { return; } - let element_span = element.span(); + let element_span = element.expr.span; let out_opt = if self.collect_values { Some(match result { @@ -250,7 +245,7 @@ fn expr_to_string(engine_state: &EngineState, expr: &Expr) -> String { Expr::Closure(_) => "closure".to_string(), Expr::DateTime(_) => "datetime".to_string(), Expr::Directory(_, _) => "directory".to_string(), - Expr::ExternalCall(_, _, _) => "external call".to_string(), + Expr::ExternalCall(_, _) => "external call".to_string(), Expr::Filepath(_, _) => "filepath".to_string(), Expr::Float(_) => "float".to_string(), Expr::FullCellPath(full_cell_path) => { diff --git a/crates/nu-protocol/src/engine/command.rs b/crates/nu-protocol/src/engine/command.rs index a3cdd9950c..c79b02df55 100644 --- a/crates/nu-protocol/src/engine/command.rs +++ b/crates/nu-protocol/src/engine/command.rs @@ -1,4 +1,4 @@ -use crate::{ast::Call, Alias, BlockId, Example, PipelineData, ShellError, Signature}; +use crate::{ast::Call, Alias, BlockId, Example, IoStream, PipelineData, ShellError, Signature}; use super::{EngineState, Stack, StateWorkingSet}; @@ -133,6 +133,10 @@ pub trait Command: Send + Sync + CommandClone { _ => CommandType::Other, } } + + fn stdio_redirect(&self) -> (Option, Option) { + (None, None) + } } pub trait CommandClone { diff --git a/crates/nu-protocol/src/engine/mod.rs b/crates/nu-protocol/src/engine/mod.rs index 49b1aec529..7deff38ad4 100644 --- a/crates/nu-protocol/src/engine/mod.rs +++ b/crates/nu-protocol/src/engine/mod.rs @@ -7,6 +7,7 @@ mod pattern_match; mod stack; mod state_delta; mod state_working_set; +mod stdio; mod usage; mod variable; @@ -19,4 +20,5 @@ pub use pattern_match::*; pub use stack::*; pub use state_delta::*; pub use state_working_set::*; +pub use stdio::*; pub use variable::*; diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 4c336d55d1..4b4d4a1b31 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -1,10 +1,12 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use crate::engine::EngineState; -use crate::engine::DEFAULT_OVERLAY_NAME; -use crate::{ShellError, Span, Value, VarId}; -use crate::{ENV_VARIABLE_ID, NU_VARIABLE_ID}; +use crate::{ + engine::{EngineState, DEFAULT_OVERLAY_NAME}, + IoStream, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID, +}; + +use super::{Redirection, StackCallArgGuard, StackCaptureGuard, StackIoGuard, StackStdio}; /// Environment variables per overlay pub type EnvVars = HashMap>; @@ -37,22 +39,36 @@ pub struct Stack { /// List of active overlays pub active_overlays: Vec, pub recursion_count: u64, - pub parent_stack: Option>, /// Variables that have been deleted (this is used to hide values from parent stack lookups) pub parent_deletions: Vec, + pub(crate) stdio: StackStdio, +} + +impl Default for Stack { + fn default() -> Self { + Self::new() + } } impl Stack { - pub fn new() -> Stack { - Stack { - vars: vec![], - env_vars: vec![], + /// Create a new stack. + /// + /// Stdio will be set to [`IoStream::Inherit`]. So, if the last command is an external command, + /// then its output will be forwarded to the terminal/stdio streams. + /// + /// Use [`Stack::capture`] afterwards if you need to evaluate an expression to a [`Value`](crate::Value) + /// (as opposed to a [`PipelineData`](crate::PipelineData)). + pub fn new() -> Self { + Self { + vars: Vec::new(), + env_vars: Vec::new(), env_hidden: HashMap::new(), active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()], recursion_count: 0, parent_stack: None, parent_deletions: vec![], + stdio: StackStdio::new(), } } @@ -82,9 +98,10 @@ impl Stack { env_hidden: parent.env_hidden.clone(), active_overlays: parent.active_overlays.clone(), recursion_count: parent.recursion_count, - parent_stack: Some(parent), vars: vec![], parent_deletions: vec![], + stdio: parent.stdio.clone(), + parent_stack: Some(parent), } } @@ -235,6 +252,10 @@ impl Stack { } pub fn captures_to_stack(&self, captures: Vec<(VarId, Value)>) -> Stack { + self.captures_to_stack_preserve_stdio(captures).capture() + } + + pub fn captures_to_stack_preserve_stdio(&self, captures: Vec<(VarId, Value)>) -> Stack { // FIXME: this is probably slow let mut env_vars = self.env_vars.clone(); env_vars.push(HashMap::new()); @@ -247,6 +268,7 @@ impl Stack { recursion_count: self.recursion_count, parent_stack: None, parent_deletions: vec![], + stdio: self.stdio.clone(), } } @@ -276,6 +298,7 @@ impl Stack { recursion_count: self.recursion_count, parent_stack: None, parent_deletions: vec![], + stdio: self.stdio.clone(), } } @@ -481,11 +504,87 @@ impl Stack { pub fn remove_overlay(&mut self, name: &str) { self.active_overlays.retain(|o| o != name); } -} -impl Default for Stack { - fn default() -> Self { - Self::new() + /// Returns the [`IoStream`] to use for the current command's stdout. + /// + /// This will be the pipe redirection if one is set, + /// otherwise it will be the current file redirection, + /// otherwise it will be the process's stdout indicated by [`IoStream::Inherit`]. + pub fn stdout(&self) -> &IoStream { + self.stdio.stdout() + } + + /// Returns the [`IoStream`] to use for the current command's stderr. + /// + /// This will be the pipe redirection if one is set, + /// otherwise it will be the current file redirection, + /// otherwise it will be the process's stderr indicated by [`IoStream::Inherit`]. + pub fn stderr(&self) -> &IoStream { + self.stdio.stderr() + } + + /// Returns the [`IoStream`] to use for the last command's stdout. + pub fn pipe_stdout(&self) -> Option<&IoStream> { + self.stdio.pipe_stdout.as_ref() + } + + /// Returns the [`IoStream`] to use for the last command's stderr. + pub fn pipe_stderr(&self) -> Option<&IoStream> { + self.stdio.pipe_stderr.as_ref() + } + + /// Temporarily set the pipe stdout redirection to [`IoStream::Capture`]. + /// + /// This is used before evaluating an expression into a `Value`. + pub fn start_capture(&mut self) -> StackCaptureGuard { + StackCaptureGuard::new(self) + } + + /// Temporarily use the stdio redirections in the parent scope. + /// + /// This is used before evaluating an argument to a call. + pub fn use_call_arg_stdio(&mut self) -> StackCallArgGuard { + StackCallArgGuard::new(self) + } + + /// Temporarily apply redirections to stdout and/or stderr. + pub fn push_redirection( + &mut self, + stdout: Option, + stderr: Option, + ) -> StackIoGuard { + StackIoGuard::new(self, stdout, stderr) + } + + /// Mark stdout for the last command as [`IoStream::Capture`]. + /// + /// This will irreversibly alter the stdio redirections, and so it only makes sense to use this on an owned `Stack` + /// (which is why this function does not take `&mut self`). + /// + /// See [`Stack::start_capture`] which can temporarily set stdout as [`IoStream::Capture`] for a mutable `Stack` reference. + pub fn capture(mut self) -> Self { + self.stdio.pipe_stdout = Some(IoStream::Capture); + self.stdio.pipe_stderr = None; + self + } + + /// Clears any pipe and file redirections and resets stdout and stderr to [`IoStream::Inherit`]. + /// + /// This will irreversibly reset the stdio redirections, and so it only makes sense to use this on an owned `Stack` + /// (which is why this function does not take `&mut self`). + pub fn reset_stdio(mut self) -> Self { + self.stdio = StackStdio::new(); + self + } + + /// Clears any pipe redirections, keeping the current stdout and stderr. + /// + /// This will irreversibly reset some of the stdio redirections, and so it only makes sense to use this on an owned `Stack` + /// (which is why this function does not take `&mut self`). + pub fn reset_pipes(mut self) -> Self { + self.stdio.pipe_stdout = None; + self.stdio.pipe_stderr = None; + self } } diff --git a/crates/nu-protocol/src/engine/stdio.rs b/crates/nu-protocol/src/engine/stdio.rs new file mode 100644 index 0000000000..ba8a3d0459 --- /dev/null +++ b/crates/nu-protocol/src/engine/stdio.rs @@ -0,0 +1,288 @@ +use std::{ + fs::File, + mem, + ops::{Deref, DerefMut}, + sync::Arc, +}; + +use crate::IoStream; + +use super::Stack; + +#[derive(Debug, Clone)] +pub enum Redirection { + /// A pipe redirection. + /// + /// This will only affect the last command of a block. + /// This is created by pipes and pipe redirections (`|`, `e>|`, `o+e>|`, etc.), + /// or set by the next command in the pipeline (e.g., `ignore` sets stdout to [`IoStream::Null`]). + Pipe(IoStream), + /// A file redirection. + /// + /// This will affect all commands in the block. + /// This is only created by file redirections (`o>`, `e>`, `o+e>`, etc.). + File(Arc), +} + +impl Redirection { + pub fn file(file: File) -> Self { + Self::File(Arc::new(file)) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct StackStdio { + /// The stream to use for the next command's stdout. + pub pipe_stdout: Option, + /// The stream to use for the next command's stderr. + pub pipe_stderr: Option, + /// The stream used for the command stdout if `pipe_stdout` is `None`. + /// + /// This should only ever be `File` or `Inherit`. + pub stdout: IoStream, + /// The stream used for the command stderr if `pipe_stderr` is `None`. + /// + /// This should only ever be `File` or `Inherit`. + pub stderr: IoStream, + /// The previous stdout used before the current `stdout` was set. + /// + /// This is used only when evaluating arguments to commands, + /// since the arguments are lazily evaluated inside each command + /// after redirections have already been applied to the command/stack. + /// + /// This should only ever be `File` or `Inherit`. + pub parent_stdout: Option, + /// The previous stderr used before the current `stderr` was set. + /// + /// This is used only when evaluating arguments to commands, + /// since the arguments are lazily evaluated inside each command + /// after redirections have already been applied to the command/stack. + /// + /// This should only ever be `File` or `Inherit`. + pub parent_stderr: Option, +} + +impl StackStdio { + pub(crate) fn new() -> Self { + Self { + pipe_stdout: None, + pipe_stderr: None, + stdout: IoStream::Inherit, + stderr: IoStream::Inherit, + parent_stdout: None, + parent_stderr: None, + } + } + + /// Returns the [`IoStream`] to use for current command's stdout. + /// + /// This will be the pipe redirection if one is set, + /// otherwise it will be the current file redirection, + /// otherwise it will be the process's stdout indicated by [`IoStream::Inherit`]. + pub(crate) fn stdout(&self) -> &IoStream { + self.pipe_stdout.as_ref().unwrap_or(&self.stdout) + } + + /// Returns the [`IoStream`] to use for current command's stderr. + /// + /// This will be the pipe redirection if one is set, + /// otherwise it will be the current file redirection, + /// otherwise it will be the process's stderr indicated by [`IoStream::Inherit`]. + pub(crate) fn stderr(&self) -> &IoStream { + self.pipe_stderr.as_ref().unwrap_or(&self.stderr) + } + + fn push_stdout(&mut self, stdout: IoStream) -> Option { + let stdout = mem::replace(&mut self.stdout, stdout); + mem::replace(&mut self.parent_stdout, Some(stdout)) + } + + fn push_stderr(&mut self, stderr: IoStream) -> Option { + let stderr = mem::replace(&mut self.stderr, stderr); + mem::replace(&mut self.parent_stderr, Some(stderr)) + } +} + +pub struct StackIoGuard<'a> { + stack: &'a mut Stack, + old_pipe_stdout: Option, + old_pipe_stderr: Option, + old_parent_stdout: Option, + old_parent_stderr: Option, +} + +impl<'a> StackIoGuard<'a> { + pub(crate) fn new( + stack: &'a mut Stack, + stdout: Option, + stderr: Option, + ) -> Self { + let stdio = &mut stack.stdio; + + let (old_pipe_stdout, old_parent_stdout) = match stdout { + Some(Redirection::Pipe(stdout)) => { + let old = mem::replace(&mut stdio.pipe_stdout, Some(stdout)); + (old, stdio.parent_stdout.take()) + } + Some(Redirection::File(file)) => { + let file = IoStream::from(file); + ( + mem::replace(&mut stdio.pipe_stdout, Some(file.clone())), + stdio.push_stdout(file), + ) + } + None => (stdio.pipe_stdout.take(), stdio.parent_stdout.take()), + }; + + let (old_pipe_stderr, old_parent_stderr) = match stderr { + Some(Redirection::Pipe(stderr)) => { + let old = mem::replace(&mut stdio.pipe_stderr, Some(stderr)); + (old, stdio.parent_stderr.take()) + } + Some(Redirection::File(file)) => { + (stdio.pipe_stderr.take(), stdio.push_stderr(file.into())) + } + None => (stdio.pipe_stderr.take(), stdio.parent_stderr.take()), + }; + + StackIoGuard { + stack, + old_pipe_stdout, + old_parent_stdout, + old_pipe_stderr, + old_parent_stderr, + } + } +} + +impl<'a> Deref for StackIoGuard<'a> { + type Target = Stack; + + fn deref(&self) -> &Self::Target { + self.stack + } +} + +impl<'a> DerefMut for StackIoGuard<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.stack + } +} + +impl Drop for StackIoGuard<'_> { + fn drop(&mut self) { + self.stdio.pipe_stdout = self.old_pipe_stdout.take(); + self.stdio.pipe_stderr = self.old_pipe_stderr.take(); + + let old_stdout = self.old_parent_stdout.take(); + if let Some(stdout) = mem::replace(&mut self.stdio.parent_stdout, old_stdout) { + self.stdio.stdout = stdout; + } + + let old_stderr = self.old_parent_stderr.take(); + if let Some(stderr) = mem::replace(&mut self.stdio.parent_stderr, old_stderr) { + self.stdio.stderr = stderr; + } + } +} + +pub struct StackCaptureGuard<'a> { + stack: &'a mut Stack, + old_pipe_stdout: Option, + old_pipe_stderr: Option, +} + +impl<'a> StackCaptureGuard<'a> { + pub(crate) fn new(stack: &'a mut Stack) -> Self { + let old_pipe_stdout = mem::replace(&mut stack.stdio.pipe_stdout, Some(IoStream::Capture)); + let old_pipe_stderr = stack.stdio.pipe_stderr.take(); + Self { + stack, + old_pipe_stdout, + old_pipe_stderr, + } + } +} + +impl<'a> Deref for StackCaptureGuard<'a> { + type Target = Stack; + + fn deref(&self) -> &Self::Target { + &*self.stack + } +} + +impl<'a> DerefMut for StackCaptureGuard<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.stack + } +} + +impl Drop for StackCaptureGuard<'_> { + fn drop(&mut self) { + self.stdio.pipe_stdout = self.old_pipe_stdout.take(); + self.stdio.pipe_stderr = self.old_pipe_stderr.take(); + } +} + +pub struct StackCallArgGuard<'a> { + stack: &'a mut Stack, + old_pipe_stdout: Option, + old_pipe_stderr: Option, + old_stdout: Option, + old_stderr: Option, +} + +impl<'a> StackCallArgGuard<'a> { + pub(crate) fn new(stack: &'a mut Stack) -> Self { + let old_pipe_stdout = mem::replace(&mut stack.stdio.pipe_stdout, Some(IoStream::Capture)); + let old_pipe_stderr = stack.stdio.pipe_stderr.take(); + + let old_stdout = stack + .stdio + .parent_stdout + .take() + .map(|stdout| mem::replace(&mut stack.stdio.stdout, stdout)); + + let old_stderr = stack + .stdio + .parent_stderr + .take() + .map(|stderr| mem::replace(&mut stack.stdio.stderr, stderr)); + + Self { + stack, + old_pipe_stdout, + old_pipe_stderr, + old_stdout, + old_stderr, + } + } +} + +impl<'a> Deref for StackCallArgGuard<'a> { + type Target = Stack; + + fn deref(&self) -> &Self::Target { + &*self.stack + } +} + +impl<'a> DerefMut for StackCallArgGuard<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.stack + } +} + +impl Drop for StackCallArgGuard<'_> { + fn drop(&mut self) { + self.stdio.pipe_stdout = self.old_pipe_stdout.take(); + self.stdio.pipe_stderr = self.old_pipe_stderr.take(); + if let Some(stdout) = self.old_stdout.take() { + self.stdio.push_stdout(stdout); + } + if let Some(stderr) = self.old_stderr.take() { + self.stdio.push_stderr(stderr); + } + } +} diff --git a/crates/nu-protocol/src/errors/parse_error.rs b/crates/nu-protocol/src/errors/parse_error.rs index 1574a329b7..80ff2d5154 100644 --- a/crates/nu-protocol/src/errors/parse_error.rs +++ b/crates/nu-protocol/src/errors/parse_error.rs @@ -3,7 +3,7 @@ use std::{ str::{from_utf8, Utf8Error}, }; -use crate::{did_you_mean, Span, Type}; +use crate::{ast::RedirectionSource, did_you_mean, Span, Type}; use miette::Diagnostic; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -85,6 +85,14 @@ pub enum ParseError { )] ShellOutErrRedirect(#[label("use 'out+err>' instead of '2>&1' in Nushell")] Span), + #[error("Multiple redirections provided for {0}.")] + #[diagnostic(code(nu::parser::multiple_redirections))] + MultipleRedirections( + RedirectionSource, + #[label = "first redirection"] Span, + #[label = "second redirection"] Span, + ), + #[error("{0} is not supported on values of type {3}")] #[diagnostic(code(nu::parser::unsupported_operation))] UnsupportedOperationLHS( @@ -449,10 +457,11 @@ pub enum ParseError { span: Span, }, - #[error("Redirection can not be used with let/mut.")] + #[error("Redirection can not be used with {0}.")] #[diagnostic()] - RedirectionInLetMut( - #[label("Not allowed here")] Span, + RedirectingBuiltinCommand( + &'static str, + #[label("not allowed here")] Span, #[label("...and here")] Option, ), @@ -540,10 +549,11 @@ impl ParseError { ParseError::ShellOrOr(s) => *s, ParseError::ShellErrRedirect(s) => *s, ParseError::ShellOutErrRedirect(s) => *s, + ParseError::MultipleRedirections(_, _, s) => *s, ParseError::UnknownOperator(_, _, s) => *s, ParseError::InvalidLiteral(_, _, s) => *s, ParseError::LabeledErrorWithHelp { span: s, .. } => *s, - ParseError::RedirectionInLetMut(s, _) => *s, + ParseError::RedirectingBuiltinCommand(_, s, _) => *s, ParseError::UnexpectedSpreadArg(_, s) => *s, } } diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index 80230733a7..f24f860482 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -145,8 +145,8 @@ pub trait Eval { }), }, Expr::Call(call) => Self::eval_call::(state, mut_state, call, expr.span), - Expr::ExternalCall(head, args, is_subexpression) => { - Self::eval_external_call(state, mut_state, head, args, *is_subexpression, expr.span) + Expr::ExternalCall(head, args) => { + Self::eval_external_call(state, mut_state, head, args, expr.span) } Expr::Subexpression(block_id) => { Self::eval_subexpression::(state, mut_state, *block_id, expr.span) @@ -338,7 +338,6 @@ pub trait Eval { mut_state: &mut Self::MutState, head: &Expression, args: &[ExternalArgument], - is_subexpression: bool, span: Span, ) -> Result; diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index 63ec61973c..cc2b219df4 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -1,6 +1,6 @@ use crate::debugger::{DebugContext, WithoutDebug}; use crate::{ - ast::{Assignment, Block, Call, Expr, Expression, ExternalArgument, PipelineElement}, + ast::{Assignment, Block, Call, Expr, Expression, ExternalArgument}, engine::{EngineState, StateWorkingSet}, eval_base::Eval, record, Config, HistoryFileFormat, PipelineData, Record, ShellError, Span, Value, VarId, @@ -227,11 +227,11 @@ pub fn eval_const_subexpression( ) -> Result { for pipeline in block.pipelines.iter() { for element in pipeline.elements.iter() { - let PipelineElement::Expression(_, expr) = element else { + if element.redirection.is_some() { return Err(ShellError::NotAConstant { span }); - }; + } - input = eval_constant_with_input(working_set, expr, input)? + input = eval_constant_with_input(working_set, &element.expr, input)? } } @@ -321,7 +321,6 @@ impl Eval for EvalConst { _: &mut (), _: &Expression, _: &[ExternalArgument], - _: bool, span: Span, ) -> Result { // TODO: It may be more helpful to give not_a_const_command error diff --git a/crates/nu-protocol/src/pipeline_data/io_stream.rs b/crates/nu-protocol/src/pipeline_data/io_stream.rs new file mode 100644 index 0000000000..b6c95d4eb9 --- /dev/null +++ b/crates/nu-protocol/src/pipeline_data/io_stream.rs @@ -0,0 +1,53 @@ +use std::{fs::File, io, process::Stdio, sync::Arc}; + +#[derive(Debug, Clone)] +pub enum IoStream { + /// Redirect the `stdout` and/or `stderr` of one command as the input for the next command in the pipeline. + /// + /// The output pipe will be available in `PipelineData::ExternalStream::stdout`. + /// + /// If both `stdout` and `stderr` are set to `Pipe`, + /// then they will combined into `ExternalStream::stdout`. + Pipe, + /// Capture output to later be collected into a [`Value`], `Vec`, or used in some other way. + /// + /// The output stream(s) will be available in + /// `PipelineData::ExternalStream::stdout` or `PipelineData::ExternalStream::stderr`. + /// + /// This is similar to `Pipe` but will never combine `stdout` and `stderr` + /// or place an external command's `stderr` into `PipelineData::ExternalStream::stdout`. + Capture, + /// Ignore output. + Null, + /// Output to nushell's `stdout` or `stderr`. + /// + /// This causes external commands to inherit nushell's `stdout` or `stderr`. + Inherit, + /// Redirect output to a file. + File(Arc), // Arc, since we sometimes need to clone `IoStream` into iterators, etc. +} + +impl From for IoStream { + fn from(file: File) -> Self { + Arc::new(file).into() + } +} + +impl From> for IoStream { + fn from(file: Arc) -> Self { + Self::File(file) + } +} + +impl TryFrom<&IoStream> for Stdio { + type Error = io::Error; + + fn try_from(stream: &IoStream) -> Result { + match stream { + IoStream::Pipe | IoStream::Capture => Ok(Self::piped()), + IoStream::Null => Ok(Self::null()), + IoStream::Inherit => Ok(Self::inherit()), + IoStream::File(file) => Ok(file.try_clone()?.into()), + } + } +} diff --git a/crates/nu-protocol/src/pipeline_data/mod.rs b/crates/nu-protocol/src/pipeline_data/mod.rs index 3dce74a3e8..f0c938ebc2 100644 --- a/crates/nu-protocol/src/pipeline_data/mod.rs +++ b/crates/nu-protocol/src/pipeline_data/mod.rs @@ -1,6 +1,8 @@ +mod io_stream; mod metadata; mod stream; +pub use io_stream::*; pub use metadata::*; pub use stream::*; @@ -10,7 +12,7 @@ use crate::{ format_error, Config, ShellError, Span, Value, }; use nu_utils::{stderr_write_all_and_flush, stdout_write_all_and_flush}; -use std::io::Write; +use std::io::{self, Cursor, Read, Write}; use std::sync::{atomic::AtomicBool, Arc}; use std::thread; @@ -204,6 +206,144 @@ impl PipelineData { } } + /// Writes all values or redirects all output to the current stdio streams in `stack`. + /// + /// For [`IoStream::Pipe`] and [`IoStream::Capture`], this will return the `PipelineData` as is + /// without consuming input and without writing anything. + /// + /// For the other [`IoStream`]s, the given `PipelineData` will be completely consumed + /// and `PipelineData::Empty` will be returned. + pub fn write_to_io_streams( + self, + engine_state: &EngineState, + stack: &mut Stack, + ) -> Result { + match (self, stack.stdout()) { + ( + PipelineData::ExternalStream { + stdout, + stderr, + exit_code, + span, + metadata, + trim_end_newline, + }, + _, + ) => { + fn needs_redirect( + stream: Option, + io_stream: &IoStream, + ) -> Result> { + match (stream, io_stream) { + (Some(stream), IoStream::Pipe | IoStream::Capture) => Err(Some(stream)), + (Some(stream), _) => Ok(stream), + (None, _) => Err(None), + } + } + + let (stdout, stderr) = match ( + needs_redirect(stdout, stack.stdout()), + needs_redirect(stderr, stack.stderr()), + ) { + (Ok(stdout), Ok(stderr)) => { + // We need to redirect both stdout and stderr + + // To avoid deadlocks, we must spawn a separate thread to wait on stderr. + let err_thread = { + let err = stack.stderr().clone(); + std::thread::Builder::new() + .spawn(move || consume_child_output(stderr, &err)) + }; + + consume_child_output(stdout, stack.stdout())?; + + match err_thread?.join() { + Ok(result) => result?, + Err(err) => { + return Err(ShellError::GenericError { + error: "Error consuming external command stderr".into(), + msg: format! {"{err:?}"}, + span: Some(span), + help: None, + inner: Vec::new(), + }) + } + } + + (None, None) + } + (Ok(stdout), Err(stderr)) => { + // single output stream, we can consume directly + consume_child_output(stdout, stack.stdout())?; + (None, stderr) + } + (Err(stdout), Ok(stderr)) => { + // single output stream, we can consume directly + consume_child_output(stderr, stack.stderr())?; + (stdout, None) + } + (Err(stdout), Err(stderr)) => (stdout, stderr), + }; + + Ok(PipelineData::ExternalStream { + stdout, + stderr, + exit_code, + span, + metadata, + trim_end_newline, + }) + } + (data, IoStream::Pipe | IoStream::Capture) => Ok(data), + (PipelineData::Empty, _) => Ok(PipelineData::Empty), + (PipelineData::Value(_, _), IoStream::Null) => Ok(PipelineData::Empty), + (PipelineData::ListStream(stream, _), IoStream::Null) => { + // we need to drain the stream in case there are external commands in the pipeline + stream.drain()?; + Ok(PipelineData::Empty) + } + (PipelineData::Value(value, _), IoStream::File(file)) => { + let bytes = value_to_bytes(value)?; + let mut file = file.try_clone()?; + file.write_all(&bytes)?; + file.flush()?; + Ok(PipelineData::Empty) + } + (PipelineData::ListStream(stream, _), IoStream::File(file)) => { + let mut file = file.try_clone()?; + // use BufWriter here? + for value in stream { + let bytes = value_to_bytes(value)?; + file.write_all(&bytes)?; + file.write_all(b"\n")?; + } + file.flush()?; + Ok(PipelineData::Empty) + } + ( + data @ (PipelineData::Value(_, _) | PipelineData::ListStream(_, _)), + IoStream::Inherit, + ) => { + let config = engine_state.get_config(); + + if let Some(decl_id) = engine_state.table_decl_id { + let command = engine_state.get_decl(decl_id); + if command.get_block_id().is_some() { + data.write_all_and_flush(engine_state, config, false, false)?; + } else { + let call = Call::new(Span::unknown()); + let stack = &mut stack.start_capture(); + let table = command.run(engine_state, stack, &call, data)?; + table.write_all_and_flush(engine_state, config, false, false)?; + } + } else { + data.write_all_and_flush(engine_state, config, false, false)?; + }; + Ok(PipelineData::Empty) + } + } + } + pub fn drain(self) -> Result<(), ShellError> { match self { PipelineData::Value(Value::Error { error, .. }, _) => Err(*error), @@ -724,10 +864,8 @@ impl PipelineData { return self.write_all_and_flush(engine_state, config, no_newline, to_stderr); } - let mut call = Call::new(Span::new(0, 0)); - call.redirect_stdout = false; + let call = Call::new(Span::new(0, 0)); let table = command.run(engine_state, stack, &call, self)?; - table.write_all_and_flush(engine_state, config, no_newline, to_stderr)?; } else { self.write_all_and_flush(engine_state, config, no_newline, to_stderr)?; @@ -841,7 +979,6 @@ pub fn print_if_stream( exit_code: Option, ) -> Result { if let Some(stderr_stream) = stderr_stream { - // Write stderr to our stderr, if it's present thread::Builder::new() .name("stderr consumer".to_string()) .spawn(move || { @@ -896,6 +1033,32 @@ fn drain_exit_code(exit_code: ListStream) -> Result { } } +/// Only call this if `output_stream` is not `IoStream::Pipe` or `IoStream::Capture`. +fn consume_child_output(child_output: RawStream, output_stream: &IoStream) -> io::Result<()> { + let mut output = ReadRawStream::new(child_output); + match output_stream { + IoStream::Pipe | IoStream::Capture => { + // The point of `consume_child_output` is to redirect output *right now*, + // but IoStream::Pipe means to redirect output + // into an OS pipe for *future use* (as input for another command). + // So, this branch makes no sense, and will simply drop `output` instead of draining it. + // This could trigger a `SIGPIPE` for the external command, + // since there will be no reader for its pipe. + debug_assert!(false) + } + IoStream::Null => { + io::copy(&mut output, &mut io::sink())?; + } + IoStream::Inherit => { + io::copy(&mut output, &mut io::stdout())?; + } + IoStream::File(file) => { + io::copy(&mut output, &mut file.try_clone()?)?; + } + } + Ok(()) +} + impl Iterator for PipelineIterator { type Item = Value; @@ -978,3 +1141,61 @@ where ) } } + +fn value_to_bytes(value: Value) -> Result, ShellError> { + let bytes = match value { + Value::String { val, .. } => val.into_bytes(), + Value::Binary { val, .. } => val, + Value::List { vals, .. } => { + let val = vals + .into_iter() + .map(Value::coerce_into_string) + .collect::, ShellError>>()? + .join("\n") + + "\n"; + + val.into_bytes() + } + // Propagate errors by explicitly matching them before the final case. + Value::Error { error, .. } => return Err(*error), + value => value.coerce_into_string()?.into_bytes(), + }; + Ok(bytes) +} + +struct ReadRawStream { + iter: Box, ShellError>>>, + cursor: Option>>, +} + +impl ReadRawStream { + fn new(stream: RawStream) -> Self { + debug_assert!(stream.leftover.is_empty()); + Self { + iter: stream.stream, + cursor: Some(Cursor::new(Vec::new())), + } + } +} + +impl Read for ReadRawStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + while let Some(cursor) = self.cursor.as_mut() { + let read = cursor.read(buf)?; + if read > 0 { + return Ok(read); + } else { + match self.iter.next().transpose() { + Ok(next) => { + self.cursor = next.map(Cursor::new); + } + Err(err) => { + // temporary hack + return Err(io::Error::new(io::ErrorKind::Other, err)); + } + } + } + } + Ok(0) + } +} diff --git a/crates/nu-std/src/lib.rs b/crates/nu-std/src/lib.rs index 0ed6876e3a..c90a2cd7b1 100644 --- a/crates/nu-std/src/lib.rs +++ b/crates/nu-std/src/lib.rs @@ -94,14 +94,7 @@ use std pwd let mut stack = Stack::new(); let pipeline_data = PipelineData::Empty; - eval_block::( - engine_state, - &mut stack, - &block, - pipeline_data, - false, - false, - )?; + eval_block::(engine_state, &mut stack, &block, pipeline_data)?; let cwd = current_dir(engine_state, &stack)?; engine_state.merge_env(&mut stack, cwd)?; diff --git a/crates/nu-std/tests/logger_tests/test_basic_commands.nu b/crates/nu-std/tests/logger_tests/test_basic_commands.nu index b93b951c21..c2dcba5a71 100644 --- a/crates/nu-std/tests/logger_tests/test_basic_commands.nu +++ b/crates/nu-std/tests/logger_tests/test_basic_commands.nu @@ -5,13 +5,12 @@ def run [ message_level --short ] { - do { - if $short { - ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log ($message_level) --short "test message"' - } else { - ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log ($message_level) "test message"' - } - } | complete | get --ignore-errors stderr + if $short { + ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log ($message_level) --short "test message"' + } else { + ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log ($message_level) "test message"' + } + | complete | get --ignore-errors stderr } def "assert no message" [ diff --git a/crates/nu-std/tests/logger_tests/test_log_custom.nu b/crates/nu-std/tests/logger_tests/test_log_custom.nu index c43f45e519..626e1f50b0 100644 --- a/crates/nu-std/tests/logger_tests/test_log_custom.nu +++ b/crates/nu-std/tests/logger_tests/test_log_custom.nu @@ -10,17 +10,16 @@ def run-command [ --level-prefix: string, --ansi: string ] { - do { - if ($level_prefix | is-empty) { - if ($ansi | is-empty) { - ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log custom "($message)" "($format)" ($log_level)' - } else { - ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log custom "($message)" "($format)" ($log_level) --ansi "($ansi)"' - } + if ($level_prefix | is-empty) { + if ($ansi | is-empty) { + ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log custom "($message)" "($format)" ($log_level)' } else { - ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log custom "($message)" "($format)" ($log_level) --level-prefix "($level_prefix)" --ansi "($ansi)"' + ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log custom "($message)" "($format)" ($log_level) --ansi "($ansi)"' } - } | complete | get --ignore-errors stderr + } else { + ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log custom "($message)" "($format)" ($log_level) --level-prefix "($level_prefix)" --ansi "($ansi)"' + } + | complete | get --ignore-errors stderr } #[test] diff --git a/crates/nu-std/tests/logger_tests/test_log_format_flag.nu b/crates/nu-std/tests/logger_tests/test_log_format_flag.nu index 9727aafd4b..93dec10acc 100644 --- a/crates/nu-std/tests/logger_tests/test_log_format_flag.nu +++ b/crates/nu-std/tests/logger_tests/test_log_format_flag.nu @@ -9,13 +9,12 @@ def run-command [ --format: string, --short ] { - do { - if $short { - ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log ($message_level) --format "($format)" --short "($message)"' - } else { - ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log ($message_level) --format "($format)" "($message)"' - } - } | complete | get --ignore-errors stderr + if $short { + ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log ($message_level) --format "($format)" --short "($message)"' + } else { + ^$nu.current-exe --commands $'use std; NU_log-level=($system_level) std log ($message_level) --format "($format)" "($message)"' + } + | complete | get --ignore-errors stderr } diff --git a/src/command.rs b/src/command.rs index 9e7a5f2908..f3b4c7a5c0 100644 --- a/src/command.rs +++ b/src/command.rs @@ -3,7 +3,7 @@ use nu_parser::parse; use nu_parser::{escape_for_script_arg, escape_quote_string}; use nu_protocol::report_error; use nu_protocol::{ - ast::{Call, Expr, Expression, PipelineElement}, + ast::{Call, Expr, Expression}, engine::{Command, EngineState, Stack, StateWorkingSet}, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, @@ -82,14 +82,7 @@ pub(crate) fn parse_commandline_args( // We should have a successful parse now if let Some(pipeline) = block.pipelines.first() { - if let Some(PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - )) = pipeline.elements.first() - { + if let Some(Expr::Call(call)) = pipeline.elements.first().map(|e| &e.expr.expr) { let redirect_stdin = call.get_named_arg("stdin"); let login_shell = call.get_named_arg("login"); let interactive_shell = call.get_named_arg("interactive"); diff --git a/src/test_bins.rs b/src/test_bins.rs index 784bfbf3f1..1605b5601c 100644 --- a/src/test_bins.rs +++ b/src/test_bins.rs @@ -321,7 +321,8 @@ pub fn nu_repl() { let input = PipelineData::empty(); let config = engine_state.get_config(); - match eval_block::(&engine_state, &mut stack, &block, input, false, false) { + let stack = &mut stack.start_capture(); + match eval_block::(&engine_state, stack, &block, input) { Ok(pipeline_data) => match pipeline_data.collect_string("", config) { Ok(s) => last_output = s, Err(err) => outcome_err(&engine_state, &err), diff --git a/tests/shell/pipeline/commands/external.rs b/tests/shell/pipeline/commands/external.rs index 54e1dd639b..ea644710ff 100644 --- a/tests/shell/pipeline/commands/external.rs +++ b/tests/shell/pipeline/commands/external.rs @@ -95,8 +95,7 @@ fn single_quote_dollar_external() { #[test] fn redirects_custom_command_external() { let actual = nu!("def foo [] { nu --testbin cococo foo bar }; foo | str length"); - - assert_eq!(actual.out, "8"); + assert_eq!(actual.out, "7"); } #[test] @@ -147,8 +146,7 @@ fn command_substitution_wont_output_extra_newline() { #[test] fn basic_err_pipe_works() { let actual = nu!(r#"with-env [FOO "bar"] { nu --testbin echo_env_stderr FOO e>| str length }"#); - // there is a `newline` output from nu --testbin - assert_eq!(actual.out, "4"); + assert_eq!(actual.out, "3"); } #[test] @@ -156,16 +154,14 @@ fn basic_outerr_pipe_works() { let actual = nu!( r#"with-env [FOO "bar"] { nu --testbin echo_env_mixed out-err FOO FOO o+e>| str length }"# ); - // there is a `newline` output from nu --testbin - assert_eq!(actual.out, "8"); + assert_eq!(actual.out, "7"); } #[test] fn err_pipe_with_failed_external_works() { let actual = nu!(r#"with-env [FOO "bar"] { nu --testbin echo_env_stderr_fail FOO e>| str length }"#); - // there is a `newline` output from nu --testbin - assert_eq!(actual.out, "4"); + assert_eq!(actual.out, "3"); } #[test]