mirror of
https://github.com/nushell/nushell.git
synced 2024-11-07 17:14:23 +01:00
Support redirect stderr and stdout+stderr with a pipe (#11708)
# Description Close: #9673 Close: #8277 Close: #10944 This pr introduces the following syntax: 1. `e>|`, pipe stderr to next command. Example: `$env.FOO=bar nu --testbin echo_env_stderr FOO e>| str length` 2. `o+e>|` and `e+o>|`, pipe both stdout and stderr to next command, example: `$env.FOO=bar nu --testbin echo_env_mixed out-err FOO FOO e+o>| str length` Note: it only works for external commands. ~There is no different for internal commands, that is, the following three commands do the same things:~ Edit: it raises errors if we want to pipes for internal commands ``` ❯ ls e>| str length Error: × `e>|` only works with external streams ╭─[entry #1:1:1] 1 │ ls e>| str length · ─┬─ · ╰── `e>|` only works on external streams ╰──── ❯ ls e+o>| str length Error: × `o+e>|` only works with external streams ╭─[entry #2:1:1] 1 │ ls e+o>| str length · ──┬── · ╰── `o+e>|` only works on external streams ╰──── ``` This can help us to avoid some strange issues like the following: `$env.FOO=bar (nu --testbin echo_env_stderr FOO) e>| str length` Which is hard to understand and hard to explain to users. # User-Facing Changes Nan # Tests + Formatting To be done # After Submitting Maybe update documentation about these syntax.
This commit is contained in:
parent
e7f1bf8535
commit
58c6fea60b
@ -129,6 +129,8 @@ 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::ErrPipedExpression(_, expr)
|
||||||
|
| PipelineElement::OutErrPipedExpression(_, expr)
|
||||||
| PipelineElement::Redirection(_, _, expr, _)
|
| PipelineElement::Redirection(_, _, expr, _)
|
||||||
| PipelineElement::And(_, expr)
|
| PipelineElement::And(_, expr)
|
||||||
| PipelineElement::Or(_, expr)
|
| PipelineElement::Or(_, expr)
|
||||||
|
@ -264,6 +264,8 @@ 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::ErrPipedExpression(_, e)
|
||||||
|
| PipelineElement::OutErrPipedExpression(_, e)
|
||||||
| PipelineElement::Redirection(_, _, e, _)
|
| PipelineElement::Redirection(_, _, e, _)
|
||||||
| PipelineElement::And(_, e)
|
| PipelineElement::And(_, e)
|
||||||
| PipelineElement::Or(_, e)
|
| PipelineElement::Or(_, e)
|
||||||
|
@ -124,6 +124,8 @@ impl Command for FromNuon {
|
|||||||
} else {
|
} else {
|
||||||
match pipeline.elements.remove(0) {
|
match pipeline.elements.remove(0) {
|
||||||
PipelineElement::Expression(_, expression)
|
PipelineElement::Expression(_, expression)
|
||||||
|
| PipelineElement::ErrPipedExpression(_, expression)
|
||||||
|
| PipelineElement::OutErrPipedExpression(_, expression)
|
||||||
| PipelineElement::Redirection(_, _, expression, _)
|
| PipelineElement::Redirection(_, _, expression, _)
|
||||||
| PipelineElement::And(_, expression)
|
| PipelineElement::And(_, expression)
|
||||||
| PipelineElement::Or(_, expression)
|
| PipelineElement::Or(_, expression)
|
||||||
|
@ -60,6 +60,26 @@ fn let_pipeline_redirects_externals() {
|
|||||||
assert_eq!(actual.out, "3");
|
assert_eq!(actual.out, "3");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn let_err_pipeline_redirects_externals() {
|
||||||
|
let actual = nu!(
|
||||||
|
r#"let x = with-env [FOO "foo"] {nu --testbin echo_env_stderr FOO e>| str length}; $x"#
|
||||||
|
);
|
||||||
|
|
||||||
|
// have an extra \n, so length is 4.
|
||||||
|
assert_eq!(actual.out, "4");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn let_outerr_pipeline_redirects_externals() {
|
||||||
|
let actual = nu!(
|
||||||
|
r#"let x = with-env [FOO "foo"] {nu --testbin echo_env_stderr FOO o+e>| str length}; $x"#
|
||||||
|
);
|
||||||
|
|
||||||
|
// have an extra \n, so length is 4.
|
||||||
|
assert_eq!(actual.out, "4");
|
||||||
|
}
|
||||||
|
|
||||||
#[ignore]
|
#[ignore]
|
||||||
#[test]
|
#[test]
|
||||||
fn let_with_external_failed() {
|
fn let_with_external_failed() {
|
||||||
|
@ -333,31 +333,71 @@ fn redirection_should_have_a_target() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn redirection_with_pipe() {
|
fn redirection_with_out_pipe() {
|
||||||
use nu_test_support::playground::Playground;
|
use nu_test_support::playground::Playground;
|
||||||
Playground::setup(
|
Playground::setup("redirection with out pipes", |dirs, _| {
|
||||||
"external with many stdout and stderr messages",
|
// check for stdout
|
||||||
|dirs, _| {
|
let actual = nu!(
|
||||||
// check for stdout
|
cwd: dirs.test(),
|
||||||
|
r#"$env.BAZ = "message"; nu --testbin echo_env_mixed out-err BAZ BAZ err> tmp_file | str length"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "8");
|
||||||
|
// check for stderr redirection file.
|
||||||
|
let expected_out_file = dirs.test().join("tmp_file");
|
||||||
|
let actual_len = file_contents(expected_out_file).len();
|
||||||
|
assert_eq!(actual_len, 8);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn redirection_with_err_pipe() {
|
||||||
|
use nu_test_support::playground::Playground;
|
||||||
|
Playground::setup("redirection with err pipe", |dirs, _| {
|
||||||
|
// check for stdout
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(),
|
||||||
|
r#"$env.BAZ = "message"; nu --testbin echo_env_mixed out-err BAZ BAZ out> tmp_file e>| str length"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "8");
|
||||||
|
// check for stdout redirection file.
|
||||||
|
let expected_out_file = dirs.test().join("tmp_file");
|
||||||
|
let actual_len = file_contents(expected_out_file).len();
|
||||||
|
assert_eq!(actual_len, 8);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_redirection_with_outerr_pipe() {
|
||||||
|
Playground::setup("redirection does not accept outerr pipe", |dirs, _| {
|
||||||
|
for redirect_type in ["o>", "e>", "o+e>"] {
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: dirs.test(),
|
cwd: dirs.test(),
|
||||||
r#"$env.BAZ = "message"; nu --testbin echo_env_mixed out-err BAZ BAZ err> tmp_file | str length"#,
|
&format!("echo 3 {redirect_type} a.txt o+e>| str length")
|
||||||
);
|
);
|
||||||
|
assert!(actual.err.contains("not allowed to use with redirection"));
|
||||||
assert_eq!(actual.out, "8");
|
assert!(
|
||||||
// check for stderr redirection file.
|
!dirs.test().join("a.txt").exists(),
|
||||||
let expected_out_file = dirs.test().join("tmp_file");
|
"No file should be created on error"
|
||||||
let actual_len = file_contents(expected_out_file).len();
|
|
||||||
assert_eq!(actual_len, 8);
|
|
||||||
|
|
||||||
// check it inside a function
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: dirs.test(),
|
|
||||||
r#"$env.BAZ = "message"; nu --testbin echo_env_mixed out-err BAZ BAZ err> tmp_file; print aa"#,
|
|
||||||
);
|
);
|
||||||
assert!(actual.out.contains("messageaa"));
|
}
|
||||||
},
|
|
||||||
)
|
// test for separate redirection
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(),
|
||||||
|
"echo 3 o> a.txt e> b.txt o+e>| str length"
|
||||||
|
);
|
||||||
|
assert!(actual.err.contains("not allowed to use with redirection"));
|
||||||
|
assert!(
|
||||||
|
!dirs.test().join("a.txt").exists(),
|
||||||
|
"No file should be created on error"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!dirs.test().join("b.txt").exists(),
|
||||||
|
"No file should be created on error"
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -7,8 +7,8 @@ use nu_protocol::{
|
|||||||
},
|
},
|
||||||
engine::{Closure, EngineState, Stack},
|
engine::{Closure, EngineState, Stack},
|
||||||
eval_base::Eval,
|
eval_base::Eval,
|
||||||
Config, DeclId, IntoPipelineData, PipelineData, ShellError, Span, Spanned, Type, Value, VarId,
|
Config, DeclId, IntoPipelineData, PipelineData, RawStream, ShellError, Span, Spanned, Type,
|
||||||
ENV_VARIABLE_ID,
|
Value, VarId, ENV_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use std::thread::{self, JoinHandle};
|
use std::thread::{self, JoinHandle};
|
||||||
use std::{borrow::Cow, collections::HashMap};
|
use std::{borrow::Cow, collections::HashMap};
|
||||||
@ -391,6 +391,7 @@ fn might_consume_external_result(input: PipelineData) -> (PipelineData, bool) {
|
|||||||
input.is_external_failed()
|
input.is_external_failed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn eval_element_with_input(
|
fn eval_element_with_input(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
@ -398,17 +399,85 @@ fn eval_element_with_input(
|
|||||||
mut input: PipelineData,
|
mut input: PipelineData,
|
||||||
redirect_stdout: bool,
|
redirect_stdout: bool,
|
||||||
redirect_stderr: bool,
|
redirect_stderr: bool,
|
||||||
|
redirect_combine: bool,
|
||||||
stderr_writer_jobs: &mut Vec<DataSaveJob>,
|
stderr_writer_jobs: &mut Vec<DataSaveJob>,
|
||||||
) -> Result<(PipelineData, bool), ShellError> {
|
) -> Result<(PipelineData, bool), ShellError> {
|
||||||
match element {
|
match element {
|
||||||
PipelineElement::Expression(_, expr) => eval_expression_with_input(
|
PipelineElement::Expression(pipe_span, expr)
|
||||||
engine_state,
|
| PipelineElement::OutErrPipedExpression(pipe_span, expr) => {
|
||||||
stack,
|
if matches!(element, PipelineElement::OutErrPipedExpression(..))
|
||||||
expr,
|
&& !matches!(input, PipelineData::ExternalStream { .. })
|
||||||
input,
|
{
|
||||||
redirect_stdout,
|
return Err(ShellError::GenericError {
|
||||||
redirect_stderr,
|
error: "`o+e>|` only works with external streams".into(),
|
||||||
),
|
msg: "`o+e>|` only works on external streams".into(),
|
||||||
|
span: *pipe_span,
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
match expr {
|
||||||
|
Expression {
|
||||||
|
expr: Expr::ExternalCall(head, args, is_subexpression),
|
||||||
|
..
|
||||||
|
} if redirect_combine => {
|
||||||
|
let result = eval_external(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
head,
|
||||||
|
args,
|
||||||
|
input,
|
||||||
|
RedirectTarget::CombinedPipe,
|
||||||
|
*is_subexpression,
|
||||||
|
)?;
|
||||||
|
Ok(might_consume_external_result(result))
|
||||||
|
}
|
||||||
|
_ => eval_expression_with_input(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
expr,
|
||||||
|
input,
|
||||||
|
redirect_stdout,
|
||||||
|
redirect_stderr,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PipelineElement::ErrPipedExpression(pipe_span, expr) => {
|
||||||
|
let input = match input {
|
||||||
|
PipelineData::ExternalStream {
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
exit_code,
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
trim_end_newline,
|
||||||
|
} => PipelineData::ExternalStream {
|
||||||
|
stdout: stderr, // swap stderr and stdout to get stderr piped feature.
|
||||||
|
stderr: stdout,
|
||||||
|
exit_code,
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
trim_end_newline,
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::GenericError {
|
||||||
|
error: "`e>|` only works with external streams".into(),
|
||||||
|
msg: "`e>|` only works on external streams".into(),
|
||||||
|
span: *pipe_span,
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
eval_expression_with_input(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
expr,
|
||||||
|
input,
|
||||||
|
redirect_stdout,
|
||||||
|
redirect_stderr,
|
||||||
|
)
|
||||||
|
}
|
||||||
PipelineElement::Redirection(span, redirection, expr, is_append_mode) => {
|
PipelineElement::Redirection(span, redirection, expr, is_append_mode) => {
|
||||||
match &expr.expr {
|
match &expr.expr {
|
||||||
Expr::String(_)
|
Expr::String(_)
|
||||||
@ -420,32 +489,8 @@ fn eval_element_with_input(
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// when nushell get Stderr Redirection, we want to take `stdout` part of `input`
|
let (input, result_out_stream, result_is_out) =
|
||||||
// so this stdout stream can be handled by next command.
|
adjust_stream_for_input_and_output(input, redirection);
|
||||||
let (input, out_stream) = match (redirection, input) {
|
|
||||||
(
|
|
||||||
Redirection::Stderr,
|
|
||||||
PipelineData::ExternalStream {
|
|
||||||
stdout,
|
|
||||||
stderr,
|
|
||||||
exit_code,
|
|
||||||
span,
|
|
||||||
metadata,
|
|
||||||
trim_end_newline,
|
|
||||||
},
|
|
||||||
) => (
|
|
||||||
PipelineData::ExternalStream {
|
|
||||||
stdout: stderr,
|
|
||||||
stderr: None,
|
|
||||||
exit_code,
|
|
||||||
span,
|
|
||||||
metadata,
|
|
||||||
trim_end_newline,
|
|
||||||
},
|
|
||||||
Some(stdout),
|
|
||||||
),
|
|
||||||
(_, input) => (input, None),
|
|
||||||
};
|
|
||||||
|
|
||||||
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(
|
let save_call = gen_save_call(
|
||||||
@ -453,7 +498,7 @@ fn eval_element_with_input(
|
|||||||
(*span, expr.clone(), *is_append_mode),
|
(*span, expr.clone(), *is_append_mode),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
match out_stream {
|
match result_out_stream {
|
||||||
None => {
|
None => {
|
||||||
eval_call(engine_state, stack, &save_call, input).map(|_| {
|
eval_call(engine_state, stack, &save_call, input).map(|_| {
|
||||||
// save is internal command, normally it exists with non-ExternalStream
|
// save is internal command, normally it exists with non-ExternalStream
|
||||||
@ -472,7 +517,7 @@ fn eval_element_with_input(
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Some(out_stream) => {
|
Some(result_out_stream) => {
|
||||||
// delegate to a different thread
|
// delegate to a different thread
|
||||||
// so nushell won't hang if external command generates both too much
|
// so nushell won't hang if external command generates both too much
|
||||||
// stderr and stdout message
|
// stderr and stdout message
|
||||||
@ -484,11 +529,25 @@ fn eval_element_with_input(
|
|||||||
save_call,
|
save_call,
|
||||||
input,
|
input,
|
||||||
));
|
));
|
||||||
|
let (result_out_stream, result_err_stream) = if result_is_out {
|
||||||
|
(result_out_stream, None)
|
||||||
|
} else {
|
||||||
|
// we need `stdout` to be an empty RawStream
|
||||||
|
// so nushell knows this result is not the last part of a command.
|
||||||
|
(
|
||||||
|
Some(RawStream::new(
|
||||||
|
Box::new(vec![].into_iter()),
|
||||||
|
None,
|
||||||
|
*span,
|
||||||
|
Some(0),
|
||||||
|
)),
|
||||||
|
result_out_stream,
|
||||||
|
)
|
||||||
|
};
|
||||||
Ok(might_consume_external_result(
|
Ok(might_consume_external_result(
|
||||||
PipelineData::ExternalStream {
|
PipelineData::ExternalStream {
|
||||||
stdout: out_stream,
|
stdout: result_out_stream,
|
||||||
stderr: None,
|
stderr: result_err_stream,
|
||||||
exit_code,
|
exit_code,
|
||||||
span: *span,
|
span: *span,
|
||||||
metadata: None,
|
metadata: None,
|
||||||
@ -582,6 +641,7 @@ fn eval_element_with_input(
|
|||||||
input,
|
input,
|
||||||
true,
|
true,
|
||||||
redirect_stderr,
|
redirect_stderr,
|
||||||
|
redirect_combine,
|
||||||
stderr_writer_jobs,
|
stderr_writer_jobs,
|
||||||
)
|
)
|
||||||
.map(|x| x.0)?
|
.map(|x| x.0)?
|
||||||
@ -599,6 +659,7 @@ fn eval_element_with_input(
|
|||||||
input,
|
input,
|
||||||
redirect_stdout,
|
redirect_stdout,
|
||||||
redirect_stderr,
|
redirect_stderr,
|
||||||
|
redirect_combine,
|
||||||
stderr_writer_jobs,
|
stderr_writer_jobs,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -621,6 +682,146 @@ fn eval_element_with_input(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In redirection context, if nushell gets an ExternalStream
|
||||||
|
// it might want to take a stream from `input`(if `input` is `PipelineData::ExternalStream`)
|
||||||
|
// so this stream can be handled by next command.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// 1. get a stderr redirection, we need to take `stdout` out of `input`.
|
||||||
|
// e.g: nu --testbin echo_env FOO e> /dev/null | str length
|
||||||
|
// 2. get a stdout redirection, we need to take `stderr` out of `input`.
|
||||||
|
// e.g: nu --testbin echo_env FOO o> /dev/null e>| str length
|
||||||
|
//
|
||||||
|
// Returns 3 values:
|
||||||
|
// 1. adjusted pipeline data
|
||||||
|
// 2. a result stream which is taken from `input`, it can be handled in next command
|
||||||
|
// 3. a boolean value indicates if result stream should be a stdout stream.
|
||||||
|
fn adjust_stream_for_input_and_output(
|
||||||
|
input: PipelineData,
|
||||||
|
redirection: &Redirection,
|
||||||
|
) -> (PipelineData, Option<Option<RawStream>>, bool) {
|
||||||
|
match (redirection, input) {
|
||||||
|
(
|
||||||
|
Redirection::Stderr,
|
||||||
|
PipelineData::ExternalStream {
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
exit_code,
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
trim_end_newline,
|
||||||
|
},
|
||||||
|
) => (
|
||||||
|
PipelineData::ExternalStream {
|
||||||
|
stdout: stderr,
|
||||||
|
stderr: None,
|
||||||
|
exit_code,
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
trim_end_newline,
|
||||||
|
},
|
||||||
|
Some(stdout),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Redirection::Stdout,
|
||||||
|
PipelineData::ExternalStream {
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
exit_code,
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
trim_end_newline,
|
||||||
|
},
|
||||||
|
) => (
|
||||||
|
PipelineData::ExternalStream {
|
||||||
|
stdout,
|
||||||
|
stderr: None,
|
||||||
|
exit_code,
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
trim_end_newline,
|
||||||
|
},
|
||||||
|
Some(stderr),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(_, input) => (input, None, true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_redirect_stderr_required(elements: &[PipelineElement], idx: usize) -> bool {
|
||||||
|
let elements_length = elements.len();
|
||||||
|
if idx < elements_length - 1 {
|
||||||
|
let next_element = &elements[idx + 1];
|
||||||
|
match next_element {
|
||||||
|
PipelineElement::Redirection(_, Redirection::Stderr, _, _)
|
||||||
|
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _, _)
|
||||||
|
| PipelineElement::SeparateRedirection { .. }
|
||||||
|
| PipelineElement::ErrPipedExpression(..)
|
||||||
|
| PipelineElement::OutErrPipedExpression(..) => return true,
|
||||||
|
PipelineElement::Redirection(_, Redirection::Stdout, _, _) => {
|
||||||
|
// a stderr redirection, but we still need to check for the next 2nd
|
||||||
|
// element, to handle for the following case:
|
||||||
|
// cat a.txt out> /dev/null e>| lines
|
||||||
|
//
|
||||||
|
// we only need to check the next 2nd element because we already make sure
|
||||||
|
// that we don't have duplicate err> like this:
|
||||||
|
// cat a.txt out> /dev/null err> /tmp/a
|
||||||
|
if idx < elements_length - 2 {
|
||||||
|
let next_2nd_element = &elements[idx + 2];
|
||||||
|
if matches!(next_2nd_element, PipelineElement::ErrPipedExpression(..)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_redirect_stdout_required(elements: &[PipelineElement], idx: usize) -> bool {
|
||||||
|
let elements_length = elements.len();
|
||||||
|
if idx < elements_length - 1 {
|
||||||
|
let next_element = &elements[idx + 1];
|
||||||
|
match next_element {
|
||||||
|
// is next element a stdout relative redirection?
|
||||||
|
PipelineElement::Redirection(_, Redirection::Stdout, _, _)
|
||||||
|
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _, _)
|
||||||
|
| PipelineElement::SeparateRedirection { .. }
|
||||||
|
| PipelineElement::Expression(..)
|
||||||
|
| PipelineElement::OutErrPipedExpression(..) => return true,
|
||||||
|
|
||||||
|
PipelineElement::Redirection(_, Redirection::Stderr, _, _) => {
|
||||||
|
// a stderr redirection, but we still need to check for the next 2nd
|
||||||
|
// element, to handle for the following case:
|
||||||
|
// cat a.txt err> /dev/null | lines
|
||||||
|
//
|
||||||
|
// we only need to check the next 2nd element because we already make sure
|
||||||
|
// that we don't have duplicate err> like this:
|
||||||
|
// cat a.txt err> /dev/null err> /tmp/a
|
||||||
|
if idx < elements_length - 2 {
|
||||||
|
let next_2nd_element = &elements[idx + 2];
|
||||||
|
if matches!(next_2nd_element, PipelineElement::Expression(..)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_redirect_combine_required(elements: &[PipelineElement], idx: usize) -> bool {
|
||||||
|
let elements_length = elements.len();
|
||||||
|
idx < elements_length - 1
|
||||||
|
&& matches!(
|
||||||
|
&elements[idx + 1],
|
||||||
|
PipelineElement::OutErrPipedExpression(..)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn eval_block_with_early_return(
|
pub fn eval_block_with_early_return(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
@ -655,50 +856,26 @@ pub fn eval_block(
|
|||||||
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
|
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
|
||||||
let mut stderr_writer_jobs = vec![];
|
let mut stderr_writer_jobs = vec![];
|
||||||
let elements = &pipeline.elements;
|
let elements = &pipeline.elements;
|
||||||
let elements_length = elements.len();
|
let elements_length = pipeline.elements.len();
|
||||||
for (idx, element) in elements.iter().enumerate() {
|
for (idx, element) in elements.iter().enumerate() {
|
||||||
let mut redirect_stdout = redirect_stdout;
|
let mut redirect_stdout = redirect_stdout;
|
||||||
let mut redirect_stderr = redirect_stderr;
|
let mut redirect_stderr = redirect_stderr;
|
||||||
if !redirect_stderr && idx < elements_length - 1 {
|
if !redirect_stderr && is_redirect_stderr_required(elements, idx) {
|
||||||
let next_element = &elements[idx + 1];
|
redirect_stderr = true;
|
||||||
if matches!(
|
|
||||||
next_element,
|
|
||||||
PipelineElement::Redirection(_, Redirection::Stderr, _, _)
|
|
||||||
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _, _)
|
|
||||||
| PipelineElement::SeparateRedirection { .. }
|
|
||||||
) {
|
|
||||||
redirect_stderr = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !redirect_stdout && idx < elements_length - 1 {
|
if !redirect_stdout {
|
||||||
let next_element = &elements[idx + 1];
|
if is_redirect_stdout_required(elements, idx) {
|
||||||
match next_element {
|
redirect_stdout = true;
|
||||||
// is next element a stdout relative redirection?
|
|
||||||
PipelineElement::Redirection(_, Redirection::Stdout, _, _)
|
|
||||||
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _, _)
|
|
||||||
| PipelineElement::SeparateRedirection { .. }
|
|
||||||
| PipelineElement::Expression(..) => redirect_stdout = true,
|
|
||||||
|
|
||||||
PipelineElement::Redirection(_, Redirection::Stderr, _, _) => {
|
|
||||||
// a stderr redirection, but we still need to check for the next 2nd
|
|
||||||
// element, to handle for the following case:
|
|
||||||
// cat a.txt err> /dev/null | lines
|
|
||||||
//
|
|
||||||
// we only need to check the next 2nd element because we already make sure
|
|
||||||
// that we don't have duplicate err> like this:
|
|
||||||
// cat a.txt err> /dev/null err> /tmp/a
|
|
||||||
if idx < elements_length - 2 {
|
|
||||||
let next_2nd_element = &elements[idx + 2];
|
|
||||||
if matches!(next_2nd_element, PipelineElement::Expression(..)) {
|
|
||||||
redirect_stdout = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
} else if idx < elements_length - 1
|
||||||
|
&& matches!(elements[idx + 1], PipelineElement::ErrPipedExpression(..))
|
||||||
|
{
|
||||||
|
redirect_stdout = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let redirect_combine = is_redirect_combine_required(elements, idx);
|
||||||
|
|
||||||
// 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,
|
||||||
@ -707,6 +884,7 @@ pub fn eval_block(
|
|||||||
input,
|
input,
|
||||||
redirect_stdout,
|
redirect_stdout,
|
||||||
redirect_stderr,
|
redirect_stderr,
|
||||||
|
redirect_combine,
|
||||||
&mut stderr_writer_jobs,
|
&mut stderr_writer_jobs,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -560,7 +560,9 @@ 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(span, expr) => {
|
PipelineElement::Expression(span, expr)
|
||||||
|
| PipelineElement::ErrPipedExpression(span, expr)
|
||||||
|
| PipelineElement::OutErrPipedExpression(span, expr) => {
|
||||||
if let Some(span) = span {
|
if let Some(span) = span {
|
||||||
let mut output = vec![(*span, FlatShape::Pipe)];
|
let mut output = vec![(*span, FlatShape::Pipe)];
|
||||||
output.append(&mut flatten_expression(working_set, expr));
|
output.append(&mut flatten_expression(working_set, expr));
|
||||||
|
@ -6,6 +6,8 @@ pub enum TokenContents {
|
|||||||
Comment,
|
Comment,
|
||||||
Pipe,
|
Pipe,
|
||||||
PipePipe,
|
PipePipe,
|
||||||
|
ErrGreaterPipe,
|
||||||
|
OutErrGreaterPipe,
|
||||||
Semicolon,
|
Semicolon,
|
||||||
OutGreaterThan,
|
OutGreaterThan,
|
||||||
OutGreaterGreaterThan,
|
OutGreaterGreaterThan,
|
||||||
@ -485,8 +487,14 @@ fn lex_internal(
|
|||||||
// If the next character is non-newline whitespace, skip it.
|
// If the next character is non-newline whitespace, skip it.
|
||||||
curr_offset += 1;
|
curr_offset += 1;
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, try to consume an unclassified token.
|
let token = try_lex_special_piped_item(input, &mut curr_offset, span_offset);
|
||||||
|
if let Some(token) = token {
|
||||||
|
output.push(token);
|
||||||
|
is_complete = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, try to consume an unclassified token.
|
||||||
let (token, err) = lex_item(
|
let (token, err) = lex_item(
|
||||||
input,
|
input,
|
||||||
&mut curr_offset,
|
&mut curr_offset,
|
||||||
@ -504,3 +512,49 @@ fn lex_internal(
|
|||||||
}
|
}
|
||||||
(output, error)
|
(output, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// trying to lex for the following item:
|
||||||
|
/// e>|, e+o>|, o+e>|
|
||||||
|
///
|
||||||
|
/// It returns Some(token) if we find the item, or else return None.
|
||||||
|
fn try_lex_special_piped_item(
|
||||||
|
input: &[u8],
|
||||||
|
curr_offset: &mut usize,
|
||||||
|
span_offset: usize,
|
||||||
|
) -> Option<Token> {
|
||||||
|
let c = input[*curr_offset];
|
||||||
|
let e_pipe_len = 3;
|
||||||
|
let eo_pipe_len = 5;
|
||||||
|
let offset = *curr_offset;
|
||||||
|
if c == b'e' {
|
||||||
|
// expect `e>|`
|
||||||
|
if (offset + e_pipe_len <= input.len()) && (&input[offset..offset + e_pipe_len] == b"e>|") {
|
||||||
|
*curr_offset += e_pipe_len;
|
||||||
|
return Some(Token::new(
|
||||||
|
TokenContents::ErrGreaterPipe,
|
||||||
|
Span::new(span_offset + offset, span_offset + offset + e_pipe_len),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (offset + eo_pipe_len <= input.len())
|
||||||
|
&& (&input[offset..offset + eo_pipe_len] == b"e+o>|")
|
||||||
|
{
|
||||||
|
*curr_offset += eo_pipe_len;
|
||||||
|
return Some(Token::new(
|
||||||
|
TokenContents::OutErrGreaterPipe,
|
||||||
|
Span::new(span_offset + offset, span_offset + offset + eo_pipe_len),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if c == b'o' {
|
||||||
|
// it can be the following case: `o+e>|`
|
||||||
|
if (offset + eo_pipe_len <= input.len())
|
||||||
|
&& (&input[offset..offset + eo_pipe_len] == b"o+e>|")
|
||||||
|
{
|
||||||
|
*curr_offset += eo_pipe_len;
|
||||||
|
return Some(Token::new(
|
||||||
|
TokenContents::OutErrGreaterPipe,
|
||||||
|
Span::new(span_offset + offset, span_offset + offset + eo_pipe_len),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
@ -37,6 +37,12 @@ impl LiteCommand {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LiteElement {
|
pub enum LiteElement {
|
||||||
Command(Option<Span>, LiteCommand),
|
Command(Option<Span>, LiteCommand),
|
||||||
|
// Similar to LiteElement::Command, except the previous command's output is stderr piped.
|
||||||
|
// e.g: `e>| cmd`
|
||||||
|
ErrPipedCommand(Option<Span>, LiteCommand),
|
||||||
|
// Similar to LiteElement::Command, except the previous command's output is stderr + stdout piped.
|
||||||
|
// e.g: `o+e>| cmd`
|
||||||
|
OutErrPipedCommand(Option<Span>, LiteCommand),
|
||||||
// final field indicates if it's in append mode
|
// final field indicates if it's in append mode
|
||||||
Redirection(Span, Redirection, LiteCommand, bool),
|
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
|
||||||
@ -272,7 +278,9 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
|
|||||||
last_connector = token.contents;
|
last_connector = token.contents;
|
||||||
last_connector_span = Some(token.span);
|
last_connector_span = Some(token.span);
|
||||||
}
|
}
|
||||||
TokenContents::Pipe => {
|
pipe_token @ (TokenContents::Pipe
|
||||||
|
| TokenContents::ErrGreaterPipe
|
||||||
|
| TokenContents::OutErrGreaterPipe) => {
|
||||||
if let Some(err) = push_command_to(
|
if let Some(err) = push_command_to(
|
||||||
&mut curr_pipeline,
|
&mut curr_pipeline,
|
||||||
curr_command,
|
curr_command,
|
||||||
@ -283,8 +291,8 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
curr_command = LiteCommand::new();
|
curr_command = LiteCommand::new();
|
||||||
last_token = TokenContents::Pipe;
|
last_token = *pipe_token;
|
||||||
last_connector = TokenContents::Pipe;
|
last_connector = *pipe_token;
|
||||||
last_connector_span = Some(token.span);
|
last_connector_span = Some(token.span);
|
||||||
}
|
}
|
||||||
TokenContents::Eol => {
|
TokenContents::Eol => {
|
||||||
@ -429,7 +437,35 @@ fn push_command_to(
|
|||||||
is_append_mode,
|
is_append_mode,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
None => pipeline.push(LiteElement::Command(last_connector_span, command)),
|
None => {
|
||||||
|
if last_connector == TokenContents::ErrGreaterPipe {
|
||||||
|
pipeline.push(LiteElement::ErrPipedCommand(last_connector_span, command))
|
||||||
|
} else if last_connector == TokenContents::OutErrGreaterPipe {
|
||||||
|
// Don't allow o+e>| along with redirection.
|
||||||
|
for cmd in &pipeline.commands {
|
||||||
|
if matches!(
|
||||||
|
cmd,
|
||||||
|
LiteElement::Redirection { .. }
|
||||||
|
| LiteElement::SameTargetRedirection { .. }
|
||||||
|
| LiteElement::SeparateRedirection { .. }
|
||||||
|
) {
|
||||||
|
return Some(ParseError::LabeledError(
|
||||||
|
"`o+e>|` pipe is not allowed to use with redirection".into(),
|
||||||
|
"try to use different type of pipe, or remove redirection".into(),
|
||||||
|
last_connector_span
|
||||||
|
.expect("internal error: outerr pipe missing span information"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.push(LiteElement::OutErrPipedCommand(
|
||||||
|
last_connector_span,
|
||||||
|
command,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
pipeline.push(LiteElement::Command(last_connector_span, command))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
} else if get_redirection(last_connector).is_some() {
|
} else if get_redirection(last_connector).is_some() {
|
||||||
|
@ -1702,7 +1702,9 @@ pub fn parse_module_block(
|
|||||||
for pipeline in output.block.iter() {
|
for pipeline in output.block.iter() {
|
||||||
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::ErrPipedCommand(_, command)
|
||||||
|
| LiteElement::OutErrPipedCommand(_, command) => {
|
||||||
let name = working_set.get_span_contents(command.parts[0]);
|
let name = working_set.get_span_contents(command.parts[0]);
|
||||||
|
|
||||||
match name {
|
match name {
|
||||||
|
@ -1299,7 +1299,9 @@ fn parse_binary_with_base(
|
|||||||
}
|
}
|
||||||
TokenContents::Pipe
|
TokenContents::Pipe
|
||||||
| TokenContents::PipePipe
|
| TokenContents::PipePipe
|
||||||
|
| TokenContents::ErrGreaterPipe
|
||||||
| TokenContents::OutGreaterThan
|
| TokenContents::OutGreaterThan
|
||||||
|
| TokenContents::OutErrGreaterPipe
|
||||||
| TokenContents::OutGreaterGreaterThan
|
| TokenContents::OutGreaterGreaterThan
|
||||||
| TokenContents::ErrGreaterThan
|
| TokenContents::ErrGreaterThan
|
||||||
| TokenContents::ErrGreaterGreaterThan
|
| TokenContents::ErrGreaterGreaterThan
|
||||||
@ -5479,7 +5481,9 @@ pub fn parse_pipeline(
|
|||||||
|
|
||||||
for command in &pipeline.commands[1..] {
|
for command in &pipeline.commands[1..] {
|
||||||
match command {
|
match command {
|
||||||
LiteElement::Command(Some(pipe_span), command) => {
|
LiteElement::Command(Some(pipe_span), command)
|
||||||
|
| LiteElement::ErrPipedCommand(Some(pipe_span), command)
|
||||||
|
| LiteElement::OutErrPipedCommand(Some(pipe_span), command) => {
|
||||||
new_command.parts.push(*pipe_span);
|
new_command.parts.push(*pipe_span);
|
||||||
|
|
||||||
new_command.comments.extend_from_slice(&command.comments);
|
new_command.comments.extend_from_slice(&command.comments);
|
||||||
@ -5584,6 +5588,18 @@ pub fn parse_pipeline(
|
|||||||
|
|
||||||
PipelineElement::Expression(*span, expr)
|
PipelineElement::Expression(*span, expr)
|
||||||
}
|
}
|
||||||
|
LiteElement::ErrPipedCommand(span, command) => {
|
||||||
|
trace!("parsing: pipeline element: err piped command");
|
||||||
|
let expr = parse_expression(working_set, &command.parts, is_subexpression);
|
||||||
|
|
||||||
|
PipelineElement::ErrPipedExpression(*span, expr)
|
||||||
|
}
|
||||||
|
LiteElement::OutErrPipedCommand(span, command) => {
|
||||||
|
trace!("parsing: pipeline element: err piped command");
|
||||||
|
let expr = parse_expression(working_set, &command.parts, is_subexpression);
|
||||||
|
|
||||||
|
PipelineElement::OutErrPipedExpression(*span, expr)
|
||||||
|
}
|
||||||
LiteElement::Redirection(span, redirection, command, is_append_mode) => {
|
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);
|
||||||
|
|
||||||
@ -5639,6 +5655,8 @@ pub fn parse_pipeline(
|
|||||||
} else {
|
} else {
|
||||||
match &pipeline.commands[0] {
|
match &pipeline.commands[0] {
|
||||||
LiteElement::Command(_, command)
|
LiteElement::Command(_, command)
|
||||||
|
| LiteElement::ErrPipedCommand(_, command)
|
||||||
|
| LiteElement::OutErrPipedCommand(_, command)
|
||||||
| LiteElement::Redirection(_, _, command, _)
|
| LiteElement::Redirection(_, _, command, _)
|
||||||
| LiteElement::SeparateRedirection {
|
| LiteElement::SeparateRedirection {
|
||||||
out: (_, command, _),
|
out: (_, command, _),
|
||||||
@ -5743,6 +5761,8 @@ 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::ErrPipedCommand(_, command)
|
||||||
|
| LiteElement::OutErrPipedCommand(_, command)
|
||||||
| LiteElement::Redirection(_, _, command, _)
|
| LiteElement::Redirection(_, _, command, _)
|
||||||
| LiteElement::SeparateRedirection {
|
| LiteElement::SeparateRedirection {
|
||||||
out: (_, command, _),
|
out: (_, command, _),
|
||||||
@ -5836,6 +5856,8 @@ pub fn discover_captures_in_pipeline_element(
|
|||||||
) -> Result<(), ParseError> {
|
) -> Result<(), ParseError> {
|
||||||
match element {
|
match element {
|
||||||
PipelineElement::Expression(_, expression)
|
PipelineElement::Expression(_, expression)
|
||||||
|
| PipelineElement::ErrPipedExpression(_, expression)
|
||||||
|
| PipelineElement::OutErrPipedExpression(_, expression)
|
||||||
| PipelineElement::Redirection(_, _, expression, _)
|
| PipelineElement::Redirection(_, _, expression, _)
|
||||||
| PipelineElement::And(_, expression)
|
| PipelineElement::And(_, expression)
|
||||||
| PipelineElement::Or(_, expression) => {
|
| PipelineElement::Or(_, expression) => {
|
||||||
@ -6181,6 +6203,18 @@ 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::ErrPipedExpression(span, expression) => {
|
||||||
|
PipelineElement::ErrPipedExpression(
|
||||||
|
*span,
|
||||||
|
wrap_expr_with_collect(working_set, expression),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PipelineElement::OutErrPipedExpression(span, expression) => {
|
||||||
|
PipelineElement::OutErrPipedExpression(
|
||||||
|
*span,
|
||||||
|
wrap_expr_with_collect(working_set, expression),
|
||||||
|
)
|
||||||
|
}
|
||||||
PipelineElement::Redirection(span, redirection, expression, is_append_mode) => {
|
PipelineElement::Redirection(span, redirection, expression, is_append_mode) => {
|
||||||
PipelineElement::Redirection(
|
PipelineElement::Redirection(
|
||||||
*span,
|
*span,
|
||||||
|
@ -68,6 +68,8 @@ 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::ErrPipedExpression(_, expr) => expr.ty.clone(),
|
||||||
|
PipelineElement::OutErrPipedExpression(_, 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,
|
||||||
|
@ -13,6 +13,8 @@ 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),
|
||||||
|
ErrPipedExpression(Option<Span>, Expression),
|
||||||
|
OutErrPipedExpression(Option<Span>, Expression),
|
||||||
// final field indicates if it's in append mode
|
// final field indicates if it's in append mode
|
||||||
Redirection(Span, Redirection, Expression, bool),
|
Redirection(Span, Redirection, Expression, bool),
|
||||||
// final bool field indicates if it's in append mode
|
// final bool field indicates if it's in append mode
|
||||||
@ -32,7 +34,9 @@ pub enum PipelineElement {
|
|||||||
impl PipelineElement {
|
impl PipelineElement {
|
||||||
pub fn expression(&self) -> &Expression {
|
pub fn expression(&self) -> &Expression {
|
||||||
match self {
|
match self {
|
||||||
PipelineElement::Expression(_, expression) => expression,
|
PipelineElement::Expression(_, expression)
|
||||||
|
| PipelineElement::ErrPipedExpression(_, expression)
|
||||||
|
| PipelineElement::OutErrPipedExpression(_, expression) => expression,
|
||||||
PipelineElement::Redirection(_, _, expression, _) => expression,
|
PipelineElement::Redirection(_, _, expression, _) => expression,
|
||||||
PipelineElement::SeparateRedirection {
|
PipelineElement::SeparateRedirection {
|
||||||
out: (_, expression, _),
|
out: (_, expression, _),
|
||||||
@ -50,11 +54,15 @@ impl PipelineElement {
|
|||||||
pub fn span(&self) -> Span {
|
pub fn span(&self) -> Span {
|
||||||
match self {
|
match self {
|
||||||
PipelineElement::Expression(None, expression)
|
PipelineElement::Expression(None, expression)
|
||||||
|
| PipelineElement::ErrPipedExpression(None, expression)
|
||||||
|
| PipelineElement::OutErrPipedExpression(None, expression)
|
||||||
| PipelineElement::SameTargetRedirection {
|
| PipelineElement::SameTargetRedirection {
|
||||||
cmd: (None, expression),
|
cmd: (None, expression),
|
||||||
..
|
..
|
||||||
} => expression.span,
|
} => expression.span,
|
||||||
PipelineElement::Expression(Some(span), expression)
|
PipelineElement::Expression(Some(span), expression)
|
||||||
|
| PipelineElement::ErrPipedExpression(Some(span), expression)
|
||||||
|
| PipelineElement::OutErrPipedExpression(Some(span), expression)
|
||||||
| PipelineElement::Redirection(span, _, expression, _)
|
| PipelineElement::Redirection(span, _, expression, _)
|
||||||
| PipelineElement::SeparateRedirection {
|
| PipelineElement::SeparateRedirection {
|
||||||
out: (span, expression, _),
|
out: (span, expression, _),
|
||||||
@ -74,6 +82,8 @@ 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::ErrPipedExpression(_, expression)
|
||||||
|
| PipelineElement::OutErrPipedExpression(_, expression)
|
||||||
| PipelineElement::Redirection(_, _, expression, _)
|
| PipelineElement::Redirection(_, _, expression, _)
|
||||||
| PipelineElement::And(_, expression)
|
| PipelineElement::And(_, expression)
|
||||||
| PipelineElement::Or(_, expression)
|
| PipelineElement::Or(_, expression)
|
||||||
@ -96,6 +106,8 @@ impl PipelineElement {
|
|||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
PipelineElement::Expression(_, expression)
|
PipelineElement::Expression(_, expression)
|
||||||
|
| PipelineElement::ErrPipedExpression(_, expression)
|
||||||
|
| PipelineElement::OutErrPipedExpression(_, expression)
|
||||||
| PipelineElement::Redirection(_, _, expression, _)
|
| PipelineElement::Redirection(_, _, expression, _)
|
||||||
| PipelineElement::And(_, expression)
|
| PipelineElement::And(_, expression)
|
||||||
| PipelineElement::Or(_, expression)
|
| PipelineElement::Or(_, expression)
|
||||||
|
@ -142,6 +142,22 @@ fn command_substitution_wont_output_extra_newline() {
|
|||||||
assert_eq!(actual.out, "bar");
|
assert_eq!(actual.out, "bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_err_pipe_works() {
|
||||||
|
let actual = nu!(r#"with-env [FOO "bar"] { nu --testbin echo_env_stderr FOO e>| str length }"#);
|
||||||
|
// there is a `newline` output from nu --testbin
|
||||||
|
assert_eq!(actual.out, "4");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_outerr_pipe_works() {
|
||||||
|
let actual = nu!(
|
||||||
|
r#"with-env [FOO "bar"] { nu --testbin echo_env_mixed out-err FOO FOO o+e>| str length }"#
|
||||||
|
);
|
||||||
|
// there is a `newline` output from nu --testbin
|
||||||
|
assert_eq!(actual.out, "8");
|
||||||
|
}
|
||||||
|
|
||||||
mod it_evaluation {
|
mod it_evaluation {
|
||||||
use super::nu;
|
use super::nu;
|
||||||
use nu_test_support::fs::Stub::{EmptyFile, FileWithContent, FileWithContentToBeTrimmed};
|
use nu_test_support::fs::Stub::{EmptyFile, FileWithContent, FileWithContentToBeTrimmed};
|
||||||
|
@ -1113,6 +1113,18 @@ fn pipe_input_to_print() {
|
|||||||
assert!(actual.err.is_empty());
|
assert!(actual.err.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn err_pipe_input_to_print() {
|
||||||
|
let actual = nu!(r#""foo" e>| print"#);
|
||||||
|
assert!(actual.err.contains("only works on external streams"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn outerr_pipe_input_to_print() {
|
||||||
|
let actual = nu!(r#""foo" o+e>| print"#);
|
||||||
|
assert!(actual.err.contains("only works on external streams"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn command_not_found_error_shows_not_found_2() {
|
fn command_not_found_error_shows_not_found_2() {
|
||||||
let actual = nu!(r#"
|
let actual = nu!(r#"
|
||||||
|
Loading…
Reference in New Issue
Block a user