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:
WindSoilder 2023-11-27 21:52:39 +08:00 committed by GitHub
parent 1ff8c2d81d
commit 077d1c8125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 364 additions and 147 deletions

View File

@ -122,11 +122,13 @@ impl NuCompleter {
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::Redirection(_, _, expr) | PipelineElement::Redirection(_, _, expr, _)
| PipelineElement::And(_, expr) | PipelineElement::And(_, expr)
| PipelineElement::Or(_, expr) | PipelineElement::Or(_, expr)
| PipelineElement::SameTargetRedirection { cmd: (_, expr), .. } | PipelineElement::SameTargetRedirection { cmd: (_, expr), .. }
| PipelineElement::SeparateRedirection { out: (_, expr), .. } => { | PipelineElement::SeparateRedirection {
out: (_, expr, _), ..
} => {
let flattened: Vec<_> = flatten_expression(&working_set, &expr); let flattened: Vec<_> = flatten_expression(&working_set, &expr);
let mut spans: Vec<String> = vec![]; let mut spans: Vec<String> = vec![];

View File

@ -252,11 +252,11 @@ fn find_matching_block_end_in_block(
for e in &p.elements { for e in &p.elements {
match e { match e {
PipelineElement::Expression(_, e) PipelineElement::Expression(_, e)
| PipelineElement::Redirection(_, _, e) | PipelineElement::Redirection(_, _, e, _)
| PipelineElement::And(_, e) | PipelineElement::And(_, e)
| PipelineElement::Or(_, e) | PipelineElement::Or(_, e)
| PipelineElement::SameTargetRedirection { cmd: (_, e), .. } | PipelineElement::SameTargetRedirection { cmd: (_, e), .. }
| PipelineElement::SeparateRedirection { out: (_, e), .. } => { | PipelineElement::SeparateRedirection { out: (_, 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

@ -1,7 +1,7 @@
use nu_engine::current_dir; use nu_engine::current_dir;
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_path::expand_path_with; use nu_path::expand_path_with;
use nu_protocol::ast::Call; use nu_protocol::ast::{Call, Expr, Expression};
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, RawStream, ShellError, Signature, Span, Spanned, SyntaxShape, Category, Example, PipelineData, RawStream, ShellError, Signature, Span, Spanned, SyntaxShape,
@ -67,6 +67,24 @@ impl Command for Save {
let append = call.has_flag("append"); let append = call.has_flag("append");
let force = call.has_flag("force"); let force = call.has_flag("force");
let progress = call.has_flag("progress"); let progress = call.has_flag("progress");
let out_append = if let Some(Expression {
expr: Expr::Bool(out_append),
..
}) = call.get_parser_info("out-append")
{
*out_append
} else {
false
};
let err_append = if let Some(Expression {
expr: Expr::Bool(err_append),
..
}) = call.get_parser_info("err-append")
{
*err_append
} else {
false
};
let span = call.head; let span = call.head;
let cwd = current_dir(engine_state, stack)?; let cwd = current_dir(engine_state, stack)?;
@ -87,7 +105,7 @@ impl Command for Save {
match input { match input {
PipelineData::ExternalStream { stdout: None, .. } => { PipelineData::ExternalStream { stdout: None, .. } => {
// Open files to possibly truncate them // Open files to possibly truncate them
let _ = get_files(&path, stderr_path.as_ref(), append, force)?; let _ = get_files(&path, stderr_path.as_ref(), append, false, false, force)?;
Ok(PipelineData::empty()) Ok(PipelineData::empty())
} }
PipelineData::ExternalStream { PipelineData::ExternalStream {
@ -95,7 +113,14 @@ impl Command for Save {
stderr, stderr,
.. ..
} => { } => {
let (file, stderr_file) = get_files(&path, stderr_path.as_ref(), append, force)?; let (file, stderr_file) = get_files(
&path,
stderr_path.as_ref(),
append,
out_append,
err_append,
force,
)?;
// delegate a thread to redirect stderr to result. // delegate a thread to redirect stderr to result.
let handler = stderr.map(|stderr_stream| match stderr_file { let handler = stderr.map(|stderr_stream| match stderr_file {
@ -127,7 +152,14 @@ impl Command for Save {
PipelineData::ListStream(ls, _) PipelineData::ListStream(ls, _)
if raw || prepare_path(&path, append, force)?.0.extension().is_none() => if raw || prepare_path(&path, append, force)?.0.extension().is_none() =>
{ {
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?; let (mut file, _) = get_files(
&path,
stderr_path.as_ref(),
append,
out_append,
err_append,
force,
)?;
for val in ls { for val in ls {
file.write_all(&value_to_bytes(val)?) file.write_all(&value_to_bytes(val)?)
.map_err(|err| ShellError::IOError(err.to_string()))?; .map_err(|err| ShellError::IOError(err.to_string()))?;
@ -143,7 +175,14 @@ impl Command for Save {
input_to_bytes(input, Path::new(&path.item), raw, engine_state, stack, span)?; input_to_bytes(input, Path::new(&path.item), raw, engine_state, stack, span)?;
// Only open file after successful conversion // Only open file after successful conversion
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?; let (mut file, _) = get_files(
&path,
stderr_path.as_ref(),
append,
out_append,
err_append,
force,
)?;
file.write_all(&bytes) file.write_all(&bytes)
.map_err(|err| ShellError::IOError(err.to_string()))?; .map_err(|err| ShellError::IOError(err.to_string()))?;
@ -319,17 +358,19 @@ fn get_files(
path: &Spanned<PathBuf>, path: &Spanned<PathBuf>,
stderr_path: Option<&Spanned<PathBuf>>, stderr_path: Option<&Spanned<PathBuf>>,
append: bool, append: bool,
out_append: bool,
err_append: bool,
force: bool, force: bool,
) -> Result<(File, Option<File>), ShellError> { ) -> Result<(File, Option<File>), ShellError> {
// First check both paths // First check both paths
let (path, path_span) = prepare_path(path, append, force)?; let (path, path_span) = prepare_path(path, append || out_append, force)?;
let stderr_path_and_span = stderr_path let stderr_path_and_span = stderr_path
.as_ref() .as_ref()
.map(|stderr_path| prepare_path(stderr_path, append, force)) .map(|stderr_path| prepare_path(stderr_path, append || err_append, force))
.transpose()?; .transpose()?;
// Only if both files can be used open and possibly truncate them // Only if both files can be used open and possibly truncate them
let file = open_file(path, path_span, append)?; let file = open_file(path, path_span, append || out_append)?;
let stderr_file = stderr_path_and_span let stderr_file = stderr_path_and_span
.map(|(stderr_path, stderr_path_span)| { .map(|(stderr_path, stderr_path_span)| {
@ -342,7 +383,7 @@ fn get_files(
vec![], vec![],
)) ))
} else { } else {
open_file(stderr_path, stderr_path_span, append) open_file(stderr_path, stderr_path_span, append || err_append)
} }
}) })
.transpose()?; .transpose()?;

View File

@ -124,7 +124,7 @@ impl Command for FromNuon {
} else { } else {
match pipeline.elements.remove(0) { match pipeline.elements.remove(0) {
PipelineElement::Expression(_, expression) PipelineElement::Expression(_, expression)
| PipelineElement::Redirection(_, _, expression) | PipelineElement::Redirection(_, _, expression, _)
| PipelineElement::And(_, expression) | PipelineElement::And(_, expression)
| PipelineElement::Or(_, expression) | PipelineElement::Or(_, expression)
| PipelineElement::SameTargetRedirection { | PipelineElement::SameTargetRedirection {
@ -132,7 +132,7 @@ impl Command for FromNuon {
.. ..
} }
| PipelineElement::SeparateRedirection { | PipelineElement::SeparateRedirection {
out: (_, expression), out: (_, expression, _),
.. ..
} => expression, } => expression,
} }

View File

@ -12,6 +12,14 @@ fn redirect_err() {
); );
assert!(output.out.contains("asdfasdfasdf.txt")); assert!(output.out.contains("asdfasdfasdf.txt"));
// check append mode
let output = nu!(
cwd: dirs.test(),
"cat asdfasdfasdf.txt err>> a.txt; cat a.txt"
);
let v: Vec<_> = output.out.match_indices("asdfasdfasdf.txt").collect();
assert_eq!(v.len(), 2);
}) })
} }
@ -25,6 +33,12 @@ fn redirect_err() {
); );
assert!(output.out.contains("true")); assert!(output.out.contains("true"));
let output = nu!(
cwd: dirs.test(),
"vol missingdrive err>> a; (open a | str stats).bytes >= 32"
);
assert!(output.out.contains("true"));
}) })
} }
@ -39,6 +53,10 @@ fn redirect_outerr() {
let output = nu!(cwd: dirs.test(), "cat a"); let output = nu!(cwd: dirs.test(), "cat a");
assert!(output.out.contains("asdfasdfasdf.txt")); assert!(output.out.contains("asdfasdfasdf.txt"));
let output = nu!(cwd: dirs.test(), "cat asdfasdfasdf.txt o+e>> a; cat a");
let v: Vec<_> = output.out.match_indices("asdfasdfasdf.txt").collect();
assert_eq!(v.len(), 2);
}) })
} }
@ -53,6 +71,13 @@ fn redirect_outerr() {
let output = nu!(cwd: dirs.test(), "(open a | str stats).bytes >= 16"); let output = nu!(cwd: dirs.test(), "(open a | str stats).bytes >= 16");
assert!(output.out.contains("true")); assert!(output.out.contains("true"));
nu!(
cwd: dirs.test(),
"vol missingdrive out+err>> a"
);
let output = nu!(cwd: dirs.test(), "(open a | str stats).bytes >= 32");
assert!(output.out.contains("true"));
}) })
} }
@ -65,6 +90,12 @@ fn redirect_out() {
); );
assert!(output.out.contains("hello")); assert!(output.out.contains("hello"));
let output = nu!(
cwd: dirs.test(),
"echo 'hello' out>> a; open a"
);
assert!(output.out.contains("hellohello"));
}) })
} }
@ -124,6 +155,25 @@ fn separate_redirection() {
let expected_err_file = dirs.test().join("err.txt"); let expected_err_file = dirs.test().join("err.txt");
let actual = file_contents(expected_err_file); let actual = file_contents(expected_err_file);
assert!(actual.contains(expect_body)); assert!(actual.contains(expect_body));
#[cfg(not(windows))]
{
sandbox.with_files(vec![FileWithContent("test.sh", script_body)]);
nu!(
cwd: dirs.test(),
"bash test.sh out>> out.txt err>> err.txt"
);
// check for stdout redirection file.
let expected_out_file = dirs.test().join("out.txt");
let actual = file_contents(expected_out_file);
let v: Vec<_> = actual.match_indices("message").collect();
assert_eq!(v.len(), 2);
// check for stderr redirection file.
let expected_err_file = dirs.test().join("err.txt");
let actual = file_contents(expected_err_file);
let v: Vec<_> = actual.match_indices("message").collect();
assert_eq!(v.len(), 2);
}
}, },
) )
} }
@ -152,6 +202,21 @@ fn same_target_redirection_with_too_much_stderr_not_hang_nushell() {
let expected_file = dirs.test().join("another_large_file.txt"); let expected_file = dirs.test().join("another_large_file.txt");
let actual = file_contents(expected_file); let actual = file_contents(expected_file);
assert_eq!(actual, format!("{large_file_body}\n")); assert_eq!(actual, format!("{large_file_body}\n"));
// not hangs in append mode either.
let cloned_body = large_file_body.clone();
large_file_body.push_str(&format!("\n{cloned_body}"));
nu!(
cwd: dirs.test(), pipeline(
"
$env.LARGE = (open --raw a_large_file.txt);
nu --testbin echo_env_stderr LARGE out+err>> another_large_file.txt
"
),
);
let expected_file = dirs.test().join("another_large_file.txt");
let actual = file_contents(expected_file);
assert_eq!(actual, format!("{large_file_body}\n"));
}) })
} }
@ -202,6 +267,16 @@ fn redirection_with_pipeline_works() {
let expected_out_file = dirs.test().join("out.txt"); let expected_out_file = dirs.test().join("out.txt");
let actual = file_contents(expected_out_file); let actual = file_contents(expected_out_file);
assert!(actual.contains(expect_body)); assert!(actual.contains(expect_body));
// check append mode works
nu!(
cwd: dirs.test(),
"bash test.sh o>> out.txt | describe"
);
let expected_out_file = dirs.test().join("out.txt");
let actual = file_contents(expected_out_file);
let v: Vec<_> = actual.match_indices("message").collect();
assert_eq!(v.len(), 2);
}, },
) )
} }
@ -224,6 +299,22 @@ fn redirect_support_variable() {
let expected_out_file = dirs.test().join("tmp_file"); let expected_out_file = dirs.test().join("tmp_file");
let actual = file_contents(expected_out_file); let actual = file_contents(expected_out_file);
assert!(actual.contains("hello there")); assert!(actual.contains("hello there"));
// append mode support variable too.
let output = nu!(
cwd: dirs.test(),
"let x = 'tmp_file'; echo 'hello' out>> $x; open tmp_file"
);
let v: Vec<_> = output.out.match_indices("hello").collect();
assert_eq!(v.len(), 2);
let output = nu!(
cwd: dirs.test(),
"let x = 'tmp_file'; echo 'hello' out+err>> $x; open tmp_file"
);
// check for stdout redirection file.
let v: Vec<_> = output.out.match_indices("hello").collect();
assert_eq!(v.len(), 3);
}) })
} }
@ -243,7 +334,7 @@ fn separate_redirection_support_variable() {
sandbox.with_files(vec![FileWithContent("test.sh", script_body)]); sandbox.with_files(vec![FileWithContent("test.sh", script_body)]);
nu!( nu!(
cwd: dirs.test(), cwd: dirs.test(),
r#"let o_f = "out.txt"; let e_f = "err.txt"; bash test.sh out> $o_f err> $e_f"# r#"let o_f = "out2.txt"; let e_f = "err2.txt"; bash test.sh out> $o_f err> $e_f"#
); );
} }
#[cfg(windows)] #[cfg(windows)]
@ -251,18 +342,38 @@ fn separate_redirection_support_variable() {
sandbox.with_files(vec![FileWithContent("test.bat", script_body)]); sandbox.with_files(vec![FileWithContent("test.bat", script_body)]);
nu!( nu!(
cwd: dirs.test(), cwd: dirs.test(),
r#"let o_f = "out.txt"; let e_f = "err.txt"; cmd /D /c test.bat out> $o_f err> $e_f"# r#"let o_f = "out2.txt"; let e_f = "err2.txt"; cmd /D /c test.bat out> $o_f err> $e_f"#
); );
} }
// check for stdout redirection file. // check for stdout redirection file.
let expected_out_file = dirs.test().join("out.txt"); let expected_out_file = dirs.test().join("out2.txt");
let actual = file_contents(expected_out_file); let actual = file_contents(expected_out_file);
assert!(actual.contains(expect_body)); assert!(actual.contains(expect_body));
// check for stderr redirection file. // check for stderr redirection file.
let expected_err_file = dirs.test().join("err.txt"); let expected_err_file = dirs.test().join("err2.txt");
let actual = file_contents(expected_err_file); let actual = file_contents(expected_err_file);
assert!(actual.contains(expect_body)); assert!(actual.contains(expect_body));
#[cfg(not(windows))]
{
sandbox.with_files(vec![FileWithContent("test.sh", script_body)]);
nu!(
cwd: dirs.test(),
r#"let o_f = "out2.txt"; let e_f = "err2.txt"; bash test.sh out>> $o_f err>> $e_f"#
);
// check for stdout redirection file.
let expected_out_file = dirs.test().join("out2.txt");
let actual = file_contents(expected_out_file);
let v: Vec<_> = actual.match_indices("message").collect();
assert_eq!(v.len(), 2);
// check for stderr redirection file.
let expected_err_file = dirs.test().join("err2.txt");
let actual = file_contents(expected_err_file);
let v: Vec<_> = actual.match_indices("message").collect();
assert_eq!(v.len(), 2);
}
}, },
) )
} }

