forked from extern/nushell
Support o>>
, e>>
, o+e>>
to append output to an external file (#10764)
# Description Close: #10278 This pr introduces `o>>`, `e>>`, `o+e>>` to allow redirection to append to a file. Examples: ```nushell echo abc o>> a.txt echo abc o>> a.txt cat asdf e>> a.txt cat asdf e>> a.txt cat asdf o+e>> a.txt ``` ~~TODO:~~ ~~1. currently internal commands with `o+e>` redirect to a variable is broken: `let x = "a.txt"; echo abc o+e> $x`, not sure when it was introduced...~~ ~~2. redirect stdout and stderr with append mode doesn't supported yet: `cat asdf o>>a.txt e>>b.ext`~~ ~~For these 2 items, I'd like to fix them in different prs.~~ Already done in this pr
This commit is contained in:
@ -528,14 +528,14 @@ pub fn flatten_pipeline_element(
|
||||
flatten_expression(working_set, expr)
|
||||
}
|
||||
}
|
||||
PipelineElement::Redirection(span, _, expr) => {
|
||||
PipelineElement::Redirection(span, _, expr, _) => {
|
||||
let mut output = vec![(*span, FlatShape::Redirection)];
|
||||
output.append(&mut flatten_expression(working_set, expr));
|
||||
output
|
||||
}
|
||||
PipelineElement::SeparateRedirection {
|
||||
out: (out_span, out_expr),
|
||||
err: (err_span, err_expr),
|
||||
out: (out_span, out_expr, _),
|
||||
err: (err_span, err_expr, _),
|
||||
} => {
|
||||
let mut output = vec![(*out_span, FlatShape::Redirection)];
|
||||
output.append(&mut flatten_expression(working_set, out_expr));
|
||||
@ -545,7 +545,7 @@ pub fn flatten_pipeline_element(
|
||||
}
|
||||
PipelineElement::SameTargetRedirection {
|
||||
cmd: (cmd_span, cmd_expr),
|
||||
redirection: (redirect_span, redirect_expr),
|
||||
redirection: (redirect_span, redirect_expr, _),
|
||||
} => {
|
||||
let mut output = if let Some(span) = cmd_span {
|
||||
let mut output = vec![(*span, FlatShape::Pipe)];
|
||||
|
@ -8,8 +8,11 @@ pub enum TokenContents {
|
||||
PipePipe,
|
||||
Semicolon,
|
||||
OutGreaterThan,
|
||||
OutGreaterGreaterThan,
|
||||
ErrGreaterThan,
|
||||
ErrGreaterGreaterThan,
|
||||
OutErrGreaterThan,
|
||||
OutErrGreaterGreaterThan,
|
||||
Eol,
|
||||
}
|
||||
|
||||
@ -260,14 +263,26 @@ pub fn lex_item(
|
||||
contents: TokenContents::OutGreaterThan,
|
||||
span,
|
||||
},
|
||||
b"out>>" | b"o>>" => Token {
|
||||
contents: TokenContents::OutGreaterGreaterThan,
|
||||
span,
|
||||
},
|
||||
b"err>" | b"e>" => Token {
|
||||
contents: TokenContents::ErrGreaterThan,
|
||||
span,
|
||||
},
|
||||
b"err>>" | b"e>>" => Token {
|
||||
contents: TokenContents::ErrGreaterGreaterThan,
|
||||
span,
|
||||
},
|
||||
b"out+err>" | b"err+out>" | b"o+e>" | b"e+o>" => Token {
|
||||
contents: TokenContents::OutErrGreaterThan,
|
||||
span,
|
||||
},
|
||||
b"out+err>>" | b"err+out>>" | b"o+e>>" | b"e+o>>" => Token {
|
||||
contents: TokenContents::OutErrGreaterGreaterThan,
|
||||
span,
|
||||
},
|
||||
b"&&" => {
|
||||
err = Some(ParseError::ShellAndAnd(span));
|
||||
Token {
|
||||
|
@ -37,16 +37,19 @@ impl LiteCommand {
|
||||
#[derive(Debug)]
|
||||
pub enum LiteElement {
|
||||
Command(Option<Span>, LiteCommand),
|
||||
Redirection(Span, Redirection, LiteCommand),
|
||||
// final field indicates if it's in append mode
|
||||
Redirection(Span, Redirection, LiteCommand, bool),
|
||||
// SeparateRedirection variant can only be generated by two different Redirection variant
|
||||
// final bool field indicates if it's in append mode
|
||||
SeparateRedirection {
|
||||
out: (Span, LiteCommand),
|
||||
err: (Span, LiteCommand),
|
||||
out: (Span, LiteCommand, bool),
|
||||
err: (Span, LiteCommand, bool),
|
||||
},
|
||||
// SameTargetRedirection variant can only be generated by Command with Redirection::OutAndErr
|
||||
// redirection's final bool field indicates if it's in append mode
|
||||
SameTargetRedirection {
|
||||
cmd: (Option<Span>, LiteCommand),
|
||||
redirection: (Span, LiteCommand),
|
||||
redirection: (Span, LiteCommand, bool),
|
||||
},
|
||||
}
|
||||
|
||||
@ -72,10 +75,10 @@ impl LitePipeline {
|
||||
self.commands.is_empty()
|
||||
}
|
||||
|
||||
pub fn exists(&self, new_target: Redirection) -> bool {
|
||||
pub fn exists(&self, new_target: &Redirection) -> bool {
|
||||
for cmd in &self.commands {
|
||||
if let LiteElement::Redirection(_, exists_target, _) = cmd {
|
||||
if exists_target == &new_target {
|
||||
if let LiteElement::Redirection(_, exists_target, _, _) = cmd {
|
||||
if exists_target == new_target {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -121,7 +124,12 @@ impl LiteBlock {
|
||||
if let LiteElement::Command(..) = cmd {
|
||||
cmd_index = Some(index);
|
||||
}
|
||||
if let LiteElement::Redirection(_span, Redirection::StdoutAndStderr, _target_cmd) = cmd
|
||||
if let LiteElement::Redirection(
|
||||
_span,
|
||||
Redirection::StdoutAndStderr,
|
||||
_target_cmd,
|
||||
_is_append_mode,
|
||||
) = cmd
|
||||
{
|
||||
outerr_index = Some(index);
|
||||
break;
|
||||
@ -134,14 +142,14 @@ impl LiteBlock {
|
||||
// `outerr_redirect` and `cmd` should always be `LiteElement::Command` and `LiteElement::Redirection`
|
||||
if let (
|
||||
LiteElement::Command(cmd_span, lite_cmd),
|
||||
LiteElement::Redirection(span, _, outerr_cmd),
|
||||
LiteElement::Redirection(span, _, outerr_cmd, is_append_mode),
|
||||
) = (cmd, outerr_redirect)
|
||||
{
|
||||
pipeline.insert(
|
||||
cmd_index,
|
||||
LiteElement::SameTargetRedirection {
|
||||
cmd: (cmd_span, lite_cmd),
|
||||
redirection: (span, outerr_cmd),
|
||||
redirection: (span, outerr_cmd, is_append_mode),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -154,7 +162,8 @@ impl LiteBlock {
|
||||
let mut stdout_index = None;
|
||||
let mut stderr_index = None;
|
||||
for (index, cmd) in pipeline.commands.iter().enumerate() {
|
||||
if let LiteElement::Redirection(_span, redirection, _target_cmd) = cmd {
|
||||
if let LiteElement::Redirection(_span, redirection, _target_cmd, _is_append_mode) = cmd
|
||||
{
|
||||
match *redirection {
|
||||
Redirection::Stderr => stderr_index = Some(index),
|
||||
Redirection::Stdout => stdout_index = Some(index),
|
||||
@ -178,8 +187,8 @@ impl LiteBlock {
|
||||
};
|
||||
// `out_redirect` and `err_redirect` should always be `LiteElement::Redirection`
|
||||
if let (
|
||||
LiteElement::Redirection(out_span, _, out_command),
|
||||
LiteElement::Redirection(err_span, _, err_command),
|
||||
LiteElement::Redirection(out_span, _, out_command, out_append_mode),
|
||||
LiteElement::Redirection(err_span, _, err_command, err_append_mode),
|
||||
) = (out_redirect, err_redirect)
|
||||
{
|
||||
// using insert with specific index to keep original
|
||||
@ -187,8 +196,8 @@ impl LiteBlock {
|
||||
pipeline.insert(
|
||||
new_indx,
|
||||
LiteElement::SeparateRedirection {
|
||||
out: (out_span, out_command),
|
||||
err: (err_span, err_command),
|
||||
out: (out_span, out_command, out_append_mode),
|
||||
err: (err_span, err_command, err_append_mode),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -244,8 +253,11 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
|
||||
last_token = TokenContents::Item;
|
||||
}
|
||||
TokenContents::OutGreaterThan
|
||||
| TokenContents::OutGreaterGreaterThan
|
||||
| TokenContents::ErrGreaterThan
|
||||
| TokenContents::OutErrGreaterThan => {
|
||||
| TokenContents::ErrGreaterGreaterThan
|
||||
| TokenContents::OutErrGreaterThan
|
||||
| TokenContents::OutErrGreaterGreaterThan => {
|
||||
if let Some(err) = push_command_to(
|
||||
&mut curr_pipeline,
|
||||
curr_command,
|
||||
@ -375,6 +387,18 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_redirection(connector: TokenContents) -> Option<(Redirection, bool)> {
|
||||
match connector {
|
||||
TokenContents::OutGreaterThan => Some((Redirection::Stdout, false)),
|
||||
TokenContents::OutGreaterGreaterThan => Some((Redirection::Stdout, true)),
|
||||
TokenContents::ErrGreaterThan => Some((Redirection::Stderr, false)),
|
||||
TokenContents::ErrGreaterGreaterThan => Some((Redirection::Stderr, true)),
|
||||
TokenContents::OutErrGreaterThan => Some((Redirection::StdoutAndStderr, false)),
|
||||
TokenContents::OutErrGreaterGreaterThan => Some((Redirection::StdoutAndStderr, true)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// push a `command` to `pipeline`
|
||||
///
|
||||
/// It will return Some(err) if `command` is empty and we want to push a
|
||||
@ -386,24 +410,11 @@ fn push_command_to(
|
||||
last_connector_span: Option<Span>,
|
||||
) -> Option<ParseError> {
|
||||
if !command.is_empty() {
|
||||
match last_connector {
|
||||
TokenContents::OutGreaterThan => {
|
||||
match get_redirection(last_connector) {
|
||||
Some((redirect, is_append_mode)) => {
|
||||
let span = last_connector_span
|
||||
.expect("internal error: redirection missing span information");
|
||||
if pipeline.exists(Redirection::Stdout) {
|
||||
return Some(ParseError::LabeledError(
|
||||
"Redirection can be set only once".into(),
|
||||
"try to remove one".into(),
|
||||
span,
|
||||
));
|
||||
}
|
||||
|
||||
pipeline.push(LiteElement::Redirection(span, Redirection::Stdout, command));
|
||||
}
|
||||
TokenContents::ErrGreaterThan => {
|
||||
let span = last_connector_span
|
||||
.expect("internal error: redirection missing span information");
|
||||
if pipeline.exists(Redirection::Stderr) {
|
||||
if pipeline.exists(&redirect) {
|
||||
return Some(ParseError::LabeledError(
|
||||
"Redirection can be set only once".into(),
|
||||
"try to remove one".into(),
|
||||
@ -413,32 +424,20 @@ fn push_command_to(
|
||||
pipeline.push(LiteElement::Redirection(
|
||||
last_connector_span
|
||||
.expect("internal error: redirection missing span information"),
|
||||
Redirection::Stderr,
|
||||
redirect,
|
||||
command,
|
||||
));
|
||||
}
|
||||
TokenContents::OutErrGreaterThan => {
|
||||
pipeline.push(LiteElement::Redirection(
|
||||
last_connector_span
|
||||
.expect("internal error: redirection missing span information"),
|
||||
Redirection::StdoutAndStderr,
|
||||
command,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
pipeline.push(LiteElement::Command(last_connector_span, command));
|
||||
is_append_mode,
|
||||
))
|
||||
}
|
||||
None => pipeline.push(LiteElement::Command(last_connector_span, command)),
|
||||
}
|
||||
None
|
||||
} else if get_redirection(last_connector).is_some() {
|
||||
Some(ParseError::Expected(
|
||||
"redirection target",
|
||||
last_connector_span.expect("internal error: redirection missing span information"),
|
||||
))
|
||||
} else {
|
||||
match last_connector {
|
||||
TokenContents::OutGreaterThan
|
||||
| TokenContents::ErrGreaterThan
|
||||
| TokenContents::OutErrGreaterThan => Some(ParseError::Expected(
|
||||
"redirection target",
|
||||
last_connector_span.expect("internal error: redirection missing span information"),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -1846,11 +1846,12 @@ pub fn parse_module_block(
|
||||
}
|
||||
}
|
||||
}
|
||||
LiteElement::Redirection(_, _, command) => {
|
||||
LiteElement::Redirection(_, _, command, _) => {
|
||||
block.pipelines.push(garbage_pipeline(&command.parts))
|
||||
}
|
||||
LiteElement::SeparateRedirection {
|
||||
out: (_, command), ..
|
||||
out: (_, command, _),
|
||||
..
|
||||
} => block.pipelines.push(garbage_pipeline(&command.parts)),
|
||||
LiteElement::SameTargetRedirection {
|
||||
cmd: (_, command), ..
|
||||
|
@ -1219,8 +1219,11 @@ fn parse_binary_with_base(
|
||||
TokenContents::Pipe
|
||||
| TokenContents::PipePipe
|
||||
| TokenContents::OutGreaterThan
|
||||
| TokenContents::OutGreaterGreaterThan
|
||||
| TokenContents::ErrGreaterThan
|
||||
| TokenContents::OutErrGreaterThan => {
|
||||
| TokenContents::ErrGreaterGreaterThan
|
||||
| TokenContents::OutErrGreaterThan
|
||||
| TokenContents::OutErrGreaterGreaterThan => {
|
||||
working_set.error(ParseError::Expected("binary", span));
|
||||
return garbage(span);
|
||||
}
|
||||
@ -5362,14 +5365,14 @@ pub fn parse_pipeline(
|
||||
|
||||
PipelineElement::Expression(*span, expr)
|
||||
}
|
||||
LiteElement::Redirection(span, redirection, command) => {
|
||||
LiteElement::Redirection(span, redirection, command, is_append_mode) => {
|
||||
let expr = parse_value(working_set, command.parts[0], &SyntaxShape::Any);
|
||||
|
||||
PipelineElement::Redirection(*span, redirection.clone(), expr)
|
||||
PipelineElement::Redirection(*span, redirection.clone(), expr, *is_append_mode)
|
||||
}
|
||||
LiteElement::SeparateRedirection {
|
||||
out: (out_span, out_command),
|
||||
err: (err_span, err_command),
|
||||
out: (out_span, out_command, out_append_mode),
|
||||
err: (err_span, err_command, err_append_mode),
|
||||
} => {
|
||||
trace!("parsing: pipeline element: separate redirection");
|
||||
let out_expr =
|
||||
@ -5379,13 +5382,13 @@ pub fn parse_pipeline(
|
||||
parse_value(working_set, err_command.parts[0], &SyntaxShape::Any);
|
||||
|
||||
PipelineElement::SeparateRedirection {
|
||||
out: (*out_span, out_expr),
|
||||
err: (*err_span, err_expr),
|
||||
out: (*out_span, out_expr, *out_append_mode),
|
||||
err: (*err_span, err_expr, *err_append_mode),
|
||||
}
|
||||
}
|
||||
LiteElement::SameTargetRedirection {
|
||||
cmd: (cmd_span, command),
|
||||
redirection: (redirect_span, redirect_command),
|
||||
redirection: (redirect_span, redirect_command, is_append_mode),
|
||||
} => {
|
||||
trace!("parsing: pipeline element: same target redirection");
|
||||
let expr = parse_expression(working_set, &command.parts, is_subexpression);
|
||||
@ -5393,7 +5396,7 @@ pub fn parse_pipeline(
|
||||
parse_value(working_set, redirect_command.parts[0], &SyntaxShape::Any);
|
||||
PipelineElement::SameTargetRedirection {
|
||||
cmd: (*cmd_span, expr),
|
||||
redirection: (*redirect_span, redirect_expr),
|
||||
redirection: (*redirect_span, redirect_expr, *is_append_mode),
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -5417,9 +5420,10 @@ pub fn parse_pipeline(
|
||||
} else {
|
||||
match &pipeline.commands[0] {
|
||||
LiteElement::Command(_, command)
|
||||
| LiteElement::Redirection(_, _, command)
|
||||
| LiteElement::Redirection(_, _, command, _)
|
||||
| LiteElement::SeparateRedirection {
|
||||
out: (_, command), ..
|
||||
out: (_, command, _),
|
||||
..
|
||||
} => {
|
||||
let mut pipeline = parse_builtin_commands(working_set, command, is_subexpression);
|
||||
|
||||
@ -5477,7 +5481,7 @@ pub fn parse_pipeline(
|
||||
}
|
||||
LiteElement::SameTargetRedirection {
|
||||
cmd: (span, command),
|
||||
redirection: (redirect_span, redirect_cmd),
|
||||
redirection: (redirect_span, redirect_cmd, is_append_mode),
|
||||
} => {
|
||||
trace!("parsing: pipeline element: same target redirection");
|
||||
let expr = parse_expression(working_set, &command.parts, is_subexpression);
|
||||
@ -5488,7 +5492,7 @@ pub fn parse_pipeline(
|
||||
Pipeline {
|
||||
elements: vec![PipelineElement::SameTargetRedirection {
|
||||
cmd: (*span, expr),
|
||||
redirection: (*redirect_span, redirect_expr),
|
||||
redirection: (*redirect_span, redirect_expr, *is_append_mode),
|
||||
}],
|
||||
}
|
||||
}
|
||||
@ -5520,9 +5524,10 @@ pub fn parse_block(
|
||||
if pipeline.commands.len() == 1 {
|
||||
match &pipeline.commands[0] {
|
||||
LiteElement::Command(_, command)
|
||||
| LiteElement::Redirection(_, _, command)
|
||||
| LiteElement::Redirection(_, _, command, _)
|
||||
| LiteElement::SeparateRedirection {
|
||||
out: (_, command), ..
|
||||
out: (_, command, _),
|
||||
..
|
||||
}
|
||||
| LiteElement::SameTargetRedirection {
|
||||
cmd: (_, command), ..
|
||||
@ -5612,14 +5617,14 @@ pub fn discover_captures_in_pipeline_element(
|
||||
) -> Result<(), ParseError> {
|
||||
match element {
|
||||
PipelineElement::Expression(_, expression)
|
||||
| PipelineElement::Redirection(_, _, expression)
|
||||
| PipelineElement::Redirection(_, _, expression, _)
|
||||
| PipelineElement::And(_, expression)
|
||||
| PipelineElement::Or(_, expression) => {
|
||||
discover_captures_in_expr(working_set, expression, seen, seen_blocks, output)
|
||||
}
|
||||
PipelineElement::SeparateRedirection {
|
||||
out: (_, out_expr),
|
||||
err: (_, err_expr),
|
||||
out: (_, out_expr, _),
|
||||
err: (_, err_expr, _),
|
||||
} => {
|
||||
discover_captures_in_expr(working_set, out_expr, seen, seen_blocks, output)?;
|
||||
discover_captures_in_expr(working_set, err_expr, seen, seen_blocks, output)?;
|
||||
@ -5627,7 +5632,7 @@ pub fn discover_captures_in_pipeline_element(
|
||||
}
|
||||
PipelineElement::SameTargetRedirection {
|
||||
cmd: (_, cmd_expr),
|
||||
redirection: (_, redirect_expr),
|
||||
redirection: (_, redirect_expr, _),
|
||||
} => {
|
||||
discover_captures_in_expr(working_set, cmd_expr, seen, seen_blocks, output)?;
|
||||
discover_captures_in_expr(working_set, redirect_expr, seen, seen_blocks, output)?;
|
||||
@ -5934,28 +5939,38 @@ fn wrap_element_with_collect(
|
||||
PipelineElement::Expression(span, expression) => {
|
||||
PipelineElement::Expression(*span, wrap_expr_with_collect(working_set, expression))
|
||||
}
|
||||
PipelineElement::Redirection(span, redirection, expression) => {
|
||||
PipelineElement::Redirection(span, redirection, expression, is_append_mode) => {
|
||||
PipelineElement::Redirection(
|
||||
*span,
|
||||
redirection.clone(),
|
||||
wrap_expr_with_collect(working_set, expression),
|
||||
*is_append_mode,
|
||||
)
|
||||
}
|
||||
PipelineElement::SeparateRedirection {
|
||||
out: (out_span, out_exp),
|
||||
err: (err_span, err_exp),
|
||||
out: (out_span, out_exp, out_append_mode),
|
||||
err: (err_span, err_exp, err_append_mode),
|
||||
} => PipelineElement::SeparateRedirection {
|
||||
out: (*out_span, wrap_expr_with_collect(working_set, out_exp)),
|
||||
err: (*err_span, wrap_expr_with_collect(working_set, err_exp)),
|
||||
out: (
|
||||
*out_span,
|
||||
wrap_expr_with_collect(working_set, out_exp),
|
||||
*out_append_mode,
|
||||
),
|
||||
err: (
|
||||
*err_span,
|
||||
wrap_expr_with_collect(working_set, err_exp),
|
||||
*err_append_mode,
|
||||
),
|
||||
},
|
||||
PipelineElement::SameTargetRedirection {
|
||||
cmd: (cmd_span, cmd_exp),
|
||||
redirection: (redirect_span, redirect_exp),
|
||||
redirection: (redirect_span, redirect_exp, is_append_mode),
|
||||
} => PipelineElement::SameTargetRedirection {
|
||||
cmd: (*cmd_span, wrap_expr_with_collect(working_set, cmd_exp)),
|
||||
redirection: (
|
||||
*redirect_span,
|
||||
wrap_expr_with_collect(working_set, redirect_exp),
|
||||
*is_append_mode,
|
||||
),
|
||||
},
|
||||
PipelineElement::And(span, expression) => {
|
||||
|
Reference in New Issue
Block a user