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
15 changed files with 856 additions and 346 deletions

View File

@ -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
}
}
}

View File

@ -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<ParseError>) {
) -> (Token, Option<ParseError>) {
// 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)

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
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));

View File

@ -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::<Vec<PipelineElement>>();
@ -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<BlockId, Vec<(VarId, Span)>>,
) -> Result<Vec<(VarId, Span)>, 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<Span>, 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<ParseError>) {
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;
for token in tokens.iter() {
@ -5858,17 +5878,96 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
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<ParseError>) {
}
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<ParseError>) {
}
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() {