From 74a73f983879b42295a686d32958fbd035262ed9 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 23 Nov 2022 07:26:13 +1300 Subject: [PATCH] Stdout/Stderr redirection (#7185) This adds new pipeline connectors called out> and err> which redirect either stdout or stderr to a file. You can also use out+err> (or err+out>) to redirect both streams into a file. --- crates/nu-cli/src/completions/completer.rs | 8 +- crates/nu-cli/src/syntax_highlight.rs | 12 +- crates/nu-color-config/src/shape_color.rs | 4 + crates/nu-command/src/formats/from/nuon.rs | 8 +- crates/nu-command/tests/commands/mod.rs | 1 + .../nu-command/tests/commands/redirection.rs | 66 +++ crates/nu-engine/src/eval.rs | 125 ++++- crates/nu-parser/src/flatten.rs | 36 +- crates/nu-parser/src/lex.rs | 65 ++- crates/nu-parser/src/parse_keywords.rs | 65 ++- crates/nu-parser/src/parser.rs | 253 +++++++--- crates/nu-parser/tests/test_parser.rs | 477 ++++++++++-------- crates/nu-protocol/src/ast/pipeline.rs | 57 ++- crates/nu-protocol/src/value/stream.rs | 14 + src/main.rs | 11 +- 15 files changed, 856 insertions(+), 346 deletions(-) create mode 100644 crates/nu-command/tests/commands/redirection.rs diff --git a/crates/nu-cli/src/completions/completer.rs b/crates/nu-cli/src/completions/completer.rs index 5acd89fefb..a2c6664550 100644 --- a/crates/nu-cli/src/completions/completer.rs +++ b/crates/nu-cli/src/completions/completer.rs @@ -134,10 +134,10 @@ impl NuCompleter { for pipeline in output.pipelines.into_iter() { for pipeline_element in pipeline.elements { match pipeline_element { - PipelineElement::Expression(expr) - | PipelineElement::Redirect(expr) - | PipelineElement::And(expr) - | PipelineElement::Or(expr) => { + PipelineElement::Expression(_, expr) + | PipelineElement::Redirection(_, _, expr) + | PipelineElement::And(_, expr) + | PipelineElement::Or(_, expr) => { let flattened: Vec<_> = flatten_expression(&working_set, &expr); let span_offset: usize = alias_offset.iter().sum(); let mut spans: Vec = vec![]; diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index bce804ddb2..1714ad3006 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -120,6 +120,10 @@ impl Highlighter for NuHighlighter { FlatShape::GlobPattern => add_colored_token!(shape.1, next_token), FlatShape::Variable => add_colored_token!(shape.1, next_token), FlatShape::Flag => add_colored_token!(shape.1, next_token), + FlatShape::Pipe => add_colored_token!(shape.1, next_token), + FlatShape::And => add_colored_token!(shape.1, next_token), + FlatShape::Or => add_colored_token!(shape.1, next_token), + FlatShape::Redirection => add_colored_token!(shape.1, next_token), FlatShape::Custom(..) => add_colored_token!(shape.1, next_token), } last_seen_span = shape.0.end; @@ -232,10 +236,10 @@ fn find_matching_block_end_in_block( for p in &block.pipelines { for e in &p.elements { match e { - PipelineElement::Expression(e) - | PipelineElement::Redirect(e) - | PipelineElement::And(e) - | PipelineElement::Or(e) => { + PipelineElement::Expression(_, e) + | PipelineElement::Redirection(_, _, e) + | PipelineElement::And(_, e) + | PipelineElement::Or(_, e) => { if e.span.contains(global_cursor_offset) { if let Some(pos) = find_matching_block_end_in_expr( line, diff --git a/crates/nu-color-config/src/shape_color.rs b/crates/nu-color-config/src/shape_color.rs index 94e4565d1e..7fdbd867c6 100644 --- a/crates/nu-color-config/src/shape_color.rs +++ b/crates/nu-color-config/src/shape_color.rs @@ -34,6 +34,10 @@ pub fn get_shape_color(shape: String, conf: &Config) -> Style { "shape_variable" => Style::new().fg(Color::Purple), "shape_flag" => Style::new().fg(Color::Blue).bold(), "shape_custom" => Style::new().fg(Color::Green), + "shape_pipe" => Style::new().fg(Color::Purple).bold(), + "shape_redirection" => Style::new().fg(Color::Purple).bold(), + "shape_and" => Style::new().fg(Color::Purple).bold(), + "shape_or" => Style::new().fg(Color::Purple).bold(), "shape_nothing" => Style::new().fg(Color::LightCyan), _ => Style::default(), }, diff --git a/crates/nu-command/src/formats/from/nuon.rs b/crates/nu-command/src/formats/from/nuon.rs index bd6e12dccb..759043915a 100644 --- a/crates/nu-command/src/formats/from/nuon.rs +++ b/crates/nu-command/src/formats/from/nuon.rs @@ -156,10 +156,10 @@ impl Command for FromNuon { } } else { match pipeline.elements.remove(0) { - PipelineElement::Expression(expression) - | PipelineElement::Redirect(expression) - | PipelineElement::And(expression) - | PipelineElement::Or(expression) => expression, + PipelineElement::Expression(_, expression) + | PipelineElement::Redirection(_, _, expression) + | PipelineElement::And(_, expression) + | PipelineElement::Or(_, expression) => expression, } } }; diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index 30e684dfe2..7353b60761 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -57,6 +57,7 @@ mod print; mod query; mod random; mod range; +mod redirection; mod reduce; mod reject; mod rename; diff --git a/crates/nu-command/tests/commands/redirection.rs b/crates/nu-command/tests/commands/redirection.rs new file mode 100644 index 0000000000..4d1e3db37a --- /dev/null +++ b/crates/nu-command/tests/commands/redirection.rs @@ -0,0 +1,66 @@ +use nu_test_support::nu; +use nu_test_support::playground::Playground; + +#[cfg(not(windows))] +#[test] +fn redirect_err() { + Playground::setup("redirect_err_test", |dirs, _sandbox| { + let output = nu!( + cwd: dirs.test(), + "cat asdfasdfasdf.txt err> a; cat a" + ); + + assert!(output.err.contains("asdfasdfasdf.txt")); + }) +} + +#[cfg(windows)] +#[test] +fn redirect_err() { + Playground::setup("redirect_err_test", |dirs, _sandbox| { + let output = nu!( + cwd: dirs.test(), + "type asdfasdfasdf.txt err> a; type a" + ); + + assert!(output.err.contains("asdfasdfasdf.txt")); + }) +} + +#[cfg(not(windows))] +#[test] +fn redirect_outerr() { + Playground::setup("redirect_outerr_test", |dirs, _sandbox| { + let output = nu!( + cwd: dirs.test(), + "cat asdfasdfasdf.txt out+err> a; cat a" + ); + + assert!(output.err.contains("asdfasdfasdf.txt")); + }) +} + +#[cfg(windows)] +#[test] +fn redirect_outerr() { + Playground::setup("redirect_outerr_test", |dirs, _sandbox| { + let output = nu!( + cwd: dirs.test(), + "type asdfasdfasdf.txt out+err> a; type a" + ); + + assert!(output.err.contains("asdfasdfasdf.txt")); + }) +} + +#[test] +fn redirect_out() { + Playground::setup("redirect_out_test", |dirs, _sandbox| { + let output = nu!( + cwd: dirs.test(), + "echo 'hello' out> a; open a" + ); + + assert!(output.out.contains("hello")); + }) +} diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 87c8ff3020..821e64b97b 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -2,8 +2,8 @@ use crate::{current_dir_str, get_full_help, scope::create_scope}; use nu_path::expand_path_with; use nu_protocol::{ ast::{ - Assignment, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math, Operator, - PathMember, PipelineElement, + Argument, Assignment, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math, + Operator, PathMember, PipelineElement, Redirection, }, engine::{EngineState, Stack}, Config, HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, ListStream, @@ -801,7 +801,7 @@ pub fn eval_element_with_input( redirect_stderr: bool, ) -> Result<(PipelineData, bool), ShellError> { match element { - PipelineElement::Expression(expr) => eval_expression_with_input( + PipelineElement::Expression(_, expr) => eval_expression_with_input( engine_state, stack, expr, @@ -809,7 +809,98 @@ pub fn eval_element_with_input( redirect_stdout, redirect_stderr, ), - PipelineElement::Redirect(expr) => eval_expression_with_input( + PipelineElement::Redirection(span, redirection, expr) => match &expr.expr { + Expr::String(_) => { + let input = match (redirection, input) { + ( + Redirection::Stderr, + PipelineData::ExternalStream { + stderr, + exit_code, + span, + metadata, + .. + }, + ) => PipelineData::ExternalStream { + stdout: stderr, + stderr: None, + exit_code, + span, + metadata, + }, + ( + Redirection::StdoutAndStderr, + PipelineData::ExternalStream { + stdout, + stderr, + exit_code, + span, + metadata, + }, + ) => match (stdout, stderr) { + (Some(stdout), Some(stderr)) => PipelineData::ExternalStream { + stdout: Some(stdout.chain(stderr)), + stderr: None, + exit_code, + span, + metadata, + }, + (None, Some(stderr)) => PipelineData::ExternalStream { + stdout: Some(stderr), + stderr: None, + exit_code, + span, + metadata, + }, + (Some(stdout), None) => PipelineData::ExternalStream { + stdout: Some(stdout), + stderr: None, + exit_code, + span, + metadata, + }, + (None, None) => PipelineData::ExternalStream { + stdout: None, + stderr: None, + exit_code, + span, + metadata, + }, + }, + (_, input) => input, + }; + + if let Some(save_command) = engine_state.find_decl(b"save", &[]) { + eval_call( + engine_state, + stack, + &Call { + decl_id: save_command, + head: *span, + arguments: vec![ + Argument::Positional(expr.clone()), + Argument::Named(( + Spanned { + item: "--raw".into(), + span: *span, + }, + None, + None, + )), + ], + redirect_stdout: false, + redirect_stderr: false, + }, + input, + ) + .map(|x| (x, false)) + } else { + Err(ShellError::CommandNotFound(*span)) + } + } + _ => Err(ShellError::CommandNotFound(*span)), + }, + PipelineElement::And(_, expr) => eval_expression_with_input( engine_state, stack, expr, @@ -817,15 +908,7 @@ pub fn eval_element_with_input( redirect_stdout, redirect_stderr, ), - PipelineElement::And(expr) => eval_expression_with_input( - engine_state, - stack, - expr, - input, - redirect_stdout, - redirect_stderr, - ), - PipelineElement::Or(expr) => eval_expression_with_input( + PipelineElement::Or(_, expr) => eval_expression_with_input( engine_state, stack, expr, @@ -846,15 +929,23 @@ pub fn eval_block( ) -> Result { let num_pipelines = block.len(); for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() { - for (i, elem) in pipeline.elements.iter().enumerate() { + let mut i = 0; + + while i < pipeline.elements.len() { // if eval internal command failed, it can just make early return with `Err(ShellError)`. let eval_result = eval_element_with_input( engine_state, stack, - elem, + &pipeline.elements[i], input, redirect_stdout || (i != pipeline.elements.len() - 1), - redirect_stderr, + redirect_stderr + || ((i < pipeline.elements.len() - 1) + && (matches!( + pipeline.elements[i + 1], + PipelineElement::Redirection(_, Redirection::Stderr, _) + | PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _) + ))), )?; input = eval_result.0; // external command may runs to failed @@ -863,6 +954,8 @@ pub fn eval_block( if eval_result.1 { return Ok(input); } + + i += 1; } if pipeline_idx < (num_pipelines) - 1 { diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index e9a197aa16..f5a655d77a 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -32,6 +32,10 @@ pub enum FlatShape { GlobPattern, Variable, Flag, + Pipe, + And, + Or, + Redirection, Custom(DeclId), } @@ -63,6 +67,10 @@ impl Display for FlatShape { FlatShape::GlobPattern => write!(f, "shape_globpattern"), FlatShape::Variable => write!(f, "shape_variable"), FlatShape::Flag => write!(f, "shape_flag"), + FlatShape::Pipe => write!(f, "shape_pipe"), + FlatShape::And => write!(f, "shape_and"), + FlatShape::Or => write!(f, "shape_or"), + FlatShape::Redirection => write!(f, "shape_redirection"), FlatShape::Custom(_) => write!(f, "shape_custom"), } } @@ -502,10 +510,30 @@ pub fn flatten_pipeline_element( pipeline_element: &PipelineElement, ) -> Vec<(Span, FlatShape)> { match pipeline_element { - PipelineElement::Expression(expr) - | PipelineElement::Redirect(expr) - | PipelineElement::And(expr) - | PipelineElement::Or(expr) => flatten_expression(working_set, expr), + PipelineElement::Expression(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) + } + } + PipelineElement::Redirection(span, _, expr) => { + let mut output = vec![(*span, FlatShape::Redirection)]; + output.append(&mut flatten_expression(working_set, 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 + } } } diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index c4fa1612ee..ed7b6a6e51 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -1,12 +1,15 @@ use crate::ParseError; use nu_protocol::Span; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum TokenContents { Item, Comment, Pipe, Semicolon, + OutGreaterThan, + ErrGreaterThan, + OutErrGreaterThan, Eol, } @@ -74,7 +77,7 @@ pub fn lex_item( span_offset: usize, additional_whitespace: &[u8], special_tokens: &[u8], -) -> (Span, Option) { +) -> (Token, Option) { // This variable tracks the starting character of a string literal, so that // we remain inside the string literal lexer mode until we encounter the // closing quote. @@ -113,7 +116,10 @@ pub fn lex_item( let span = Span::new(span_offset + token_start, span_offset + *curr_offset); return ( - span, + Token { + contents: TokenContents::Item, + span, + }, Some(ParseError::UnexpectedEof( (start as char).to_string(), Span { @@ -195,7 +201,13 @@ pub fn lex_item( }, ); - return (span, Some(cause)); + return ( + Token { + contents: TokenContents::Item, + span, + }, + Some(cause), + ); } if let Some(delim) = quote_start { @@ -203,7 +215,10 @@ pub fn lex_item( // anyone wanting to consume this partial parse (e.g., completions) will be able to get // correct information from the non-lite parse. return ( - span, + Token { + contents: TokenContents::Item, + span, + }, Some(ParseError::UnexpectedEof( (delim as char).to_string(), Span { @@ -217,12 +232,44 @@ pub fn lex_item( // If we didn't accumulate any characters, it's an unexpected error. if *curr_offset - token_start == 0 { return ( - span, + Token { + contents: TokenContents::Item, + span, + }, Some(ParseError::UnexpectedEof("command".to_string(), span)), ); } - (span, None) + match &input[(span.start - span_offset)..(span.end - span_offset)] { + b"out>" => ( + Token { + contents: TokenContents::OutGreaterThan, + span, + }, + None, + ), + b"err>" => ( + Token { + contents: TokenContents::ErrGreaterThan, + span, + }, + None, + ), + b"out+err>" | b"err+out>" => ( + Token { + contents: TokenContents::OutErrGreaterThan, + span, + }, + None, + ), + _ => ( + Token { + contents: TokenContents::Item, + span, + }, + None, + ), + } } pub fn lex( @@ -347,7 +394,7 @@ pub fn lex( } else { // Otherwise, try to consume an unclassified token. - let (span, err) = lex_item( + let (token, err) = lex_item( input, &mut curr_offset, span_offset, @@ -358,7 +405,7 @@ pub fn lex( error = err; } is_complete = true; - output.push(Token::new(TokenContents::Item, span)); + output.push(token); } } (output, error) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index e1fc5d3424..2e52326037 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -865,10 +865,13 @@ 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.get(0) + if let Some(PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(ref def_call), + .. + }, + )) = pipeline.elements.get(0) { call = def_call.clone(); @@ -930,10 +933,13 @@ 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.get(0) + if let Some(PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(ref def_call), + .. + }, + )) = pipeline.elements.get(0) { call = def_call.clone(); @@ -996,10 +1002,13 @@ 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.get(0) + if let Some(PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(ref def_call), + .. + }, + )) = pipeline.elements.get(0) { call = def_call.clone(); @@ -1062,10 +1071,13 @@ 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.get(0) + if let Some(PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(ref alias_call), + .. + }, + )) = pipeline.elements.get(0) { call = alias_call.clone(); @@ -1128,10 +1140,13 @@ 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.get(0) + if let Some(PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(ref use_call), + .. + }, + )) = pipeline.elements.get(0) { call = use_call.clone(); @@ -1313,7 +1328,7 @@ pub fn parse_module_block( for pipeline in &output.block { if pipeline.commands.len() == 1 { - if let LiteElement::Command(command) = &pipeline.commands[0] { + if let LiteElement::Command(_, command) = &pipeline.commands[0] { parse_def_predecl(working_set, &command.parts, expand_aliases_denylist); } } @@ -1327,7 +1342,7 @@ pub fn parse_module_block( .map(|pipeline| { if pipeline.commands.len() == 1 { match &pipeline.commands[0] { - LiteElement::Command(command) => { + LiteElement::Command(_, command) => { let name = working_set.get_span_contents(command.parts[0]); let (pipeline, err) = match name { @@ -1407,9 +1422,9 @@ pub fn parse_module_block( pipeline } - LiteElement::Or(command) - | LiteElement::And(command) - | LiteElement::Redirection(command) => (garbage_pipeline(&command.parts)), + LiteElement::Or(_, command) + | LiteElement::And(_, command) + | LiteElement::Redirection(_, _, command) => (garbage_pipeline(&command.parts)), } } else { error = Some(ParseError::Expected("not a pipeline".into(), span)); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 9b38a1aa20..718541a2de 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -8,7 +8,7 @@ use nu_protocol::{ ast::{ Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression, FullCellPath, ImportPattern, ImportPatternHead, ImportPatternMember, Math, Operator, - PathMember, Pipeline, PipelineElement, RangeInclusion, RangeOperator, + PathMember, Pipeline, PipelineElement, RangeInclusion, RangeOperator, Redirection, }, engine::StateWorkingSet, span, BlockId, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId, @@ -1056,7 +1056,7 @@ pub fn parse_call( let result = result.elements.remove(0); // If this is the first element in a pipeline, we know it has to be an expression - if let PipelineElement::Expression(mut result) = result { + if let PipelineElement::Expression(_, mut result) = result { result.replace_span(working_set, expansion_span, orig_span); return (result, err); @@ -1203,7 +1203,12 @@ fn parse_binary_with_base( Some(ParseError::Expected("binary".into(), span)), ); } - TokenContents::Comment | TokenContents::Semicolon | TokenContents::Eol => {} + TokenContents::Comment + | TokenContents::Semicolon + | TokenContents::Eol + | TokenContents::OutGreaterThan + | TokenContents::ErrGreaterThan + | TokenContents::OutErrGreaterThan => {} } } @@ -1924,7 +1929,7 @@ pub fn parse_full_cell_path( .last() .and_then(|Pipeline { elements, .. }| elements.last()) .map(|element| match element { - PipelineElement::Expression(expr) + PipelineElement::Expression(_, expr) if matches!( expr, Expression { @@ -3071,7 +3076,7 @@ pub fn parse_row_condition( let mut pipeline = Pipeline::new(); pipeline .elements - .push(PipelineElement::Expression(expression)); + .push(PipelineElement::Expression(None, expression)); block.pipelines.push(pipeline); @@ -3721,7 +3726,7 @@ pub fn parse_list_expression( for arg in &output.block[0].commands { let mut spans_idx = 0; - if let LiteElement::Command(command) = arg { + if let LiteElement::Command(_, command) = arg { while spans_idx < command.parts.len() { let (arg, err) = parse_multispan_value( working_set, @@ -3814,10 +3819,10 @@ pub fn parse_table_expression( } _ => { match &output.block[0].commands[0] { - LiteElement::Command(command) - | LiteElement::Redirection(command) - | LiteElement::And(command) - | LiteElement::Or(command) => { + LiteElement::Command(_, command) + | LiteElement::Redirection(_, _, command) + | LiteElement::And(_, command) + | LiteElement::Or(_, command) => { let mut table_headers = vec![]; let (headers, err) = parse_value( @@ -3837,10 +3842,10 @@ pub fn parse_table_expression( } match &output.block[1].commands[0] { - LiteElement::Command(command) - | LiteElement::Redirection(command) - | LiteElement::And(command) - | LiteElement::Or(command) => { + LiteElement::Command(_, command) + | LiteElement::Redirection(_, _, command) + | LiteElement::And(_, command) + | LiteElement::Or(_, command) => { let mut rows = vec![]; for part in &command.parts { let (values, err) = parse_value( @@ -5184,10 +5189,10 @@ pub fn parse_block( for pipeline in &lite_block.block { if pipeline.commands.len() == 1 { match &pipeline.commands[0] { - LiteElement::Command(command) - | LiteElement::Redirection(command) - | LiteElement::And(command) - | LiteElement::Or(command) => { + LiteElement::Command(_, command) + | LiteElement::Redirection(_, _, command) + | LiteElement::And(_, command) + | LiteElement::Or(_, command) => { if let Some(err) = parse_def_predecl(working_set, &command.parts, expand_aliases_denylist) { @@ -5208,7 +5213,7 @@ pub fn parse_block( .commands .iter() .map(|command| match command { - LiteElement::Command(command) => { + LiteElement::Command(span, command) => { let (expr, err) = parse_expression( working_set, &command.parts, @@ -5221,12 +5226,12 @@ pub fn parse_block( error = err; } - PipelineElement::Expression(expr) + PipelineElement::Expression(*span, expr) } - LiteElement::Redirection(command) => { - let (expr, err) = parse_expression( + LiteElement::Redirection(span, redirection, command) => { + let (expr, err) = parse_string( working_set, - &command.parts, + command.parts[0], expand_aliases_denylist, ); @@ -5236,9 +5241,9 @@ pub fn parse_block( error = err; } - PipelineElement::Redirect(expr) + PipelineElement::Redirection(*span, redirection.clone(), expr) } - LiteElement::And(command) => { + LiteElement::And(span, command) => { let (expr, err) = parse_expression( working_set, &command.parts, @@ -5251,9 +5256,9 @@ pub fn parse_block( error = err; } - PipelineElement::And(expr) + PipelineElement::And(*span, expr) } - LiteElement::Or(command) => { + LiteElement::Or(span, command) => { let (expr, err) = parse_expression( working_set, &command.parts, @@ -5266,7 +5271,7 @@ pub fn parse_block( error = err; } - PipelineElement::Or(expr) + PipelineElement::Or(*span, expr) } }) .collect::>(); @@ -5288,10 +5293,10 @@ pub fn parse_block( Pipeline { elements: output } } else { match &pipeline.commands[0] { - LiteElement::Command(command) - | LiteElement::Redirection(command) - | LiteElement::And(command) - | LiteElement::Or(command) => { + LiteElement::Command(_, command) + | LiteElement::Redirection(_, _, command) + | LiteElement::And(_, command) + | LiteElement::Or(_, command) => { let (mut pipeline, err) = parse_builtin_commands(working_set, command, expand_aliases_denylist); @@ -5301,10 +5306,13 @@ pub fn parse_block( working_set.find_decl(b"let-env", &Type::Any) { for element in pipeline.elements.iter_mut() { - if let PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) = element + if let PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) = element { if call.decl_id == let_decl_id || call.decl_id == let_env_decl_id @@ -5421,10 +5429,10 @@ pub fn discover_captures_in_pipeline_element( seen_blocks: &mut HashMap>, ) -> Result, ParseError> { match element { - PipelineElement::Expression(expression) - | PipelineElement::Redirect(expression) - | PipelineElement::And(expression) - | PipelineElement::Or(expression) => { + PipelineElement::Expression(_, expression) + | PipelineElement::Redirection(_, _, expression) + | PipelineElement::And(_, expression) + | PipelineElement::Or(_, expression) => { discover_captures_in_expr(working_set, expression, seen, seen_blocks) } } @@ -5674,17 +5682,21 @@ fn wrap_element_with_collect( element: &PipelineElement, ) -> PipelineElement { match element { - PipelineElement::Expression(expression) => { - PipelineElement::Expression(wrap_expr_with_collect(working_set, expression)) + PipelineElement::Expression(span, expression) => { + PipelineElement::Expression(*span, wrap_expr_with_collect(working_set, expression)) } - PipelineElement::Redirect(expression) => { - PipelineElement::Redirect(wrap_expr_with_collect(working_set, expression)) + PipelineElement::Redirection(span, redirection, expression) => { + PipelineElement::Redirection( + *span, + redirection.clone(), + wrap_expr_with_collect(working_set, expression), + ) } - PipelineElement::And(expression) => { - PipelineElement::And(wrap_expr_with_collect(working_set, expression)) + PipelineElement::And(span, expression) => { + PipelineElement::And(*span, wrap_expr_with_collect(working_set, expression)) } - PipelineElement::Or(expression) => { - PipelineElement::Or(wrap_expr_with_collect(working_set, expression)) + PipelineElement::Or(span, expression) => { + PipelineElement::Or(*span, wrap_expr_with_collect(working_set, expression)) } } } @@ -5781,12 +5793,13 @@ impl LiteCommand { } } +// Note: the Span is the span of the connector not the whole element #[derive(Debug)] pub enum LiteElement { - Command(LiteCommand), - Redirection(LiteCommand), - And(LiteCommand), - Or(LiteCommand), + Command(Option, LiteCommand), + Redirection(Span, Redirection, LiteCommand), + And(Span, LiteCommand), + Or(Span, LiteCommand), } #[derive(Debug)] @@ -5846,6 +5859,13 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { 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); + } + let mut curr_comment: Option> = None; for token in tokens.iter() { @@ -5858,17 +5878,96 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { curr_command.push(token.span); last_token = TokenContents::Item; } + TokenContents::OutGreaterThan + | TokenContents::ErrGreaterThan + | TokenContents::OutErrGreaterThan => { + if !curr_command.is_empty() { + match last_connector { + TokenContents::OutGreaterThan => { + curr_pipeline.push(LiteElement::Redirection( + token.span, + Redirection::Stdout, + curr_command, + )); + } + TokenContents::ErrGreaterThan => { + curr_pipeline.push(LiteElement::Redirection( + token.span, + Redirection::Stderr, + curr_command, + )); + } + TokenContents::OutErrGreaterThan => { + curr_pipeline.push(LiteElement::Redirection( + token.span, + Redirection::StdoutAndStderr, + curr_command, + )); + } + _ => { + curr_pipeline + .push(LiteElement::Command(last_connector_span, curr_command)); + } + } + curr_command = LiteCommand::new(); + } + last_token = token.contents; + last_connector = token.contents; + last_connector_span = Some(token.span); + } TokenContents::Pipe => { if !curr_command.is_empty() { - curr_pipeline.push(LiteElement::Command(curr_command)); + match last_connector { + TokenContents::OutGreaterThan => { + curr_pipeline.push(LiteElement::Redirection( + token.span, + Redirection::Stdout, + curr_command, + )); + } + TokenContents::ErrGreaterThan => { + curr_pipeline.push(LiteElement::Redirection( + token.span, + Redirection::Stderr, + curr_command, + )); + } + TokenContents::OutErrGreaterThan => { + curr_pipeline.push(LiteElement::Redirection( + token.span, + Redirection::StdoutAndStderr, + curr_command, + )); + } + _ => { + curr_pipeline + .push(LiteElement::Command(last_connector_span, curr_command)); + } + } curr_command = LiteCommand::new(); } last_token = TokenContents::Pipe; + last_connector = TokenContents::Pipe; + last_connector_span = Some(token.span); } TokenContents::Eol => { - if last_token != TokenContents::Pipe { + if last_token != TokenContents::Pipe && last_token != TokenContents::OutGreaterThan + { if !curr_command.is_empty() { - curr_pipeline.push(LiteElement::Command(curr_command)); + if last_connector == TokenContents::OutGreaterThan + || last_connector == TokenContents::ErrGreaterThan + || last_connector == TokenContents::OutErrGreaterThan + { + curr_pipeline.push(LiteElement::Redirection( + last_connector_span + .expect("internal error: redirection missing span information"), + Redirection::Stdout, + curr_command, + )); + } else { + curr_pipeline + .push(LiteElement::Command(last_connector_span, curr_command)); + } curr_command = LiteCommand::new(); } @@ -5889,7 +5988,19 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { } TokenContents::Semicolon => { if !curr_command.is_empty() { - curr_pipeline.push(LiteElement::Command(curr_command)); + if last_connector == TokenContents::OutGreaterThan + || last_connector == TokenContents::ErrGreaterThan + || last_connector == TokenContents::OutErrGreaterThan + { + curr_pipeline.push(LiteElement::Redirection( + last_connector_span + .expect("internal error: redirection missing span information"), + Redirection::Stdout, + curr_command, + )); + } else { + curr_pipeline.push(LiteElement::Command(last_connector_span, curr_command)); + } curr_command = LiteCommand::new(); } @@ -5922,7 +6033,35 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { } if !curr_command.is_empty() { - curr_pipeline.push(LiteElement::Command(curr_command)); + match last_connector { + TokenContents::OutGreaterThan => { + curr_pipeline.push(LiteElement::Redirection( + last_connector_span + .expect("internal error: redirection missing span information"), + Redirection::Stdout, + curr_command, + )); + } + TokenContents::ErrGreaterThan => { + curr_pipeline.push(LiteElement::Redirection( + last_connector_span + .expect("internal error: redirection missing span information"), + Redirection::Stderr, + curr_command, + )); + } + TokenContents::OutErrGreaterThan => { + curr_pipeline.push(LiteElement::Redirection( + last_connector_span + .expect("internal error: redirection missing span information"), + Redirection::StdoutAndStderr, + curr_command, + )); + } + _ => { + curr_pipeline.push(LiteElement::Command(last_connector_span, curr_command)); + } + } } if !curr_pipeline.is_empty() { diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index d67c246b08..d59da00990 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -54,10 +54,13 @@ pub fn parse_int() { assert!(expressions.len() == 1); assert!(matches!( expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::Int(3), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::Int(3), + .. + } + ) )) } @@ -72,7 +75,7 @@ pub fn parse_binary_with_hex_format() { assert!(block.len() == 1); let expressions = &block[0]; assert!(expressions.len() == 1); - if let PipelineElement::Expression(expr) = &expressions[0] { + if let PipelineElement::Expression(_, expr) = &expressions[0] { assert_eq!(expr.expr, Expr::Binary(vec![0x13])) } else { panic!("Not an expression") @@ -90,7 +93,7 @@ pub fn parse_binary_with_incomplete_hex_format() { assert!(block.len() == 1); let expressions = &block[0]; assert!(expressions.len() == 1); - if let PipelineElement::Expression(expr) = &expressions[0] { + if let PipelineElement::Expression(_, expr) = &expressions[0] { assert_eq!(expr.expr, Expr::Binary(vec![0x03])) } else { panic!("Not an expression") @@ -108,7 +111,7 @@ pub fn parse_binary_with_binary_format() { assert!(block.len() == 1); let expressions = &block[0]; assert!(expressions.len() == 1); - if let PipelineElement::Expression(expr) = &expressions[0] { + if let PipelineElement::Expression(_, expr) = &expressions[0] { assert_eq!(expr.expr, Expr::Binary(vec![0b10101000])) } else { panic!("Not an expression") @@ -126,7 +129,7 @@ pub fn parse_binary_with_incomplete_binary_format() { assert!(block.len() == 1); let expressions = &block[0]; assert!(expressions.len() == 1); - if let PipelineElement::Expression(expr) = &expressions[0] { + if let PipelineElement::Expression(_, expr) = &expressions[0] { assert_eq!(expr.expr, Expr::Binary(vec![0b00000010])) } else { panic!("Not an expression") @@ -144,7 +147,7 @@ pub fn parse_binary_with_octal_format() { assert!(block.len() == 1); let expressions = &block[0]; assert!(expressions.len() == 1); - if let PipelineElement::Expression(expr) = &expressions[0] { + if let PipelineElement::Expression(_, expr) = &expressions[0] { assert_eq!(expr.expr, Expr::Binary(vec![0o250])) } else { panic!("Not an expression") @@ -162,7 +165,7 @@ pub fn parse_binary_with_incomplete_octal_format() { assert!(block.len() == 1); let expressions = &block[0]; assert!(expressions.len() == 1); - if let PipelineElement::Expression(expr) = &expressions[0] { + if let PipelineElement::Expression(_, expr) = &expressions[0] { assert_eq!(expr.expr, Expr::Binary(vec![0o2])) } else { panic!("Not an expression") @@ -180,7 +183,7 @@ pub fn parse_binary_with_invalid_octal_format() { assert!(block.len() == 1); let expressions = &block[0]; assert!(expressions.len() == 1); - if let PipelineElement::Expression(expr) = &expressions[0] { + if let PipelineElement::Expression(_, expr) = &expressions[0] { assert!(!matches!(&expr.expr, Expr::Binary(_))) } else { panic!("Not an expression") @@ -200,7 +203,7 @@ pub fn parse_binary_with_multi_byte_char() { assert!(block.len() == 1); let expressions = &block[0]; assert!(expressions.len() == 1); - if let PipelineElement::Expression(expr) = &expressions[0] { + if let PipelineElement::Expression(_, expr) = &expressions[0] { assert!(!matches!(&expr.expr, Expr::Binary(_))) } else { panic!("Not an expression") @@ -218,7 +221,7 @@ pub fn parse_string() { assert!(block.len() == 1); let expressions = &block[0]; assert!(expressions.len() == 1); - if let PipelineElement::Expression(expr) = &expressions[0] { + if let PipelineElement::Expression(_, expr) = &expressions[0] { assert_eq!(expr.expr, Expr::String("hello nushell".to_string())) } else { panic!("Not an expression") @@ -242,7 +245,7 @@ pub fn parse_escaped_string() { assert!(block.len() == 1); let expressions = &block[0]; assert!(expressions.len() == 1); - if let PipelineElement::Expression(expr) = &expressions[0] { + if let PipelineElement::Expression(_, expr) = &expressions[0] { assert_eq!(expr.expr, Expr::String("hello nushell".to_string())) } else { panic!("Not an expression") @@ -265,10 +268,13 @@ pub fn parse_call() { let expressions = &block[0]; assert_eq!(expressions.len(), 1); - if let PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) = &expressions[0] + if let PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) = &expressions[0] { assert_eq!(call.decl_id, 0); } @@ -371,10 +377,13 @@ fn test_nothing_comparisson_eq() { assert!(expressions.len() == 1); assert!(matches!( &expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::BinaryOp(..), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::BinaryOp(..), + .. + } + ) )) } @@ -391,10 +400,13 @@ fn test_nothing_comparisson_neq() { assert!(expressions.len() == 1); assert!(matches!( &expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::BinaryOp(..), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::BinaryOp(..), + .. + } + ) )) } @@ -416,18 +428,21 @@ mod range { assert!(expressions.len() == 1); assert!(matches!( expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::Range( - Some(_), - None, - Some(_), - RangeOperator { - inclusion: RangeInclusion::Inclusive, - .. - } - ), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::Range( + Some(_), + None, + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + ) )) } @@ -445,18 +460,21 @@ mod range { assert!(expressions.len() == 1); assert!(matches!( expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::Range( - Some(_), - None, - Some(_), - RangeOperator { - inclusion: RangeInclusion::RightExclusive, - .. - } - ), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::Range( + Some(_), + None, + Some(_), + RangeOperator { + inclusion: RangeInclusion::RightExclusive, + .. + } + ), + .. + } + ) )) } @@ -474,18 +492,21 @@ mod range { assert!(expressions.len() == 1); assert!(matches!( expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::Range( - Some(_), - None, - Some(_), - RangeOperator { - inclusion: RangeInclusion::Inclusive, - .. - } - ), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::Range( + Some(_), + None, + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + ) )) } @@ -503,18 +524,21 @@ mod range { assert!(expressions.len() == 1); assert!(matches!( expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::Range( - Some(_), - None, - Some(_), - RangeOperator { - inclusion: RangeInclusion::RightExclusive, - .. - } - ), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::Range( + Some(_), + None, + Some(_), + RangeOperator { + inclusion: RangeInclusion::RightExclusive, + .. + } + ), + .. + } + ) )) } @@ -534,18 +558,21 @@ mod range { assert!(expressions.len() == 1); assert!(matches!( expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::Range( - Some(_), - None, - Some(_), - RangeOperator { - inclusion: RangeInclusion::Inclusive, - .. - } - ), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::Range( + Some(_), + None, + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + ) )) } @@ -571,18 +598,21 @@ mod range { assert!(expressions.len() == 1); assert!(matches!( expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::Range( - Some(_), - None, - Some(_), - RangeOperator { - inclusion: RangeInclusion::RightExclusive, - .. - } - ), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::Range( + Some(_), + None, + Some(_), + RangeOperator { + inclusion: RangeInclusion::RightExclusive, + .. + } + ), + .. + } + ) )) } @@ -600,18 +630,21 @@ mod range { assert!(expressions.len() == 1); assert!(matches!( expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::Range( - Some(_), - None, - None, - RangeOperator { - inclusion: RangeInclusion::Inclusive, - .. - } - ), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::Range( + Some(_), + None, + None, + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + ) )) } @@ -629,18 +662,21 @@ mod range { assert!(expressions.len() == 1); assert!(matches!( expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::Range( - None, - None, - Some(_), - RangeOperator { - inclusion: RangeInclusion::Inclusive, - .. - } - ), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::Range( + None, + None, + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + ) )) } @@ -658,18 +694,21 @@ mod range { assert!(expressions.len() == 1); assert!(matches!( expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::Range( - Some(_), - None, - Some(_), - RangeOperator { - inclusion: RangeInclusion::Inclusive, - .. - } - ), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::Range( + Some(_), + None, + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + ) )) } @@ -687,18 +726,21 @@ mod range { assert!(expressions.len() == 1); assert!(matches!( expressions[0], - PipelineElement::Expression(Expression { - expr: Expr::Range( - Some(_), - Some(_), - Some(_), - RangeOperator { - inclusion: RangeInclusion::Inclusive, - .. - } - ), - .. - }) + PipelineElement::Expression( + _, + Expression { + expr: Expr::Range( + Some(_), + Some(_), + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + ) )) } @@ -1032,10 +1074,13 @@ mod input_types { assert!(expressions.len() == 3); match &expressions[0] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let expected_id = working_set .find_decl(b"ls", &Type::Any) .expect("Error merging delta"); @@ -1045,10 +1090,13 @@ mod input_types { } match &expressions[1] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap(); assert_eq!(call.decl_id, expected_id) } @@ -1056,10 +1104,13 @@ mod input_types { } match &expressions[2] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let expected_id = working_set .find_decl(b"group-by", &Type::Custom("custom".into())) .unwrap(); @@ -1085,10 +1136,13 @@ mod input_types { let expressions = &block[2]; match &expressions[1] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let expected_id = working_set .find_decl(b"agg", &Type::Custom("custom".into())) .unwrap(); @@ -1113,10 +1167,13 @@ mod input_types { let expressions = &block[1]; match &expressions[1] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let expected_id = working_set .find_decl(b"agg", &Type::Custom("custom".into())) .unwrap(); @@ -1142,10 +1199,13 @@ mod input_types { let expressions = &block[1]; match &expressions[1] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap(); assert_eq!(call.decl_id, expected_id) } @@ -1154,10 +1214,13 @@ mod input_types { let expressions = &block[2]; match &expressions[1] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap(); assert_eq!(call.decl_id, expected_id) } @@ -1182,10 +1245,13 @@ mod input_types { assert!(expressions.len() == 2); match &expressions[0] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let expected_id = working_set.find_decl(b"ls", &Type::Any).unwrap(); assert_eq!(call.decl_id, expected_id) } @@ -1193,10 +1259,13 @@ mod input_types { } match &expressions[1] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let expected_id = working_set.find_decl(b"group-by", &Type::Any).unwrap(); assert_eq!(call.decl_id, expected_id) } @@ -1221,10 +1290,13 @@ mod input_types { let expressions = &block[0]; match &expressions[3] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let arg = &call.arguments[0]; match arg { Argument::Positional(a) => match &a.expr { @@ -1236,10 +1308,13 @@ mod input_types { assert!(expressions.len() == 2); match &expressions[1] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let working_set = StateWorkingSet::new(&engine_state); let expected_id = working_set.find_decl(b"min", &Type::Any).unwrap(); @@ -1274,10 +1349,13 @@ mod input_types { let expressions = &block[0]; match &expressions[2] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let expected_id = working_set .find_decl(b"with-column", &Type::Custom("custom".into())) .unwrap(); @@ -1287,10 +1365,13 @@ mod input_types { } match &expressions[3] { - PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + ) => { let expected_id = working_set .find_decl(b"collect", &Type::Custom("custom".into())) .unwrap(); diff --git a/crates/nu-protocol/src/ast/pipeline.rs b/crates/nu-protocol/src/ast/pipeline.rs index 02614ca3af..a7df63bc1b 100644 --- a/crates/nu-protocol/src/ast/pipeline.rs +++ b/crates/nu-protocol/src/ast/pipeline.rs @@ -2,38 +2,50 @@ use std::ops::{Index, IndexMut}; use crate::{ast::Expression, engine::StateWorkingSet, Span, VarId}; +#[derive(Debug, Clone)] +pub enum Redirection { + Stdout, + Stderr, + StdoutAndStderr, +} + +// Note: Span in the below is for the span of the connector not the whole element #[derive(Debug, Clone)] pub enum PipelineElement { - Expression(Expression), - Redirect(Expression), - And(Expression), - Or(Expression), + Expression(Option, Expression), + Redirection(Span, Redirection, Expression), + And(Span, Expression), + Or(Span, Expression), } impl PipelineElement { pub fn span(&self) -> Span { match self { - PipelineElement::Expression(expression) - | PipelineElement::Redirect(expression) - | PipelineElement::And(expression) - | PipelineElement::Or(expression) => expression.span, + PipelineElement::Expression(None, expression) => expression.span, + PipelineElement::Expression(Some(span), expression) + | PipelineElement::Redirection(span, _, expression) + | PipelineElement::And(span, expression) + | PipelineElement::Or(span, expression) => Span { + start: span.start, + end: expression.span.end, + }, } } pub fn has_in_variable(&self, working_set: &StateWorkingSet) -> bool { match self { - PipelineElement::Expression(expression) - | PipelineElement::Redirect(expression) - | PipelineElement::And(expression) - | PipelineElement::Or(expression) => expression.has_in_variable(working_set), + PipelineElement::Expression(_, expression) + | PipelineElement::Redirection(_, _, expression) + | PipelineElement::And(_, expression) + | PipelineElement::Or(_, expression) => expression.has_in_variable(working_set), } } pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) { match self { - PipelineElement::Expression(expression) - | PipelineElement::Redirect(expression) - | PipelineElement::And(expression) - | PipelineElement::Or(expression) => { + PipelineElement::Expression(_, expression) + | PipelineElement::Redirection(_, _, expression) + | PipelineElement::And(_, expression) + | PipelineElement::Or(_, expression) => { expression.replace_in_variable(working_set, new_var_id) } } @@ -46,10 +58,10 @@ impl PipelineElement { new_span: Span, ) { match self { - PipelineElement::Expression(expression) - | PipelineElement::Redirect(expression) - | PipelineElement::And(expression) - | PipelineElement::Or(expression) => { + PipelineElement::Expression(_, expression) + | PipelineElement::Redirection(_, _, expression) + | PipelineElement::And(_, expression) + | PipelineElement::Or(_, expression) => { expression.replace_span(working_set, replaced, new_span) } } @@ -76,7 +88,10 @@ impl Pipeline { Self { elements: expressions .into_iter() - .map(PipelineElement::Expression) + .enumerate() + .map(|(idx, x)| { + PipelineElement::Expression(if idx == 0 { None } else { Some(x.span) }, x) + }) .collect(), } } diff --git a/crates/nu-protocol/src/value/stream.rs b/crates/nu-protocol/src/value/stream.rs index a111120f38..47ccb6b44e 100644 --- a/crates/nu-protocol/src/value/stream.rs +++ b/crates/nu-protocol/src/value/stream.rs @@ -53,6 +53,20 @@ impl RawStream { Ok(Spanned { item: output, span }) } + + pub fn chain(self, stream: RawStream) -> RawStream { + RawStream { + stream: Box::new(self.stream.chain(stream.stream)), + leftover: self + .leftover + .into_iter() + .chain(stream.leftover.into_iter()) + .collect(), + ctrlc: self.ctrlc, + is_binary: self.is_binary, + span: self.span, + } + } } impl Debug for RawStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/main.rs b/src/main.rs index 32ee1de138..461abdafac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -511,10 +511,13 @@ fn parse_commandline_args( // We should have a successful parse now if let Some(pipeline) = block.pipelines.get(0) { - if let Some(PipelineElement::Expression(Expression { - expr: Expr::Call(call), - .. - })) = pipeline.elements.get(0) + if let Some(PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + )) = pipeline.elements.get(0) { let redirect_stdin = call.get_named_arg("stdin"); let login_shell = call.get_named_arg("login");