View File

@ -7,7 +7,7 @@ use nu_protocol::{
}, },
engine::{Closure, EngineState, Stack}, engine::{Closure, EngineState, Stack},
DeclId, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Range, Record, DeclId, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Range, Record,
ShellError, Span, Spanned, Unit, Value, VarId, ENV_VARIABLE_ID, ShellError, Span, Spanned, Type, Unit, Value, VarId, ENV_VARIABLE_ID,
}; };
use std::collections::HashMap; use std::collections::HashMap;
use std::thread::{self, JoinHandle}; use std::thread::{self, JoinHandle};
@ -754,7 +754,7 @@ fn eval_element_with_input(
redirect_stdout, redirect_stdout,
redirect_stderr, redirect_stderr,
), ),
PipelineElement::Redirection(span, redirection, expr) => { PipelineElement::Redirection(span, redirection, expr, is_append_mode) => {
match &expr.expr { match &expr.expr {
Expr::String(_) Expr::String(_)
| Expr::FullCellPath(_) | Expr::FullCellPath(_)
@ -793,7 +793,11 @@ fn eval_element_with_input(
}; };
if let Some(save_command) = engine_state.find_decl(b"save", &[]) { if let Some(save_command) = engine_state.find_decl(b"save", &[]) {
let save_call = gen_save_call(save_command, (*span, expr.clone()), None); let save_call = gen_save_call(
save_command,
(*span, expr.clone(), *is_append_mode),
None,
);
match out_stream { match out_stream {
None => { None => {
eval_call(engine_state, stack, &save_call, input).map(|_| { eval_call(engine_state, stack, &save_call, input).map(|_| {
@ -847,8 +851,8 @@ fn eval_element_with_input(
} }
} }
PipelineElement::SeparateRedirection { PipelineElement::SeparateRedirection {
out: (out_span, out_expr), out: (out_span, out_expr, out_append_mode),
err: (err_span, err_expr), err: (err_span, err_expr, err_append_mode),
} => match (&out_expr.expr, &err_expr.expr) { } => match (&out_expr.expr, &err_expr.expr) {
( (
Expr::String(_) Expr::String(_)
@ -867,8 +871,8 @@ fn eval_element_with_input(
}; };
let save_call = gen_save_call( let save_call = gen_save_call(
save_command, save_command,
(*out_span, out_expr.clone()), (*out_span, out_expr.clone(), *out_append_mode),
Some((*err_span, err_expr.clone())), Some((*err_span, err_expr.clone(), *err_append_mode)),
); );
eval_call(engine_state, stack, &save_call, input).map(|_| { eval_call(engine_state, stack, &save_call, input).map(|_| {
@ -901,7 +905,7 @@ fn eval_element_with_input(
}, },
PipelineElement::SameTargetRedirection { PipelineElement::SameTargetRedirection {
cmd: (cmd_span, cmd_exp), cmd: (cmd_span, cmd_exp),
redirection: (redirect_span, redirect_exp), redirection: (redirect_span, redirect_exp, is_append_mode),
} => { } => {
// general idea: eval cmd and call save command to redirect stdout to result. // general idea: eval cmd and call save command to redirect stdout to result.
input = match &cmd_exp.expr { input = match &cmd_exp.expr {
@ -939,6 +943,7 @@ fn eval_element_with_input(
*redirect_span, *redirect_span,
Redirection::Stdout, Redirection::Stdout,
redirect_exp.clone(), redirect_exp.clone(),
*is_append_mode,
), ),
input, input,
redirect_stdout, redirect_stdout,
@ -1021,8 +1026,8 @@ pub fn eval_block(
let next_element = &elements[idx + 1]; let next_element = &elements[idx + 1];
if matches!( if matches!(
next_element, next_element,
PipelineElement::Redirection(_, Redirection::Stderr, _) PipelineElement::Redirection(_, Redirection::Stderr, _, _)
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _) | PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _, _)
| PipelineElement::SeparateRedirection { .. } | PipelineElement::SeparateRedirection { .. }
) { ) {
redirect_stderr = true; redirect_stderr = true;
@ -1033,12 +1038,12 @@ pub fn eval_block(
let next_element = &elements[idx + 1]; let next_element = &elements[idx + 1];
match next_element { match next_element {
// is next element a stdout relative redirection? // is next element a stdout relative redirection?
PipelineElement::Redirection(_, Redirection::Stdout, _) PipelineElement::Redirection(_, Redirection::Stdout, _, _)
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _) | PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _, _)
| PipelineElement::SeparateRedirection { .. } | PipelineElement::SeparateRedirection { .. }
| PipelineElement::Expression(..) => redirect_stdout = true, | PipelineElement::Expression(..) => redirect_stdout = true,
PipelineElement::Redirection(_, Redirection::Stderr, _) => { PipelineElement::Redirection(_, Redirection::Stderr, _, _) => {
// a stderr redirection, but we still need to check for the next 2nd // a stderr redirection, but we still need to check for the next 2nd
// element, to handle for the following case: // element, to handle for the following case:
// cat a.txt err> /dev/null | lines // cat a.txt err> /dev/null | lines
@ -1199,10 +1204,19 @@ fn compute(size: i64, unit: Unit, span: Span) -> Result<Value, ShellError> {
fn gen_save_call( fn gen_save_call(
save_decl_id: DeclId, save_decl_id: DeclId,
out_info: (Span, Expression), out_info: (Span, Expression, bool),
err_info: Option<(Span, Expression)>, err_info: Option<(Span, Expression, bool)>,
) -> Call { ) -> Call {
let (out_span, out_expr) = out_info; let (out_span, out_expr, out_append_mode) = out_info;
let mut call = Call {
decl_id: save_decl_id,
head: out_span,
arguments: vec![],
redirect_stdout: false,
redirect_stderr: false,
parser_info: HashMap::new(),
};
let mut args = vec![ let mut args = vec![
Argument::Positional(out_expr), Argument::Positional(out_expr),
Argument::Named(( Argument::Named((
@ -1222,7 +1236,18 @@ fn gen_save_call(
None, None,
)), )),
]; ];
if let Some((err_span, err_expr)) = err_info { if out_append_mode {
call.set_parser_info(
"out-append".to_string(),
Expression {
expr: Expr::Bool(true),
span: out_span,
ty: Type::Bool,
custom_completion: None,
},
);
}
if let Some((err_span, err_expr, err_append_mode)) = err_info {
args.push(Argument::Named(( args.push(Argument::Named((
Spanned { Spanned {
item: "stderr".into(), item: "stderr".into(),
@ -1230,17 +1255,22 @@ fn gen_save_call(
}, },
None, None,
Some(err_expr), Some(err_expr),
))) )));
if err_append_mode {
call.set_parser_info(
"err-append".to_string(),
Expression {
expr: Expr::Bool(true),
span: err_span,
ty: Type::Bool,
custom_completion: None,
},
);
}
} }
Call { call.arguments.append(&mut args);
decl_id: save_decl_id, call
head: out_span,
arguments: args,
redirect_stdout: false,
redirect_stderr: false,
parser_info: HashMap::new(),
}
} }
/// A job which saves `PipelineData` to a file in a child thread. /// A job which saves `PipelineData` to a file in a child thread.

View File

@ -528,14 +528,14 @@ pub fn flatten_pipeline_element(
flatten_expression(working_set, expr) flatten_expression(working_set, expr)
} }
} }
PipelineElement::Redirection(span, _, expr) => { PipelineElement::Redirection(span, _, expr, _) => {
let mut output = vec![(*span, FlatShape::Redirection)]; let mut output = vec![(*span, FlatShape::Redirection)];
output.append(&mut flatten_expression(working_set, expr)); output.append(&mut flatten_expression(working_set, expr));
output output
} }
PipelineElement::SeparateRedirection { PipelineElement::SeparateRedirection {
out: (out_span, out_expr), out: (out_span, out_expr, _),
err: (err_span, err_expr), err: (err_span, err_expr, _),
} => { } => {
let mut output = vec![(*out_span, FlatShape::Redirection)]; let mut output = vec![(*out_span, FlatShape::Redirection)];
output.append(&mut flatten_expression(working_set, out_expr)); output.append(&mut flatten_expression(working_set, out_expr));
@ -545,7 +545,7 @@ pub fn flatten_pipeline_element(
} }
PipelineElement::SameTargetRedirection { PipelineElement::SameTargetRedirection {
cmd: (cmd_span, cmd_expr), 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 = if let Some(span) = cmd_span {
let mut output = vec![(*span, FlatShape::Pipe)]; let mut output = vec![(*span, FlatShape::Pipe)];

View File

@ -8,8 +8,11 @@ pub enum TokenContents {
PipePipe, PipePipe,
Semicolon, Semicolon,
OutGreaterThan, OutGreaterThan,
OutGreaterGreaterThan,
ErrGreaterThan, ErrGreaterThan,
ErrGreaterGreaterThan,
OutErrGreaterThan, OutErrGreaterThan,
OutErrGreaterGreaterThan,
Eol, Eol,
} }
@ -260,14 +263,26 @@ pub fn lex_item(
contents: TokenContents::OutGreaterThan, contents: TokenContents::OutGreaterThan,
span, span,
}, },
b"out>>" | b"o>>" => Token {
contents: TokenContents::OutGreaterGreaterThan,
span,
},
b"err>" | b"e>" => Token { b"err>" | b"e>" => Token {
contents: TokenContents::ErrGreaterThan, contents: TokenContents::ErrGreaterThan,
span, span,
}, },
b"err>>" | b"e>>" => Token {
contents: TokenContents::ErrGreaterGreaterThan,
span,
},
b"out+err>" | b"err+out>" | b"o+e>" | b"e+o>" => Token { b"out+err>" | b"err+out>" | b"o+e>" | b"e+o>" => Token {
contents: TokenContents::OutErrGreaterThan, contents: TokenContents::OutErrGreaterThan,
span, span,
}, },
b"out+err>>" | b"err+out>>" | b"o+e>>" | b"e+o>>" => Token {
contents: TokenContents::OutErrGreaterGreaterThan,
span,
},
b"&&" => { b"&&" => {
err = Some(ParseError::ShellAndAnd(span)); err = Some(ParseError::ShellAndAnd(span));
Token { Token {

View File

@ -37,16 +37,19 @@ impl LiteCommand {
#[derive(Debug)] #[derive(Debug)]
pub enum LiteElement { pub enum LiteElement {
Command(Option<Span>, LiteCommand), 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 // SeparateRedirection variant can only be generated by two different Redirection variant
// final bool field indicates if it's in append mode
SeparateRedirection { SeparateRedirection {
out: (Span, LiteCommand), out: (Span, LiteCommand, bool),
err: (Span, LiteCommand), err: (Span, LiteCommand, bool),
}, },
// SameTargetRedirection variant can only be generated by Command with Redirection::OutAndErr // SameTargetRedirection variant can only be generated by Command with Redirection::OutAndErr
// redirection's final bool field indicates if it's in append mode
SameTargetRedirection { SameTargetRedirection {
cmd: (Option<Span>, LiteCommand), cmd: (Option<Span>, LiteCommand),
redirection: (Span, LiteCommand), redirection: (Span, LiteCommand, bool),
}, },
} }
@ -72,10 +75,10 @@ impl LitePipeline {
self.commands.is_empty() 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 { for cmd in &self.commands {
if let LiteElement::Redirection(_, exists_target, _) = cmd { if let LiteElement::Redirection(_, exists_target, _, _) = cmd {
if exists_target == &new_target { if exists_target == new_target {
return true; return true;
} }
} }
@ -121,7 +124,12 @@ impl LiteBlock {
if let LiteElement::Command(..) = cmd { if let LiteElement::Command(..) = cmd {
cmd_index = Some(index); 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); outerr_index = Some(index);
break; break;
@ -134,14 +142,14 @@ impl LiteBlock {
// `outerr_redirect` and `cmd` should always be `LiteElement::Command` and `LiteElement::Redirection` // `outerr_redirect` and `cmd` should always be `LiteElement::Command` and `LiteElement::Redirection`
if let ( if let (
LiteElement::Command(cmd_span, lite_cmd), LiteElement::Command(cmd_span, lite_cmd),
LiteElement::Redirection(span, _, outerr_cmd), LiteElement::Redirection(span, _, outerr_cmd, is_append_mode),
) = (cmd, outerr_redirect) ) = (cmd, outerr_redirect)
{ {
pipeline.insert( pipeline.insert(
cmd_index, cmd_index,
LiteElement::SameTargetRedirection { LiteElement::SameTargetRedirection {
cmd: (cmd_span, lite_cmd), 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 stdout_index = None;
let mut stderr_index = None; let mut stderr_index = None;
for (index, cmd) in pipeline.commands.iter().enumerate() { 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 { match *redirection {
Redirection::Stderr => stderr_index = Some(index), Redirection::Stderr => stderr_index = Some(index),
Redirection::Stdout => stdout_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` // `out_redirect` and `err_redirect` should always be `LiteElement::Redirection`
if let ( if let (
LiteElement::Redirection(out_span, _, out_command), LiteElement::Redirection(out_span, _, out_command, out_append_mode),
LiteElement::Redirection(err_span, _, err_command), LiteElement::Redirection(err_span, _, err_command, err_append_mode),
) = (out_redirect, err_redirect) ) = (out_redirect, err_redirect)
{ {
// using insert with specific index to keep original // using insert with specific index to keep original
@ -187,8 +196,8 @@ impl LiteBlock {
pipeline.insert( pipeline.insert(
new_indx, new_indx,
LiteElement::SeparateRedirection { LiteElement::SeparateRedirection {
out: (out_span, out_command), out: (out_span, out_command, out_append_mode),
err: (err_span, err_command), 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; last_token = TokenContents::Item;
} }
TokenContents::OutGreaterThan TokenContents::OutGreaterThan
| TokenContents::OutGreaterGreaterThan
| TokenContents::ErrGreaterThan | TokenContents::ErrGreaterThan
| TokenContents::OutErrGreaterThan => { | TokenContents::ErrGreaterGreaterThan
| TokenContents::OutErrGreaterThan
| TokenContents::OutErrGreaterGreaterThan => {
if let Some(err) = push_command_to( if let Some(err) = push_command_to(
&mut curr_pipeline, &mut curr_pipeline,
curr_command, 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` /// push a `command` to `pipeline`
/// ///
/// It will return Some(err) if `command` is empty and we want to push a /// 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>, last_connector_span: Option<Span>,
) -> Option<ParseError> { ) -> Option<ParseError> {
if !command.is_empty() { if !command.is_empty() {
match last_connector { match get_redirection(last_connector) {
TokenContents::OutGreaterThan => { Some((redirect, is_append_mode)) => {
let span = last_connector_span let span = last_connector_span
.expect("internal error: redirection missing span information"); .expect("internal error: redirection missing span information");
if pipeline.exists(Redirection::Stdout) { if pipeline.exists(&redirect) {
return Some(ParseError::LabeledError(
"Redirection can be set only once".into(),
"try to remove one".into(),
span,
));
}
pipeline.push(LiteElement::Redirection(span, Redirection::Stdout, command));
}
TokenContents::ErrGreaterThan => {
let span = last_connector_span
.expect("internal error: redirection missing span information");
if pipeline.exists(Redirection::Stderr) {
return Some(ParseError::LabeledError( return Some(ParseError::LabeledError(
"Redirection can be set only once".into(), "Redirection can be set only once".into(),
"try to remove one".into(), "try to remove one".into(),
@ -413,32 +424,20 @@ fn push_command_to(
pipeline.push(LiteElement::Redirection( pipeline.push(LiteElement::Redirection(
last_connector_span last_connector_span
.expect("internal error: redirection missing span information"), .expect("internal error: redirection missing span information"),
Redirection::Stderr, redirect,
command, command,
)); is_append_mode,
} ))
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));
} }
None => pipeline.push(LiteElement::Command(last_connector_span, command)),
} }
None 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 { } else {
match last_connector { None
TokenContents::OutGreaterThan
| TokenContents::ErrGreaterThan
| TokenContents::OutErrGreaterThan => Some(ParseError::Expected(
"redirection target",
last_connector_span.expect("internal error: redirection missing span information"),
)),
_ => None,
}
} }
} }

View File

@ -1846,11 +1846,12 @@ pub fn parse_module_block(
} }
} }
} }
LiteElement::Redirection(_, _, command) => { LiteElement::Redirection(_, _, command, _) => {
block.pipelines.push(garbage_pipeline(&command.parts)) block.pipelines.push(garbage_pipeline(&command.parts))
} }
LiteElement::SeparateRedirection { LiteElement::SeparateRedirection {
out: (_, command), .. out: (_, command, _),
..
} => block.pipelines.push(garbage_pipeline(&command.parts)), } => block.pipelines.push(garbage_pipeline(&command.parts)),
LiteElement::SameTargetRedirection { LiteElement::SameTargetRedirection {
cmd: (_, command), .. cmd: (_, command), ..

View File

@ -1219,8 +1219,11 @@ fn parse_binary_with_base(
TokenContents::Pipe TokenContents::Pipe
| TokenContents::PipePipe | TokenContents::PipePipe
| TokenContents::OutGreaterThan | TokenContents::OutGreaterThan
| TokenContents::OutGreaterGreaterThan
| TokenContents::ErrGreaterThan | TokenContents::ErrGreaterThan
| TokenContents::OutErrGreaterThan => { | TokenContents::ErrGreaterGreaterThan
| TokenContents::OutErrGreaterThan
| TokenContents::OutErrGreaterGreaterThan => {
working_set.error(ParseError::Expected("binary", span)); working_set.error(ParseError::Expected("binary", span));
return garbage(span); return garbage(span);
} }
@ -5362,14 +5365,14 @@ pub fn parse_pipeline(
PipelineElement::Expression(*span, expr) 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); 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 { LiteElement::SeparateRedirection {
out: (out_span, out_command), out: (out_span, out_command, out_append_mode),
err: (err_span, err_command), err: (err_span, err_command, err_append_mode),
} => { } => {
trace!("parsing: pipeline element: separate redirection"); trace!("parsing: pipeline element: separate redirection");
let out_expr = let out_expr =
@ -5379,13 +5382,13 @@ pub fn parse_pipeline(
parse_value(working_set, err_command.parts[0], &SyntaxShape::Any); parse_value(working_set, err_command.parts[0], &SyntaxShape::Any);
PipelineElement::SeparateRedirection { PipelineElement::SeparateRedirection {
out: (*out_span, out_expr), out: (*out_span, out_expr, *out_append_mode),
err: (*err_span, err_expr), err: (*err_span, err_expr, *err_append_mode),
} }
} }
LiteElement::SameTargetRedirection { LiteElement::SameTargetRedirection {
cmd: (cmd_span, command), cmd: (cmd_span, command),
redirection: (redirect_span, redirect_command), redirection: (redirect_span, redirect_command, is_append_mode),
} => { } => {
trace!("parsing: pipeline element: same target redirection"); trace!("parsing: pipeline element: same target redirection");
let expr = parse_expression(working_set, &command.parts, is_subexpression); 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); parse_value(working_set, redirect_command.parts[0], &SyntaxShape::Any);
PipelineElement::SameTargetRedirection { PipelineElement::SameTargetRedirection {
cmd: (*cmd_span, expr), 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 { } else {
match &pipeline.commands[0] { match &pipeline.commands[0] {
LiteElement::Command(_, command) LiteElement::Command(_, command)
| LiteElement::Redirection(_, _, command) | LiteElement::Redirection(_, _, command, _)
| LiteElement::SeparateRedirection { | LiteElement::SeparateRedirection {
out: (_, command), .. out: (_, command, _),
..
} => { } => {
let mut pipeline = parse_builtin_commands(working_set, command, is_subexpression); let mut pipeline = parse_builtin_commands(working_set, command, is_subexpression);
@ -5477,7 +5481,7 @@ pub fn parse_pipeline(
} }
LiteElement::SameTargetRedirection { LiteElement::SameTargetRedirection {
cmd: (span, command), cmd: (span, command),
redirection: (redirect_span, redirect_cmd), redirection: (redirect_span, redirect_cmd, is_append_mode),
} => { } => {
trace!("parsing: pipeline element: same target redirection"); trace!("parsing: pipeline element: same target redirection");
let expr = parse_expression(working_set, &command.parts, is_subexpression); let expr = parse_expression(working_set, &command.parts, is_subexpression);
@ -5488,7 +5492,7 @@ pub fn parse_pipeline(
Pipeline { Pipeline {
elements: vec![PipelineElement::SameTargetRedirection { elements: vec![PipelineElement::SameTargetRedirection {
cmd: (*span, expr), 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 { 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::SeparateRedirection { | LiteElement::SeparateRedirection {
out: (_, command), .. out: (_, command, _),
..
} }
| LiteElement::SameTargetRedirection { | LiteElement::SameTargetRedirection {
cmd: (_, command), .. cmd: (_, command), ..
@ -5612,14 +5617,14 @@ pub fn discover_captures_in_pipeline_element(
) -> Result<(), ParseError> { ) -> Result<(), ParseError> {
match element { match element {
PipelineElement::Expression(_, expression) PipelineElement::Expression(_, expression)
| PipelineElement::Redirection(_, _, 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, output) discover_captures_in_expr(working_set, expression, seen, seen_blocks, output)
} }
PipelineElement::SeparateRedirection { PipelineElement::SeparateRedirection {
out: (_, out_expr), out: (_, out_expr, _),
err: (_, err_expr), err: (_, err_expr, _),
} => { } => {
discover_captures_in_expr(working_set, out_expr, seen, seen_blocks, output)?; discover_captures_in_expr(working_set, out_expr, seen, seen_blocks, output)?;
discover_captures_in_expr(working_set, err_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 { PipelineElement::SameTargetRedirection {
cmd: (_, cmd_expr), 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, cmd_expr, seen, seen_blocks, output)?;
discover_captures_in_expr(working_set, redirect_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, expression) => {
PipelineElement::Expression(*span, wrap_expr_with_collect(working_set, 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( PipelineElement::Redirection(
*span, *span,
redirection.clone(), redirection.clone(),
wrap_expr_with_collect(working_set, expression), wrap_expr_with_collect(working_set, expression),
*is_append_mode,
) )
} }
PipelineElement::SeparateRedirection { PipelineElement::SeparateRedirection {
out: (out_span, out_exp), out: (out_span, out_exp, out_append_mode),
err: (err_span, err_exp), err: (err_span, err_exp, err_append_mode),
} => PipelineElement::SeparateRedirection { } => PipelineElement::SeparateRedirection {
out: (*out_span, wrap_expr_with_collect(working_set, out_exp)), out: (
err: (*err_span, wrap_expr_with_collect(working_set, err_exp)), *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 { PipelineElement::SameTargetRedirection {
cmd: (cmd_span, cmd_exp), cmd: (cmd_span, cmd_exp),
redirection: (redirect_span, redirect_exp), redirection: (redirect_span, redirect_exp, is_append_mode),
} => PipelineElement::SameTargetRedirection { } => PipelineElement::SameTargetRedirection {
cmd: (*cmd_span, wrap_expr_with_collect(working_set, cmd_exp)), cmd: (*cmd_span, wrap_expr_with_collect(working_set, cmd_exp)),
redirection: ( redirection: (
*redirect_span, *redirect_span,
wrap_expr_with_collect(working_set, redirect_exp), wrap_expr_with_collect(working_set, redirect_exp),
*is_append_mode,
), ),
}, },
PipelineElement::And(span, expression) => { PipelineElement::And(span, expression) => {

View File

@ -71,7 +71,7 @@ impl Block {
if let Some(last) = last.elements.last() { if let Some(last) = last.elements.last() {
match last { match last {
PipelineElement::Expression(_, expr) => expr.ty.clone(), PipelineElement::Expression(_, expr) => expr.ty.clone(),
PipelineElement::Redirection(_, _, _) => Type::Any, PipelineElement::Redirection(_, _, _, _) => Type::Any,
PipelineElement::SeparateRedirection { .. } => Type::Any, PipelineElement::SeparateRedirection { .. } => Type::Any,
PipelineElement::SameTargetRedirection { .. } => Type::Any, PipelineElement::SameTargetRedirection { .. } => Type::Any,
PipelineElement::And(_, expr) => expr.ty.clone(), PipelineElement::And(_, expr) => expr.ty.clone(),

View File

@ -13,14 +13,17 @@ pub enum Redirection {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PipelineElement { pub enum PipelineElement {
Expression(Option<Span>, Expression), Expression(Option<Span>, Expression),
Redirection(Span, Redirection, Expression), // final field indicates if it's in append mode
Redirection(Span, Redirection, Expression, bool),
// final bool field indicates if it's in append mode
SeparateRedirection { SeparateRedirection {
out: (Span, Expression), out: (Span, Expression, bool),
err: (Span, Expression), err: (Span, Expression, bool),
}, },
// redirection's final bool field indicates if it's in append mode
SameTargetRedirection { SameTargetRedirection {
cmd: (Option<Span>, Expression), cmd: (Option<Span>, Expression),
redirection: (Span, Expression), redirection: (Span, Expression, bool),
}, },
And(Span, Expression), And(Span, Expression),
Or(Span, Expression), Or(Span, Expression),
@ -30,9 +33,9 @@ impl PipelineElement {
pub fn expression(&self) -> &Expression { pub fn expression(&self) -> &Expression {
match self { match self {
PipelineElement::Expression(_, expression) => expression, PipelineElement::Expression(_, expression) => expression,
PipelineElement::Redirection(_, _, expression) => expression, PipelineElement::Redirection(_, _, expression, _) => expression,
PipelineElement::SeparateRedirection { PipelineElement::SeparateRedirection {
out: (_, expression), out: (_, expression, _),
.. ..
} => expression, } => expression,
PipelineElement::SameTargetRedirection { PipelineElement::SameTargetRedirection {
@ -52,9 +55,9 @@ impl PipelineElement {
.. ..
} => expression.span, } => expression.span,
PipelineElement::Expression(Some(span), expression) PipelineElement::Expression(Some(span), expression)
| PipelineElement::Redirection(span, _, expression) | PipelineElement::Redirection(span, _, expression, _)
| PipelineElement::SeparateRedirection { | PipelineElement::SeparateRedirection {
out: (span, expression), out: (span, expression, _),
.. ..
} }
| PipelineElement::And(span, expression) | PipelineElement::And(span, expression)
@ -71,7 +74,7 @@ impl PipelineElement {
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::Redirection(_, _, expression) | PipelineElement::Redirection(_, _, expression, _)
| PipelineElement::And(_, expression) | PipelineElement::And(_, expression)
| PipelineElement::Or(_, expression) | PipelineElement::Or(_, expression)
| PipelineElement::SameTargetRedirection { | PipelineElement::SameTargetRedirection {
@ -79,8 +82,8 @@ impl PipelineElement {
.. ..
} => expression.has_in_variable(working_set), } => expression.has_in_variable(working_set),
PipelineElement::SeparateRedirection { PipelineElement::SeparateRedirection {
out: (_, out_expr), out: (_, out_expr, _),
err: (_, err_expr), err: (_, err_expr, _),
} => out_expr.has_in_variable(working_set) || err_expr.has_in_variable(working_set), } => out_expr.has_in_variable(working_set) || err_expr.has_in_variable(working_set),
} }
} }
@ -88,7 +91,7 @@ impl PipelineElement {
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::Redirection(_, _, expression) | PipelineElement::Redirection(_, _, expression, _)
| PipelineElement::And(_, expression) | PipelineElement::And(_, expression)
| PipelineElement::Or(_, expression) | PipelineElement::Or(_, expression)
| PipelineElement::SameTargetRedirection { | PipelineElement::SameTargetRedirection {
@ -96,8 +99,8 @@ impl PipelineElement {
.. ..
} => expression.replace_in_variable(working_set, new_var_id), } => expression.replace_in_variable(working_set, new_var_id),
PipelineElement::SeparateRedirection { PipelineElement::SeparateRedirection {
out: (_, out_expr), out: (_, out_expr, _),
err: (_, err_expr), err: (_, err_expr, _),
} => { } => {
if out_expr.has_in_variable(working_set) { if out_expr.has_in_variable(working_set) {
out_expr.replace_in_variable(working_set, new_var_id) out_expr.replace_in_variable(working_set, new_var_id)
@ -117,7 +120,7 @@ impl PipelineElement {
) { ) {
match self { match self {
PipelineElement::Expression(_, expression) PipelineElement::Expression(_, expression)
| PipelineElement::Redirection(_, _, expression) | PipelineElement::Redirection(_, _, expression, _)
| PipelineElement::And(_, expression) | PipelineElement::And(_, expression)
| PipelineElement::Or(_, expression) | PipelineElement::Or(_, expression)
| PipelineElement::SameTargetRedirection { | PipelineElement::SameTargetRedirection {
@ -125,7 +128,7 @@ impl PipelineElement {
.. ..
} }
| PipelineElement::SeparateRedirection { | PipelineElement::SeparateRedirection {
out: (_, expression), out: (_, expression, _),
.. ..
} => expression.replace_span(working_set, replaced, new_span), } => expression.replace_span(working_set, replaced, new_span),
} }