fix try for external command runs to failed (#7300)

# Description

Fixes: #7298

So `try .. catch` works better on external command failed.

# User-Facing Changes

```
try {nu --testbin fail} catch {print "fail"}
```

After this pr, it will output "fail"

# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.

Co-authored-by: JT <547158+jntrnr@users.noreply.github.com>
This commit is contained in:
WindSoilder 2022-12-02 00:58:32 +08:00 committed by GitHub
parent 718ee3d545
commit f71a45235a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 1 deletions

View File

@ -1,7 +1,9 @@
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Block, Closure, Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type, Value};
use nu_protocol::{
Category, Example, ListStream, PipelineData, Signature, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct Try;
@ -77,6 +79,63 @@ impl Command for Try {
Ok(PipelineData::new(call.head))
}
}
// external command may fail to run
Ok(PipelineData::ExternalStream {
stdout: None,
stderr,
mut exit_code,
span,
metadata,
trim_end_newline,
}) => {
let exit_code = exit_code.take();
let mut failed_to_run = false;
let mut exit_code_stream = None;
if let Some(stream) = exit_code {
let ctrlc = stream.ctrlc.clone();
let exit_code: Vec<Value> = 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 {
failed_to_run = true;
}
}
exit_code_stream = Some(ListStream::from_stream(exit_code.into_iter(), ctrlc));
}
if failed_to_run {
if let Some(catch_block) = catch_block {
let catch_block = engine_state.get_block(catch_block.block_id);
if let Some(var) = catch_block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
let err_value = Value::nothing(call.head);
stack.add_var(*var_id, err_value);
}
}
eval_block(
engine_state,
stack,
catch_block,
PipelineData::new(call.head),
false,
false,
)
} else {
Ok(PipelineData::new(call.head))
}
} else {
Ok(PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: exit_code_stream,
span,
metadata,
trim_end_newline,
})
}
}
Ok(output) => Ok(output),
}
}

View File

@ -36,3 +36,15 @@ fn catch_can_access_error() {
assert!(output.err.contains("External command failed"));
})
}
#[test]
fn external_failed_should_be_catched() {
Playground::setup("try_catch_test", |dirs, _sandbox| {
let output = nu!(
cwd: dirs.test(),
"try { nu --testbin fail; echo 'success' } catch { echo 'fail' }"
);
assert!(output.out.contains("fail"));
})
}