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.
This commit is contained in:
JT 2022-11-23 07:26:13 +13:00 committed by GitHub
parent c9f9078726
commit 74a73f9838
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 856 additions and 346 deletions

View File

@ -134,10 +134,10 @@ impl NuCompleter {
for pipeline in output.pipelines.into_iter() { for pipeline in output.pipelines.into_iter() {
for pipeline_element in pipeline.elements { for pipeline_element in pipeline.elements {
match pipeline_element { match pipeline_element {
PipelineElement::Expression(expr) PipelineElement::Expression(_, expr)
| PipelineElement::Redirect(expr) | PipelineElement::Redirection(_, _, expr)
| PipelineElement::And(expr) | PipelineElement::And(_, expr)
| PipelineElement::Or(expr) => { | PipelineElement::Or(_, expr) => {
let flattened: Vec<_> = flatten_expression(&working_set, &expr); let flattened: Vec<_> = flatten_expression(&working_set, &expr);
let span_offset: usize = alias_offset.iter().sum(); let span_offset: usize = alias_offset.iter().sum();
let mut spans: Vec<String> = vec![]; let mut spans: Vec<String> = vec![];

View File

@ -120,6 +120,10 @@ impl Highlighter for NuHighlighter {
FlatShape::GlobPattern => add_colored_token!(shape.1, next_token), FlatShape::GlobPattern => add_colored_token!(shape.1, next_token),
FlatShape::Variable => 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::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), FlatShape::Custom(..) => add_colored_token!(shape.1, next_token),
} }
last_seen_span = shape.0.end; last_seen_span = shape.0.end;
@ -232,10 +236,10 @@ fn find_matching_block_end_in_block(
for p in &block.pipelines { for p in &block.pipelines {
for e in &p.elements { for e in &p.elements {
match e { match e {
PipelineElement::Expression(e) PipelineElement::Expression(_, e)
| PipelineElement::Redirect(e) | PipelineElement::Redirection(_, _, e)
| PipelineElement::And(e) | PipelineElement::And(_, e)
| PipelineElement::Or(e) => { | PipelineElement::Or(_, e) => {
if e.span.contains(global_cursor_offset) { if e.span.contains(global_cursor_offset) {
if let Some(pos) = find_matching_block_end_in_expr( if let Some(pos) = find_matching_block_end_in_expr(
line, line,

View File

@ -34,6 +34,10 @@ pub fn get_shape_color(shape: String, conf: &Config) -> Style {
"shape_variable" => Style::new().fg(Color::Purple), "shape_variable" => Style::new().fg(Color::Purple),
"shape_flag" => Style::new().fg(Color::Blue).bold(), "shape_flag" => Style::new().fg(Color::Blue).bold(),
"shape_custom" => Style::new().fg(Color::Green), "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), "shape_nothing" => Style::new().fg(Color::LightCyan),
_ => Style::default(), _ => Style::default(),
}, },

View File

@ -156,10 +156,10 @@ impl Command for FromNuon {
} }
} else { } else {
match pipeline.elements.remove(0) { match pipeline.elements.remove(0) {
PipelineElement::Expression(expression) PipelineElement::Expression(_, expression)
| PipelineElement::Redirect(expression) | PipelineElement::Redirection(_, _, expression)
| PipelineElement::And(expression) | PipelineElement::And(_, expression)
| PipelineElement::Or(expression) => expression, | PipelineElement::Or(_, expression) => expression,
} }
} }
}; };

View File

@ -57,6 +57,7 @@ mod print;
mod query; mod query;
mod random; mod random;
mod range; mod range;
mod redirection;
mod reduce; mod reduce;
mod reject; mod reject;
mod rename; mod rename;

View File

@ -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"));
})
}

View File

@ -2,8 +2,8 @@ use crate::{current_dir_str, get_full_help, scope::create_scope};
use nu_path::expand_path_with; use nu_path::expand_path_with;
use nu_protocol::{ use nu_protocol::{
ast::{ ast::{
Assignment, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math, Operator, Argument, Assignment, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math,
PathMember, PipelineElement, Operator, PathMember, PipelineElement, Redirection,
}, },
engine::{EngineState, Stack}, engine::{EngineState, Stack},
Config, HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, ListStream, Config, HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, ListStream,
@ -801,7 +801,7 @@ pub fn eval_element_with_input(
redirect_stderr: bool, redirect_stderr: bool,
) -> Result<(PipelineData, bool), ShellError> { ) -> Result<(PipelineData, bool), ShellError> {
match element { match element {
PipelineElement::Expression(expr) => eval_expression_with_input( PipelineElement::Expression(_, expr) => eval_expression_with_input(
engine_state, engine_state,
stack, stack,
expr, expr,
@ -809,7 +809,98 @@ pub fn eval_element_with_input(
redirect_stdout, redirect_stdout,
redirect_stderr, 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, engine_state,
stack, stack,
expr, expr,
@ -817,15 +908,7 @@ pub fn eval_element_with_input(
redirect_stdout, redirect_stdout,
redirect_stderr, redirect_stderr,
), ),
PipelineElement::And(expr) => eval_expression_with_input( PipelineElement::Or(_, expr) => eval_expression_with_input(
engine_state,
stack,
expr,
input,
redirect_stdout,
redirect_stderr,
),
PipelineElement::Or(expr) => eval_expression_with_input(
engine_state, engine_state,
stack, stack,
expr, expr,
@ -846,15 +929,23 @@ pub fn eval_block(
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let num_pipelines = block.len(); let num_pipelines = block.len();
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() { 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)`. // if eval internal command failed, it can just make early return with `Err(ShellError)`.
let eval_result = eval_element_with_input( let eval_result = eval_element_with_input(
engine_state, engine_state,
stack, stack,
elem, &pipeline.elements[i],
input, input,
redirect_stdout || (i != pipeline.elements.len() - 1), 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; input = eval_result.0;
// external command may runs to failed // external command may runs to failed
@ -863,6 +954,8 @@ pub fn eval_block(
if eval_result.1 { if eval_result.1 {
return Ok(input); return Ok(input);
} }
i += 1;
} }
if pipeline_idx < (num_pipelines) - 1 { if pipeline_idx < (num_pipelines) - 1 {

View File

@ -32,6 +32,10 @@ pub enum FlatShape {
GlobPattern, GlobPattern,
Variable, Variable,
Flag, Flag,
Pipe,
And,
Or,
Redirection,
Custom(DeclId), Custom(DeclId),
} }
@ -63,6 +67,10 @@ impl Display for FlatShape {
FlatShape::GlobPattern => write!(f, "shape_globpattern"), FlatShape::GlobPattern => write!(f, "shape_globpattern"),
FlatShape::Variable => write!(f, "shape_variable"), FlatShape::Variable => write!(f, "shape_variable"),
FlatShape::Flag => write!(f, "shape_flag"), 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"), FlatShape::Custom(_) => write!(f, "shape_custom"),
} }
} }
@ -502,10 +510,30 @@ pub fn flatten_pipeline_element(
pipeline_element: &PipelineElement, pipeline_element: &PipelineElement,
) -> Vec<(Span, FlatShape)> { ) -> Vec<(Span, FlatShape)> {
match pipeline_element { match pipeline_element {
PipelineElement::Expression(expr) PipelineElement::Expression(span, expr) => {
| PipelineElement::Redirect(expr) if let Some(span) = span {
| PipelineElement::And(expr) let mut output = vec![(*span, FlatShape::Pipe)];
| PipelineElement::Or(expr) => flatten_expression(working_set, expr), 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
}
} }
} }

View File

@ -1,12 +1,15 @@
use crate::ParseError; use crate::ParseError;
use nu_protocol::Span; use nu_protocol::Span;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum TokenContents { pub enum TokenContents {
Item, Item,
Comment, Comment,
Pipe, Pipe,
Semicolon, Semicolon,
OutGreaterThan,
ErrGreaterThan,
OutErrGreaterThan,
Eol, Eol,
} }
@ -74,7 +77,7 @@ pub fn lex_item(
span_offset: usize, span_offset: usize,
additional_whitespace: &[u8], additional_whitespace: &[u8],
special_tokens: &[u8], special_tokens: &[u8],
) -> (Span, Option<ParseError>) { ) -> (Token, Option<ParseError>) {
// This variable tracks the starting character of a string literal, so that // This variable tracks the starting character of a string literal, so that
// we remain inside the string literal lexer mode until we encounter the // we remain inside the string literal lexer mode until we encounter the
// closing quote. // closing quote.
@ -113,7 +116,10 @@ pub fn lex_item(
let span = Span::new(span_offset + token_start, span_offset + *curr_offset); let span = Span::new(span_offset + token_start, span_offset + *curr_offset);
return ( return (
span, Token {
contents: TokenContents::Item,
span,
},
Some(ParseError::UnexpectedEof( Some(ParseError::UnexpectedEof(
(start as char).to_string(), (start as char).to_string(),
Span { 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 { 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 // anyone wanting to consume this partial parse (e.g., completions) will be able to get
// correct information from the non-lite parse. // correct information from the non-lite parse.
return ( return (
span, Token {
contents: TokenContents::Item,
span,
},
Some(ParseError::UnexpectedEof( Some(ParseError::UnexpectedEof(
(delim as char).to_string(), (delim as char).to_string(),
Span { Span {
@ -217,12 +232,44 @@ pub fn lex_item(
// If we didn't accumulate any characters, it's an unexpected error. // If we didn't accumulate any characters, it's an unexpected error.
if *curr_offset - token_start == 0 { if *curr_offset - token_start == 0 {
return ( return (
span, Token {
contents: TokenContents::Item,
span,
},
Some(ParseError::UnexpectedEof("command".to_string(), 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( pub fn lex(
@ -347,7 +394,7 @@ pub fn lex(
} else { } else {
// Otherwise, try to consume an unclassified token. // Otherwise, try to consume an unclassified token.
let (span, err) = lex_item( let (token, err) = lex_item(
input, input,
&mut curr_offset, &mut curr_offset,
span_offset, span_offset,
@ -358,7 +405,7 @@ pub fn lex(
error = err; error = err;
} }
is_complete = true; is_complete = true;
output.push(Token::new(TokenContents::Item, span)); output.push(token);
} }
} }
(output, error) (output, error)

View File

@ -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 // Trying to warp the 'def' call into the 'export def' in a very clumsy way
if let Some(PipelineElement::Expression(Expression { if let Some(PipelineElement::Expression(
expr: Expr::Call(ref def_call), _,
.. Expression {
})) = pipeline.elements.get(0) expr: Expr::Call(ref def_call),
..
},
)) = pipeline.elements.get(0)
{ {
call = def_call.clone(); 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 // Trying to warp the 'def' call into the 'export def' in a very clumsy way
if let Some(PipelineElement::Expression(Expression { if let Some(PipelineElement::Expression(
expr: Expr::Call(ref def_call), _,
.. Expression {
})) = pipeline.elements.get(0) expr: Expr::Call(ref def_call),
..
},
)) = pipeline.elements.get(0)
{ {
call = def_call.clone(); 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 // Trying to warp the 'def' call into the 'export def' in a very clumsy way
if let Some(PipelineElement::Expression(Expression { if let Some(PipelineElement::Expression(
expr: Expr::Call(ref def_call), _,
.. Expression {
})) = pipeline.elements.get(0) expr: Expr::Call(ref def_call),
..
},
)) = pipeline.elements.get(0)
{ {
call = def_call.clone(); 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 // Trying to warp the 'alias' call into the 'export alias' in a very clumsy way
if let Some(PipelineElement::Expression(Expression { if let Some(PipelineElement::Expression(
expr: Expr::Call(ref alias_call), _,
.. Expression {
})) = pipeline.elements.get(0) expr: Expr::Call(ref alias_call),
..
},
)) = pipeline.elements.get(0)
{ {
call = alias_call.clone(); 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 // Trying to warp the 'use' call into the 'export use' in a very clumsy way
if let Some(PipelineElement::Expression(Expression { if let Some(PipelineElement::Expression(
expr: Expr::Call(ref use_call), _,
.. Expression {
})) = pipeline.elements.get(0) expr: Expr::Call(ref use_call),
..
},
)) = pipeline.elements.get(0)
{ {
call = use_call.clone(); call = use_call.clone();
@ -1313,7 +1328,7 @@ pub fn parse_module_block(
for pipeline in &output.block { for pipeline in &output.block {
if pipeline.commands.len() == 1 { 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); parse_def_predecl(working_set, &command.parts, expand_aliases_denylist);
} }
} }
@ -1327,7 +1342,7 @@ pub fn parse_module_block(
.map(|pipeline| { .map(|pipeline| {
if pipeline.commands.len() == 1 { if pipeline.commands.len() == 1 {
match &pipeline.commands[0] { match &pipeline.commands[0] {
LiteElement::Command(command) => { LiteElement::Command(_, command) => {
let name = working_set.get_span_contents(command.parts[0]); let name = working_set.get_span_contents(command.parts[0]);
let (pipeline, err) = match name { let (pipeline, err) = match name {
@ -1407,9 +1422,9 @@ pub fn parse_module_block(
pipeline pipeline
} }
LiteElement::Or(command) LiteElement::Or(_, command)
| LiteElement::And(command) | LiteElement::And(_, command)
| LiteElement::Redirection(command) => (garbage_pipeline(&command.parts)), | LiteElement::Redirection(_, _, command) => (garbage_pipeline(&command.parts)),
} }
} else { } else {
error = Some(ParseError::Expected("not a pipeline".into(), span)); error = Some(ParseError::Expected("not a pipeline".into(), span));

View File

@ -8,7 +8,7 @@ use nu_protocol::{
ast::{ ast::{
Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression, Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression,
FullCellPath, ImportPattern, ImportPatternHead, ImportPatternMember, Math, Operator, FullCellPath, ImportPattern, ImportPatternHead, ImportPatternMember, Math, Operator,
PathMember, Pipeline, PipelineElement, RangeInclusion, RangeOperator, PathMember, Pipeline, PipelineElement, RangeInclusion, RangeOperator, Redirection,
}, },
engine::StateWorkingSet, engine::StateWorkingSet,
span, BlockId, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId, 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); let result = result.elements.remove(0);
// If this is the first element in a pipeline, we know it has to be an expression // 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); result.replace_span(working_set, expansion_span, orig_span);
return (result, err); return (result, err);
@ -1203,7 +1203,12 @@ fn parse_binary_with_base(
Some(ParseError::Expected("binary".into(), span)), 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() .last()
.and_then(|Pipeline { elements, .. }| elements.last()) .and_then(|Pipeline { elements, .. }| elements.last())
.map(|element| match element { .map(|element| match element {
PipelineElement::Expression(expr) PipelineElement::Expression(_, expr)
if matches!( if matches!(
expr, expr,
Expression { Expression {
@ -3071,7 +3076,7 @@ pub fn parse_row_condition(
let mut pipeline = Pipeline::new(); let mut pipeline = Pipeline::new();
pipeline pipeline
.elements .elements
.push(PipelineElement::Expression(expression)); .push(PipelineElement::Expression(None, expression));
block.pipelines.push(pipeline); block.pipelines.push(pipeline);
@ -3721,7 +3726,7 @@ pub fn parse_list_expression(
for arg in &output.block[0].commands { for arg in &output.block[0].commands {
let mut spans_idx = 0; let mut spans_idx = 0;
if let LiteElement::Command(command) = arg { if let LiteElement::Command(_, command) = arg {
while spans_idx < command.parts.len() { while spans_idx < command.parts.len() {
let (arg, err) = parse_multispan_value( let (arg, err) = parse_multispan_value(
working_set, working_set,
@ -3814,10 +3819,10 @@ pub fn parse_table_expression(
} }
_ => { _ => {
match &output.block[0].commands[0] { match &output.block[0].commands[0] {
LiteElement::Command(command) LiteElement::Command(_, command)
| LiteElement::Redirection(command) | LiteElement::Redirection(_, _, command)
| LiteElement::And(command) | LiteElement::And(_, command)
| LiteElement::Or(command) => { | LiteElement::Or(_, command) => {
let mut table_headers = vec![]; let mut table_headers = vec![];
let (headers, err) = parse_value( let (headers, err) = parse_value(
@ -3837,10 +3842,10 @@ pub fn parse_table_expression(
} }
match &output.block[1].commands[0] { match &output.block[1].commands[0] {
LiteElement::Command(command) LiteElement::Command(_, command)
| LiteElement::Redirection(command) | LiteElement::Redirection(_, _, command)
| LiteElement::And(command) | LiteElement::And(_, command)
| LiteElement::Or(command) => { | LiteElement::Or(_, command) => {
let mut rows = vec![]; let mut rows = vec![];
for part in &command.parts { for part in &command.parts {
let (values, err) = parse_value( let (values, err) = parse_value(
@ -5184,10 +5189,10 @@ pub fn parse_block(
for pipeline in &lite_block.block { for pipeline in &lite_block.block {
if pipeline.commands.len() == 1 { if pipeline.commands.len() == 1 {
match &pipeline.commands[0] { match &pipeline.commands[0] {
LiteElement::Command(command) LiteElement::Command(_, command)
| LiteElement::Redirection(command) | LiteElement::Redirection(_, _, command)
| LiteElement::And(command) | LiteElement::And(_, command)
| LiteElement::Or(command) => { | LiteElement::Or(_, command) => {
if let Some(err) = if let Some(err) =
parse_def_predecl(working_set, &command.parts, expand_aliases_denylist) parse_def_predecl(working_set, &command.parts, expand_aliases_denylist)
{ {
@ -5208,7 +5213,7 @@ pub fn parse_block(
.commands .commands
.iter() .iter()
.map(|command| match command { .map(|command| match command {
LiteElement::Command(command) => { LiteElement::Command(span, command) => {
let (expr, err) = parse_expression( let (expr, err) = parse_expression(
working_set, working_set,
&command.parts, &command.parts,
@ -5221,12 +5226,12 @@ pub fn parse_block(
error = err; error = err;
} }
PipelineElement::Expression(expr) PipelineElement::Expression(*span, expr)
} }
LiteElement::Redirection(command) => { LiteElement::Redirection(span, redirection, command) => {
let (expr, err) = parse_expression( let (expr, err) = parse_string(
working_set, working_set,
&command.parts, command.parts[0],
expand_aliases_denylist, expand_aliases_denylist,
); );
@ -5236,9 +5241,9 @@ pub fn parse_block(
error = err; error = err;
} }
PipelineElement::Redirect(expr) PipelineElement::Redirection(*span, redirection.clone(), expr)
} }
LiteElement::And(command) => { LiteElement::And(span, command) => {
let (expr, err) = parse_expression( let (expr, err) = parse_expression(
working_set, working_set,
&command.parts, &command.parts,
@ -5251,9 +5256,9 @@ pub fn parse_block(
error = err; error = err;
} }
PipelineElement::And(expr) PipelineElement::And(*span, expr)
} }
LiteElement::Or(command) => { LiteElement::Or(span, command) => {
let (expr, err) = parse_expression( let (expr, err) = parse_expression(
working_set, working_set,
&command.parts, &command.parts,
@ -5266,7 +5271,7 @@ pub fn parse_block(
error = err; error = err;
} }
PipelineElement::Or(expr) PipelineElement::Or(*span, expr)
} }
}) })
.collect::<Vec<PipelineElement>>(); .collect::<Vec<PipelineElement>>();
@ -5288,10 +5293,10 @@ pub fn parse_block(
Pipeline { elements: output } Pipeline { elements: output }
} else { } else {
match &pipeline.commands[0] { match &pipeline.commands[0] {
LiteElement::Command(command) LiteElement::Command(_, command)
| LiteElement::Redirection(command) | LiteElement::Redirection(_, _, command)
| LiteElement::And(command) | LiteElement::And(_, command)
| LiteElement::Or(command) => { | LiteElement::Or(_, command) => {
let (mut pipeline, err) = let (mut pipeline, err) =
parse_builtin_commands(working_set, command, expand_aliases_denylist); 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) working_set.find_decl(b"let-env", &Type::Any)
{ {
for element in pipeline.elements.iter_mut() { for element in pipeline.elements.iter_mut() {
if let PipelineElement::Expression(Expression { if let PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) = element expr: Expr::Call(call),
..
},
) = element
{ {
if call.decl_id == let_decl_id if call.decl_id == let_decl_id
|| call.decl_id == let_env_decl_id || call.decl_id == let_env_decl_id
@ -5421,10 +5429,10 @@ pub fn discover_captures_in_pipeline_element(
seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>, seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
) -> Result<Vec<(VarId, Span)>, ParseError> { ) -> Result<Vec<(VarId, Span)>, ParseError> {
match element { match element {
PipelineElement::Expression(expression) PipelineElement::Expression(_, expression)
| PipelineElement::Redirect(expression) | PipelineElement::Redirection(_, _, expression)
| PipelineElement::And(expression) | PipelineElement::And(_, expression)
| PipelineElement::Or(expression) => { | PipelineElement::Or(_, expression) => {
discover_captures_in_expr(working_set, expression, seen, seen_blocks) discover_captures_in_expr(working_set, expression, seen, seen_blocks)
} }
} }
@ -5674,17 +5682,21 @@ fn wrap_element_with_collect(
element: &PipelineElement, element: &PipelineElement,
) -> PipelineElement { ) -> PipelineElement {
match element { match element {
PipelineElement::Expression(expression) => { PipelineElement::Expression(span, expression) => {
PipelineElement::Expression(wrap_expr_with_collect(working_set, expression)) PipelineElement::Expression(*span, wrap_expr_with_collect(working_set, expression))
} }
PipelineElement::Redirect(expression) => { PipelineElement::Redirection(span, redirection, expression) => {
PipelineElement::Redirect(wrap_expr_with_collect(working_set, expression)) PipelineElement::Redirection(
*span,
redirection.clone(),
wrap_expr_with_collect(working_set, expression),
)
} }
PipelineElement::And(expression) => { PipelineElement::And(span, expression) => {
PipelineElement::And(wrap_expr_with_collect(working_set, expression)) PipelineElement::And(*span, wrap_expr_with_collect(working_set, expression))
} }
PipelineElement::Or(expression) => { PipelineElement::Or(span, expression) => {
PipelineElement::Or(wrap_expr_with_collect(working_set, 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)] #[derive(Debug)]
pub enum LiteElement { pub enum LiteElement {
Command(LiteCommand), Command(Option<Span>, LiteCommand),
Redirection(LiteCommand), Redirection(Span, Redirection, LiteCommand),
And(LiteCommand), And(Span, LiteCommand),
Or(LiteCommand), Or(Span, LiteCommand),
} }
#[derive(Debug)] #[derive(Debug)]
@ -5846,6 +5859,13 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
let mut last_token = TokenContents::Eol; let mut last_token = TokenContents::Eol;
let mut last_connector = TokenContents::Pipe;
let mut last_connector_span: Option<Span> = None;
if tokens.is_empty() {
return (LiteBlock::new(), None);
}
let mut curr_comment: Option<Vec<Span>> = None; let mut curr_comment: Option<Vec<Span>> = None;
for token in tokens.iter() { for token in tokens.iter() {
@ -5858,17 +5878,96 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
curr_command.push(token.span); curr_command.push(token.span);
last_token = TokenContents::Item; 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 => { TokenContents::Pipe => {
if !curr_command.is_empty() { 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(); curr_command = LiteCommand::new();
} }
last_token = TokenContents::Pipe; last_token = TokenContents::Pipe;
last_connector = TokenContents::Pipe;
last_connector_span = Some(token.span);
} }
TokenContents::Eol => { TokenContents::Eol => {
if last_token != TokenContents::Pipe { if last_token != TokenContents::Pipe && last_token != TokenContents::OutGreaterThan
{
if !curr_command.is_empty() { 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(); curr_command = LiteCommand::new();
} }
@ -5889,7 +5988,19 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
} }
TokenContents::Semicolon => { TokenContents::Semicolon => {
if !curr_command.is_empty() { 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(); curr_command = LiteCommand::new();
} }
@ -5922,7 +6033,35 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
} }
if !curr_command.is_empty() { 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() { if !curr_pipeline.is_empty() {

View File

@ -54,10 +54,13 @@ pub fn parse_int() {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
expressions[0], expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Int(3), _,
.. Expression {
}) expr: Expr::Int(3),
..
}
)
)) ))
} }
@ -72,7 +75,7 @@ pub fn parse_binary_with_hex_format() {
assert!(block.len() == 1); assert!(block.len() == 1);
let expressions = &block[0]; let expressions = &block[0];
assert!(expressions.len() == 1); 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])) assert_eq!(expr.expr, Expr::Binary(vec![0x13]))
} else { } else {
panic!("Not an expression") panic!("Not an expression")
@ -90,7 +93,7 @@ pub fn parse_binary_with_incomplete_hex_format() {
assert!(block.len() == 1); assert!(block.len() == 1);
let expressions = &block[0]; let expressions = &block[0];
assert!(expressions.len() == 1); 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])) assert_eq!(expr.expr, Expr::Binary(vec![0x03]))
} else { } else {
panic!("Not an expression") panic!("Not an expression")
@ -108,7 +111,7 @@ pub fn parse_binary_with_binary_format() {
assert!(block.len() == 1); assert!(block.len() == 1);
let expressions = &block[0]; let expressions = &block[0];
assert!(expressions.len() == 1); 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])) assert_eq!(expr.expr, Expr::Binary(vec![0b10101000]))
} else { } else {
panic!("Not an expression") panic!("Not an expression")
@ -126,7 +129,7 @@ pub fn parse_binary_with_incomplete_binary_format() {
assert!(block.len() == 1); assert!(block.len() == 1);
let expressions = &block[0]; let expressions = &block[0];
assert!(expressions.len() == 1); 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])) assert_eq!(expr.expr, Expr::Binary(vec![0b00000010]))
} else { } else {
panic!("Not an expression") panic!("Not an expression")
@ -144,7 +147,7 @@ pub fn parse_binary_with_octal_format() {
assert!(block.len() == 1); assert!(block.len() == 1);
let expressions = &block[0]; let expressions = &block[0];
assert!(expressions.len() == 1); 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])) assert_eq!(expr.expr, Expr::Binary(vec![0o250]))
} else { } else {
panic!("Not an expression") panic!("Not an expression")
@ -162,7 +165,7 @@ pub fn parse_binary_with_incomplete_octal_format() {
assert!(block.len() == 1); assert!(block.len() == 1);
let expressions = &block[0]; let expressions = &block[0];
assert!(expressions.len() == 1); 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])) assert_eq!(expr.expr, Expr::Binary(vec![0o2]))
} else { } else {
panic!("Not an expression") panic!("Not an expression")
@ -180,7 +183,7 @@ pub fn parse_binary_with_invalid_octal_format() {
assert!(block.len() == 1); assert!(block.len() == 1);
let expressions = &block[0]; let expressions = &block[0];
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
if let PipelineElement::Expression(expr) = &expressions[0] { if let PipelineElement::Expression(_, expr) = &expressions[0] {
assert!(!matches!(&expr.expr, Expr::Binary(_))) assert!(!matches!(&expr.expr, Expr::Binary(_)))
} else { } else {
panic!("Not an expression") panic!("Not an expression")
@ -200,7 +203,7 @@ pub fn parse_binary_with_multi_byte_char() {
assert!(block.len() == 1); assert!(block.len() == 1);
let expressions = &block[0]; let expressions = &block[0];
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
if let PipelineElement::Expression(expr) = &expressions[0] { if let PipelineElement::Expression(_, expr) = &expressions[0] {
assert!(!matches!(&expr.expr, Expr::Binary(_))) assert!(!matches!(&expr.expr, Expr::Binary(_)))
} else { } else {
panic!("Not an expression") panic!("Not an expression")
@ -218,7 +221,7 @@ pub fn parse_string() {
assert!(block.len() == 1); assert!(block.len() == 1);
let expressions = &block[0]; let expressions = &block[0];
assert!(expressions.len() == 1); 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())) assert_eq!(expr.expr, Expr::String("hello nushell".to_string()))
} else { } else {
panic!("Not an expression") panic!("Not an expression")
@ -242,7 +245,7 @@ pub fn parse_escaped_string() {
assert!(block.len() == 1); assert!(block.len() == 1);
let expressions = &block[0]; let expressions = &block[0];
assert!(expressions.len() == 1); 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())) assert_eq!(expr.expr, Expr::String("hello nushell".to_string()))
} else { } else {
panic!("Not an expression") panic!("Not an expression")
@ -265,10 +268,13 @@ pub fn parse_call() {
let expressions = &block[0]; let expressions = &block[0];
assert_eq!(expressions.len(), 1); assert_eq!(expressions.len(), 1);
if let PipelineElement::Expression(Expression { if let PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) = &expressions[0] expr: Expr::Call(call),
..
},
) = &expressions[0]
{ {
assert_eq!(call.decl_id, 0); assert_eq!(call.decl_id, 0);
} }
@ -371,10 +377,13 @@ fn test_nothing_comparisson_eq() {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
&expressions[0], &expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::BinaryOp(..), _,
.. Expression {
}) expr: Expr::BinaryOp(..),
..
}
)
)) ))
} }
@ -391,10 +400,13 @@ fn test_nothing_comparisson_neq() {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
&expressions[0], &expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::BinaryOp(..), _,
.. Expression {
}) expr: Expr::BinaryOp(..),
..
}
)
)) ))
} }
@ -416,18 +428,21 @@ mod range {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
expressions[0], expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Range( _,
Some(_), Expression {
None, expr: Expr::Range(
Some(_), Some(_),
RangeOperator { None,
inclusion: RangeInclusion::Inclusive, Some(_),
.. RangeOperator {
} inclusion: RangeInclusion::Inclusive,
), ..
.. }
}) ),
..
}
)
)) ))
} }
@ -445,18 +460,21 @@ mod range {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
expressions[0], expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Range( _,
Some(_), Expression {
None, expr: Expr::Range(
Some(_), Some(_),
RangeOperator { None,
inclusion: RangeInclusion::RightExclusive, Some(_),
.. RangeOperator {
} inclusion: RangeInclusion::RightExclusive,
), ..
.. }
}) ),
..
}
)
)) ))
} }
@ -474,18 +492,21 @@ mod range {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
expressions[0], expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Range( _,
Some(_), Expression {
None, expr: Expr::Range(
Some(_), Some(_),
RangeOperator { None,
inclusion: RangeInclusion::Inclusive, Some(_),
.. RangeOperator {
} inclusion: RangeInclusion::Inclusive,
), ..
.. }
}) ),
..
}
)
)) ))
} }
@ -503,18 +524,21 @@ mod range {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
expressions[0], expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Range( _,
Some(_), Expression {
None, expr: Expr::Range(
Some(_), Some(_),
RangeOperator { None,
inclusion: RangeInclusion::RightExclusive, Some(_),
.. RangeOperator {
} inclusion: RangeInclusion::RightExclusive,
), ..
.. }
}) ),
..
}
)
)) ))
} }
@ -534,18 +558,21 @@ mod range {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
expressions[0], expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Range( _,
Some(_), Expression {
None, expr: Expr::Range(
Some(_), Some(_),
RangeOperator { None,
inclusion: RangeInclusion::Inclusive, Some(_),
.. RangeOperator {
} inclusion: RangeInclusion::Inclusive,
), ..
.. }
}) ),
..
}
)
)) ))
} }
@ -571,18 +598,21 @@ mod range {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
expressions[0], expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Range( _,
Some(_), Expression {
None, expr: Expr::Range(
Some(_), Some(_),
RangeOperator { None,
inclusion: RangeInclusion::RightExclusive, Some(_),
.. RangeOperator {
} inclusion: RangeInclusion::RightExclusive,
), ..
.. }
}) ),
..
}
)
)) ))
} }
@ -600,18 +630,21 @@ mod range {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
expressions[0], expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Range( _,
Some(_), Expression {
None, expr: Expr::Range(
None, Some(_),
RangeOperator { None,
inclusion: RangeInclusion::Inclusive, None,
.. RangeOperator {
} inclusion: RangeInclusion::Inclusive,
), ..
.. }
}) ),
..
}
)
)) ))
} }
@ -629,18 +662,21 @@ mod range {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
expressions[0], expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Range( _,
None, Expression {
None, expr: Expr::Range(
Some(_), None,
RangeOperator { None,
inclusion: RangeInclusion::Inclusive, Some(_),
.. RangeOperator {
} inclusion: RangeInclusion::Inclusive,
), ..
.. }
}) ),
..
}
)
)) ))
} }
@ -658,18 +694,21 @@ mod range {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
expressions[0], expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Range( _,
Some(_), Expression {
None, expr: Expr::Range(
Some(_), Some(_),
RangeOperator { None,
inclusion: RangeInclusion::Inclusive, Some(_),
.. RangeOperator {
} inclusion: RangeInclusion::Inclusive,
), ..
.. }
}) ),
..
}
)
)) ))
} }
@ -687,18 +726,21 @@ mod range {
assert!(expressions.len() == 1); assert!(expressions.len() == 1);
assert!(matches!( assert!(matches!(
expressions[0], expressions[0],
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Range( _,
Some(_), Expression {
Some(_), expr: Expr::Range(
Some(_), Some(_),
RangeOperator { Some(_),
inclusion: RangeInclusion::Inclusive, Some(_),
.. RangeOperator {
} inclusion: RangeInclusion::Inclusive,
), ..
.. }
}) ),
..
}
)
)) ))
} }
@ -1032,10 +1074,13 @@ mod input_types {
assert!(expressions.len() == 3); assert!(expressions.len() == 3);
match &expressions[0] { match &expressions[0] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let expected_id = working_set let expected_id = working_set
.find_decl(b"ls", &Type::Any) .find_decl(b"ls", &Type::Any)
.expect("Error merging delta"); .expect("Error merging delta");
@ -1045,10 +1090,13 @@ mod input_types {
} }
match &expressions[1] { match &expressions[1] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap(); let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap();
assert_eq!(call.decl_id, expected_id) assert_eq!(call.decl_id, expected_id)
} }
@ -1056,10 +1104,13 @@ mod input_types {
} }
match &expressions[2] { match &expressions[2] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let expected_id = working_set let expected_id = working_set
.find_decl(b"group-by", &Type::Custom("custom".into())) .find_decl(b"group-by", &Type::Custom("custom".into()))
.unwrap(); .unwrap();
@ -1085,10 +1136,13 @@ mod input_types {
let expressions = &block[2]; let expressions = &block[2];
match &expressions[1] { match &expressions[1] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let expected_id = working_set let expected_id = working_set
.find_decl(b"agg", &Type::Custom("custom".into())) .find_decl(b"agg", &Type::Custom("custom".into()))
.unwrap(); .unwrap();
@ -1113,10 +1167,13 @@ mod input_types {
let expressions = &block[1]; let expressions = &block[1];
match &expressions[1] { match &expressions[1] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let expected_id = working_set let expected_id = working_set
.find_decl(b"agg", &Type::Custom("custom".into())) .find_decl(b"agg", &Type::Custom("custom".into()))
.unwrap(); .unwrap();
@ -1142,10 +1199,13 @@ mod input_types {
let expressions = &block[1]; let expressions = &block[1];
match &expressions[1] { match &expressions[1] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap(); let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap();
assert_eq!(call.decl_id, expected_id) assert_eq!(call.decl_id, expected_id)
} }
@ -1154,10 +1214,13 @@ mod input_types {
let expressions = &block[2]; let expressions = &block[2];
match &expressions[1] { match &expressions[1] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap(); let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap();
assert_eq!(call.decl_id, expected_id) assert_eq!(call.decl_id, expected_id)
} }
@ -1182,10 +1245,13 @@ mod input_types {
assert!(expressions.len() == 2); assert!(expressions.len() == 2);
match &expressions[0] { match &expressions[0] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let expected_id = working_set.find_decl(b"ls", &Type::Any).unwrap(); let expected_id = working_set.find_decl(b"ls", &Type::Any).unwrap();
assert_eq!(call.decl_id, expected_id) assert_eq!(call.decl_id, expected_id)
} }
@ -1193,10 +1259,13 @@ mod input_types {
} }
match &expressions[1] { match &expressions[1] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let expected_id = working_set.find_decl(b"group-by", &Type::Any).unwrap(); let expected_id = working_set.find_decl(b"group-by", &Type::Any).unwrap();
assert_eq!(call.decl_id, expected_id) assert_eq!(call.decl_id, expected_id)
} }
@ -1221,10 +1290,13 @@ mod input_types {
let expressions = &block[0]; let expressions = &block[0];
match &expressions[3] { match &expressions[3] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let arg = &call.arguments[0]; let arg = &call.arguments[0];
match arg { match arg {
Argument::Positional(a) => match &a.expr { Argument::Positional(a) => match &a.expr {
@ -1236,10 +1308,13 @@ mod input_types {
assert!(expressions.len() == 2); assert!(expressions.len() == 2);
match &expressions[1] { match &expressions[1] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let working_set = StateWorkingSet::new(&engine_state); let working_set = StateWorkingSet::new(&engine_state);
let expected_id = let expected_id =
working_set.find_decl(b"min", &Type::Any).unwrap(); working_set.find_decl(b"min", &Type::Any).unwrap();
@ -1274,10 +1349,13 @@ mod input_types {
let expressions = &block[0]; let expressions = &block[0];
match &expressions[2] { match &expressions[2] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let expected_id = working_set let expected_id = working_set
.find_decl(b"with-column", &Type::Custom("custom".into())) .find_decl(b"with-column", &Type::Custom("custom".into()))
.unwrap(); .unwrap();
@ -1287,10 +1365,13 @@ mod input_types {
} }
match &expressions[3] { match &expressions[3] {
PipelineElement::Expression(Expression { PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
}) => { expr: Expr::Call(call),
..
},
) => {
let expected_id = working_set let expected_id = working_set
.find_decl(b"collect", &Type::Custom("custom".into())) .find_decl(b"collect", &Type::Custom("custom".into()))
.unwrap(); .unwrap();

View File

@ -2,38 +2,50 @@ use std::ops::{Index, IndexMut};
use crate::{ast::Expression, engine::StateWorkingSet, Span, VarId}; 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)] #[derive(Debug, Clone)]
pub enum PipelineElement { pub enum PipelineElement {
Expression(Expression), Expression(Option<Span>, Expression),
Redirect(Expression), Redirection(Span, Redirection, Expression),
And(Expression), And(Span, Expression),
Or(Expression), Or(Span, Expression),
} }
impl PipelineElement { impl PipelineElement {
pub fn span(&self) -> Span { pub fn span(&self) -> Span {
match self { match self {
PipelineElement::Expression(expression) PipelineElement::Expression(None, expression) => expression.span,
| PipelineElement::Redirect(expression) PipelineElement::Expression(Some(span), expression)
| PipelineElement::And(expression) | PipelineElement::Redirection(span, _, expression)
| PipelineElement::Or(expression) => expression.span, | 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 { pub fn has_in_variable(&self, working_set: &StateWorkingSet) -> bool {
match self { match self {
PipelineElement::Expression(expression) PipelineElement::Expression(_, expression)
| PipelineElement::Redirect(expression) | PipelineElement::Redirection(_, _, expression)
| PipelineElement::And(expression) | PipelineElement::And(_, expression)
| PipelineElement::Or(expression) => expression.has_in_variable(working_set), | PipelineElement::Or(_, expression) => expression.has_in_variable(working_set),
} }
} }
pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) { pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) {
match self { match self {
PipelineElement::Expression(expression) PipelineElement::Expression(_, expression)
| PipelineElement::Redirect(expression) | PipelineElement::Redirection(_, _, expression)
| PipelineElement::And(expression) | PipelineElement::And(_, expression)
| PipelineElement::Or(expression) => { | PipelineElement::Or(_, expression) => {
expression.replace_in_variable(working_set, new_var_id) expression.replace_in_variable(working_set, new_var_id)
} }
} }
@ -46,10 +58,10 @@ impl PipelineElement {
new_span: Span, new_span: Span,
) { ) {
match self { match self {
PipelineElement::Expression(expression) PipelineElement::Expression(_, expression)
| PipelineElement::Redirect(expression) | PipelineElement::Redirection(_, _, expression)
| PipelineElement::And(expression) | PipelineElement::And(_, expression)
| PipelineElement::Or(expression) => { | PipelineElement::Or(_, expression) => {
expression.replace_span(working_set, replaced, new_span) expression.replace_span(working_set, replaced, new_span)
} }
} }
@ -76,7 +88,10 @@ impl Pipeline {
Self { Self {
elements: expressions elements: expressions
.into_iter() .into_iter()
.map(PipelineElement::Expression) .enumerate()
.map(|(idx, x)| {
PipelineElement::Expression(if idx == 0 { None } else { Some(x.span) }, x)
})
.collect(), .collect(),
} }
} }

View File

@ -53,6 +53,20 @@ impl RawStream {
Ok(Spanned { item: output, span }) 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 { impl Debug for RawStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View File

@ -511,10 +511,13 @@ fn parse_commandline_args(
// We should have a successful parse now // We should have a successful parse now
if let Some(pipeline) = block.pipelines.get(0) { if let Some(pipeline) = block.pipelines.get(0) {
if let Some(PipelineElement::Expression(Expression { if let Some(PipelineElement::Expression(
expr: Expr::Call(call), _,
.. Expression {
})) = pipeline.elements.get(0) expr: Expr::Call(call),
..
},
)) = pipeline.elements.get(0)
{ {
let redirect_stdin = call.get_named_arg("stdin"); let redirect_stdin = call.get_named_arg("stdin");
let login_shell = call.get_named_arg("login"); let login_shell = call.get_named_arg("login");