Make semicolon works better for internal commands (#6643)

* make semicolon works with some internal command like do

* refactor, make consume external result logic out of eval_external

* update comment
This commit is contained in:
WindSoilder 2022-09-30 20:13:46 +08:00 committed by GitHub
parent ca715bb929
commit 6f59167960
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 56 deletions

View File

@ -11,3 +11,15 @@ fn capture_errors_works() {
assert_eq!(actual.out, "error"); assert_eq!(actual.out, "error");
} }
#[test]
fn do_with_semicolon_break_on_failed_external() {
let actual = nu!(
cwd: ".", pipeline(
r#"
do { nu --not_exist_flag }; `text`
"#
));
assert_eq!(actual.out, "");
}

View File

@ -201,7 +201,7 @@ fn eval_external(
input: PipelineData, input: PipelineData,
redirect_stdout: bool, redirect_stdout: bool,
redirect_stderr: bool, redirect_stderr: bool,
) -> Result<(PipelineData, bool), ShellError> { ) -> Result<PipelineData, ShellError> {
let decl_id = engine_state let decl_id = engine_state
.find_decl("run-external".as_bytes(), &[]) .find_decl("run-external".as_bytes(), &[])
.ok_or(ShellError::ExternalNotSupported(head.span))?; .ok_or(ShellError::ExternalNotSupported(head.span))?;
@ -238,54 +238,7 @@ fn eval_external(
)) ))
} }
// when the external command doesn't redirect output, we eagerly check the result command.run(engine_state, stack, &call, input)
// and find if the command runs to failed.
let mut runs_to_failed = false;
let result = command.run(engine_state, stack, &call, input)?;
if let PipelineData::ExternalStream {
stdout: None,
stderr,
mut exit_code,
span,
metadata,
} = result
{
let exit_code = exit_code.take();
match exit_code {
Some(exit_code_stream) => {
let ctrlc = exit_code_stream.ctrlc.clone();
let exit_code: Vec<Value> = exit_code_stream.into_iter().collect();
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
// if exit_code is not 0, it indicates error occured, return back Err.
if *code != 0 {
runs_to_failed = true;
}
}
Ok((
PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: Some(ListStream::from_stream(exit_code.into_iter(), ctrlc)),
span,
metadata,
},
runs_to_failed,
))
}
None => Ok((
PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: None,
span,
metadata,
},
runs_to_failed,
)),
}
} else {
Ok((result, runs_to_failed))
}
} }
pub fn eval_expression( pub fn eval_expression(
@ -383,7 +336,6 @@ pub fn eval_expression(
false, false,
false, false,
)? )?
.0
.into_value(span)) .into_value(span))
} }
Expr::DateTime(dt) => Ok(Value::Date { Expr::DateTime(dt) => Ok(Value::Date {
@ -682,7 +634,6 @@ pub fn eval_expression_with_input(
redirect_stdout: bool, redirect_stdout: bool,
redirect_stderr: bool, redirect_stderr: bool,
) -> Result<(PipelineData, bool), ShellError> { ) -> Result<(PipelineData, bool), ShellError> {
let mut external_failed = false;
match expr { match expr {
Expression { Expression {
expr: Expr::Call(call), expr: Expr::Call(call),
@ -702,7 +653,7 @@ pub fn eval_expression_with_input(
expr: Expr::ExternalCall(head, args), expr: Expr::ExternalCall(head, args),
.. ..
} => { } => {
let external_result = eval_external( input = eval_external(
engine_state, engine_state,
stack, stack,
head, head,
@ -711,8 +662,6 @@ pub fn eval_expression_with_input(
redirect_stdout, redirect_stdout,
redirect_stderr, redirect_stderr,
)?; )?;
input = external_result.0;
external_failed = external_result.1
} }
Expression { Expression {
@ -728,9 +677,63 @@ pub fn eval_expression_with_input(
elem => { elem => {
input = eval_expression(engine_state, stack, elem)?.into_pipeline_data(); input = eval_expression(engine_state, stack, elem)?.into_pipeline_data();
} }
} };
Ok((input, external_failed)) Ok(might_consume_external_result(input))
}
// if the result is ExternalStream without redirecting output.
// that indicates we have no more commands to execute currently.
// we can try to catch and detect if external command runs to failed.
//
// This is useful to commands with semicolon, we can detect errors early to avoid
// commands after semicolon running.
fn might_consume_external_result(input: PipelineData) -> (PipelineData, bool) {
let mut runs_to_failed = false;
if let PipelineData::ExternalStream {
stdout: None,
stderr,
mut exit_code,
span,
metadata,
} = input
{
let exit_code = exit_code.take();
match exit_code {
Some(exit_code_stream) => {
let ctrlc = exit_code_stream.ctrlc.clone();
let exit_code: Vec<Value> = exit_code_stream.into_iter().collect();
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
// if exit_code is not 0, it indicates error occured, return back Err.
if *code != 0 {
runs_to_failed = true;
}
}
(
PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: Some(ListStream::from_stream(exit_code.into_iter(), ctrlc)),
span,
metadata,
},
runs_to_failed,
)
}
None => (
PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: None,
span,
metadata,
},
runs_to_failed,
),
}
} else {
(input, false)
}
} }
pub fn eval_block( pub fn eval_block(