forked from extern/nushell
Make Semicolon stop on error (#6079)
* introduce external command runs to failed error, and implement semicolon relative logic * ignore test due to semicolon works * not raise ShellError for external commands * update comment * add relative test in for windows * fix type-o Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
This commit is contained in:
parent
558cd58d09
commit
a35a71fd82
@ -92,6 +92,7 @@ impl Command for If {
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)
|
||||
.map(|res| res.0)
|
||||
}
|
||||
} else {
|
||||
eval_expression_with_input(
|
||||
@ -102,6 +103,7 @@ impl Command for If {
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)
|
||||
.map(|res| res.0)
|
||||
}
|
||||
} else {
|
||||
Ok(PipelineData::new(call.head))
|
||||
|
@ -61,7 +61,8 @@ impl Command for Let {
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)?;
|
||||
)?
|
||||
.0;
|
||||
|
||||
//println!("Adding: {:?} to {}", rhs, var_id);
|
||||
|
||||
|
1
crates/nu-command/src/env/let_env.rs
vendored
1
crates/nu-command/src/env/let_env.rs
vendored
@ -43,6 +43,7 @@ impl Command for LetEnv {
|
||||
|
||||
let rhs =
|
||||
eval_expression_with_input(engine_state, stack, keyword_expr, input, false, true)?
|
||||
.0
|
||||
.into_value(call.head);
|
||||
|
||||
if env_var == "PWD" {
|
||||
|
@ -123,6 +123,21 @@ fn double_quote_does_not_expand_path_glob() {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn failed_command_with_semicolon_will_not_execute_following_cmds() {
|
||||
Playground::setup("external failed command with semicolon", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^ls *.abc; echo done
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(!actual.out.contains("done"));
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn explicit_glob_windows() {
|
||||
@ -166,6 +181,21 @@ fn bare_word_expand_path_glob_windows() {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn failed_command_with_semicolon_will_not_execute_following_cmds_windows() {
|
||||
Playground::setup("external failed command with semicolon", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^cargo asdf; echo done
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(!actual.out.contains("done"));
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
// This test case might fail based on the running shell on Windows - CMD vs PowerShell, the reason is
|
||||
|
@ -3,8 +3,8 @@ use nu_path::expand_path_with;
|
||||
use nu_protocol::{
|
||||
ast::{Block, Call, Expr, Expression, Operator},
|
||||
engine::{EngineState, Stack, Visibility},
|
||||
HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Range,
|
||||
ShellError, Span, Spanned, SyntaxShape, Unit, Value, VarId, ENV_VARIABLE_ID,
|
||||
HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, ListStream, PipelineData,
|
||||
Range, ShellError, Span, Spanned, SyntaxShape, Unit, Value, VarId, ENV_VARIABLE_ID,
|
||||
};
|
||||
use nu_utils::stdout_write_all_and_flush;
|
||||
use std::cmp::Ordering;
|
||||
@ -183,6 +183,9 @@ pub fn eval_call(
|
||||
}
|
||||
}
|
||||
|
||||
/// Eval extarnal expression
|
||||
///
|
||||
/// It returns PipelineData with a boolean flag, indicate that if the external runs to failed.
|
||||
fn eval_external(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
@ -191,7 +194,7 @@ fn eval_external(
|
||||
input: PipelineData,
|
||||
redirect_stdout: bool,
|
||||
redirect_stderr: bool,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
) -> Result<(PipelineData, bool), ShellError> {
|
||||
let decl_id = engine_state
|
||||
.find_decl("run-external".as_bytes(), &[])
|
||||
.ok_or(ShellError::ExternalNotSupported(head.span))?;
|
||||
@ -228,7 +231,54 @@ fn eval_external(
|
||||
))
|
||||
}
|
||||
|
||||
command.run(engine_state, stack, &call, input)
|
||||
// when the external command doesn't redirect output, we eagerly check the result
|
||||
// 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(
|
||||
@ -317,6 +367,7 @@ pub fn eval_expression(
|
||||
false,
|
||||
false,
|
||||
)?
|
||||
.0
|
||||
.into_value(span))
|
||||
}
|
||||
Expr::DateTime(dt) => Ok(Value::Date {
|
||||
@ -604,6 +655,9 @@ pub fn eval_expression(
|
||||
/// Checks the expression to see if it's a internal or external call. If so, passes the input
|
||||
/// into the call and gets out the result
|
||||
/// Otherwise, invokes the expression
|
||||
///
|
||||
/// It returns PipelineData with a boolean flag, indicate that if the external runs to failed.
|
||||
/// The boolean flag **may only be true** for external calls, for internal calls, it always to be false.
|
||||
pub fn eval_expression_with_input(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
@ -611,7 +665,8 @@ pub fn eval_expression_with_input(
|
||||
mut input: PipelineData,
|
||||
redirect_stdout: bool,
|
||||
redirect_stderr: bool,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
) -> Result<(PipelineData, bool), ShellError> {
|
||||
let mut external_failed = false;
|
||||
match expr {
|
||||
Expression {
|
||||
expr: Expr::Call(call),
|
||||
@ -631,7 +686,7 @@ pub fn eval_expression_with_input(
|
||||
expr: Expr::ExternalCall(head, args),
|
||||
..
|
||||
} => {
|
||||
input = eval_external(
|
||||
let external_result = eval_external(
|
||||
engine_state,
|
||||
stack,
|
||||
head,
|
||||
@ -640,6 +695,8 @@ pub fn eval_expression_with_input(
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
)?;
|
||||
input = external_result.0;
|
||||
external_failed = external_result.1
|
||||
}
|
||||
|
||||
Expression {
|
||||
@ -657,7 +714,7 @@ pub fn eval_expression_with_input(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(input)
|
||||
Ok((input, external_failed))
|
||||
}
|
||||
|
||||
pub fn eval_block(
|
||||
@ -671,14 +728,22 @@ pub fn eval_block(
|
||||
let num_pipelines = block.len();
|
||||
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
|
||||
for (i, elem) in pipeline.expressions.iter().enumerate() {
|
||||
input = eval_expression_with_input(
|
||||
// if eval internal command failed, it can just make early return with `Err(ShellError)`.
|
||||
let eval_result = eval_expression_with_input(
|
||||
engine_state,
|
||||
stack,
|
||||
elem,
|
||||
input,
|
||||
redirect_stdout || (i != pipeline.expressions.len() - 1),
|
||||
redirect_stderr,
|
||||
)?
|
||||
)?;
|
||||
input = eval_result.0;
|
||||
// external command may runs to failed
|
||||
// make early return so remaining commands will not be executed.
|
||||
// don't return `Err(ShellError)`, so nushell wouldn't show extra error message.
|
||||
if eval_result.1 {
|
||||
return Ok(input);
|
||||
}
|
||||
}
|
||||
|
||||
if pipeline_idx < (num_pipelines) - 1 {
|
||||
@ -789,7 +854,7 @@ pub fn eval_subexpression(
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
for pipeline in block.pipelines.iter() {
|
||||
for expr in pipeline.expressions.iter() {
|
||||
input = eval_expression_with_input(engine_state, stack, expr, input, true, false)?
|
||||
input = eval_expression_with_input(engine_state, stack, expr, input, true, false)?.0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,6 +301,7 @@ mod nu_commands {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "For now we have no way to check LAST_EXIT_CODE in tests, ignore it for now"]
|
||||
fn failed_with_proper_exit_code() {
|
||||
Playground::setup("external failed", |dirs, _sandbox| {
|
||||
let actual = nu!(cwd: dirs.test(), r#"
|
||||
|
Loading…
Reference in New Issue
Block a user