mirror of
https://github.com/nushell/nushell.git
synced 2025-01-11 08:48:23 +01:00
A set of fixes for stderr redirect (#7219)
# Description This is a set of fixes to `err>` to make it work a bit more predictably. I've also revised the tests, which accidentally tested the wrong thing for redirection, but should be more correct now. # User-Facing Changes _(List of all changes that impact the user experience here. This helps us keep track of breaking changes.)_ # 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.
This commit is contained in:
parent
651e86a3c0
commit
8cca447e8c
@ -74,6 +74,14 @@ pub fn print_table_or_error(
|
|||||||
// Change the engine_state config to use the passed in configuration
|
// Change the engine_state config to use the passed in configuration
|
||||||
engine_state.set_config(config);
|
engine_state.set_config(config);
|
||||||
|
|
||||||
|
if let PipelineData::Value(Value::Error { error }, ..) = &pipeline_data {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
report_error(&working_set, error);
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
match engine_state.find_decl("table".as_bytes(), &[]) {
|
match engine_state.find_decl("table".as_bytes(), &[]) {
|
||||||
Some(decl_id) => {
|
Some(decl_id) => {
|
||||||
let command = engine_state.get_decl(decl_id);
|
let command = engine_state.get_decl(decl_id);
|
||||||
|
@ -528,7 +528,7 @@ pub fn evaluate_repl(
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
let message = err.to_string();
|
let message = err.to_string();
|
||||||
if !message.contains("duration") {
|
if !message.contains("duration") {
|
||||||
println!("Error: {:?}", err);
|
eprintln!("Error: {:?}", err);
|
||||||
// TODO: Identify possible error cases where a hard failure is preferable
|
// TODO: Identify possible error cases where a hard failure is preferable
|
||||||
// Ignoring and reporting could hide bigger problems
|
// Ignoring and reporting could hide bigger problems
|
||||||
// e.g. https://github.com/nushell/nushell/issues/6452
|
// e.g. https://github.com/nushell/nushell/issues/6452
|
||||||
|
@ -168,6 +168,9 @@ impl Command for Do {
|
|||||||
metadata,
|
metadata,
|
||||||
trim_end_newline,
|
trim_end_newline,
|
||||||
}),
|
}),
|
||||||
|
Ok(PipelineData::Value(Value::Error { .. }, ..)) if ignore_shell_errors => {
|
||||||
|
Ok(PipelineData::new(call.head))
|
||||||
|
}
|
||||||
Err(_) if ignore_shell_errors => Ok(PipelineData::new(call.head)),
|
Err(_) if ignore_shell_errors => Ok(PipelineData::new(call.head)),
|
||||||
r => r,
|
r => r,
|
||||||
}
|
}
|
||||||
|
@ -390,7 +390,7 @@ impl ExternalCommand {
|
|||||||
use std::os::unix::process::ExitStatusExt;
|
use std::os::unix::process::ExitStatusExt;
|
||||||
if x.core_dumped() {
|
if x.core_dumped() {
|
||||||
let style = Style::new().bold().on(Color::Red);
|
let style = Style::new().bold().on(Color::Red);
|
||||||
println!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
style.paint(format!(
|
style.paint(format!(
|
||||||
"nushell: oops, process '{commandname}' core dumped"
|
"nushell: oops, process '{commandname}' core dumped"
|
||||||
|
@ -9,6 +9,8 @@ fn capture_errors_works() {
|
|||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
|
eprintln!("actual.err: {:?}", actual.err);
|
||||||
|
|
||||||
assert!(actual.err.contains("column_not_found"));
|
assert!(actual.err.contains("column_not_found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +67,7 @@ fn ignore_shell_errors_works_for_external_with_semicolon() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".", pipeline(
|
cwd: ".", pipeline(
|
||||||
r#"
|
r#"
|
||||||
do -s { fail }; `text`
|
do -s { open asdfasdf.txt }; "text"
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -323,7 +323,7 @@ fn parse_string_as_script() {
|
|||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
println!("the out put is {}", actual.err);
|
println!("the output is {}", actual.err);
|
||||||
assert!(actual.err.contains("Failed to parse content"));
|
assert!(actual.err.contains("Failed to parse content"));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,10 @@ fn redirect_err() {
|
|||||||
Playground::setup("redirect_err_test", |dirs, _sandbox| {
|
Playground::setup("redirect_err_test", |dirs, _sandbox| {
|
||||||
let output = nu!(
|
let output = nu!(
|
||||||
cwd: dirs.test(),
|
cwd: dirs.test(),
|
||||||
"cat asdfasdfasdf.txt err> a; cat a"
|
"cat asdfasdfasdf.txt err> a.txt; cat a.txt"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(output.err.contains("asdfasdfasdf.txt"));
|
assert!(output.out.contains("asdfasdfasdf.txt"));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,10 +20,10 @@ fn redirect_err() {
|
|||||||
Playground::setup("redirect_err_test", |dirs, _sandbox| {
|
Playground::setup("redirect_err_test", |dirs, _sandbox| {
|
||||||
let output = nu!(
|
let output = nu!(
|
||||||
cwd: dirs.test(),
|
cwd: dirs.test(),
|
||||||
"type asdfasdfasdf.txt err> a; type a"
|
"dir missingapplication err> a; (open a | size).bytes >= 16"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(output.err.contains("asdfasdfasdf.txt"));
|
assert!(output.out.contains("true"));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ fn redirect_outerr() {
|
|||||||
"cat asdfasdfasdf.txt out+err> a; cat a"
|
"cat asdfasdfasdf.txt out+err> a; cat a"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(output.err.contains("asdfasdfasdf.txt"));
|
assert!(output.out.contains("asdfasdfasdf.txt"));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,10 +46,10 @@ fn redirect_outerr() {
|
|||||||
Playground::setup("redirect_outerr_test", |dirs, _sandbox| {
|
Playground::setup("redirect_outerr_test", |dirs, _sandbox| {
|
||||||
let output = nu!(
|
let output = nu!(
|
||||||
cwd: dirs.test(),
|
cwd: dirs.test(),
|
||||||
"type asdfasdfasdf.txt out+err> a; type a"
|
"dir missingapplication out+err> a; (open a | size).bytes >= 16"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(output.err.contains("asdfasdfasdf.txt"));
|
assert!(output.out.contains("true"));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -669,7 +669,7 @@ pub fn eval_expression(
|
|||||||
/// into the call and gets out the result
|
/// into the call and gets out the result
|
||||||
/// Otherwise, invokes the expression
|
/// Otherwise, invokes the expression
|
||||||
///
|
///
|
||||||
/// It returns PipelineData with a boolean flag, indicate that if the external runs to failed.
|
/// It returns PipelineData with a boolean flag, indicating if the external failed to run.
|
||||||
/// The boolean flag **may only be true** for external calls, for internal calls, it always to be false.
|
/// The boolean flag **may only be true** for external calls, for internal calls, it always to be false.
|
||||||
pub fn eval_expression_with_input(
|
pub fn eval_expression_with_input(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -957,6 +957,14 @@ pub fn eval_block(
|
|||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|
||||||
while i < pipeline.elements.len() {
|
while i < pipeline.elements.len() {
|
||||||
|
let redirect_stderr = redirect_stderr
|
||||||
|
|| ((i < pipeline.elements.len() - 1)
|
||||||
|
&& (matches!(
|
||||||
|
pipeline.elements[i + 1],
|
||||||
|
PipelineElement::Redirection(_, Redirection::Stderr, _)
|
||||||
|
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _)
|
||||||
|
)));
|
||||||
|
|
||||||
// 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,
|
||||||
@ -971,20 +979,28 @@ pub fn eval_block(
|
|||||||
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _)
|
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _)
|
||||||
| PipelineElement::Expression(..)
|
| PipelineElement::Expression(..)
|
||||||
)),
|
)),
|
||||||
redirect_stderr
|
redirect_stderr,
|
||||||
|| ((i < pipeline.elements.len() - 1)
|
);
|
||||||
&& (matches!(
|
|
||||||
pipeline.elements[i + 1],
|
match (eval_result, redirect_stderr) {
|
||||||
PipelineElement::Redirection(_, Redirection::Stderr, _)
|
(Ok((pipeline_data, _)), true) => {
|
||||||
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _)
|
input = pipeline_data;
|
||||||
))),
|
|
||||||
)?;
|
// external command may runs to failed
|
||||||
input = eval_result.0;
|
// make early return so remaining commands will not be executed.
|
||||||
// external command may runs to failed
|
// don't return `Err(ShellError)`, so nushell wouldn't show extra error message.
|
||||||
// make early return so remaining commands will not be executed.
|
}
|
||||||
// don't return `Err(ShellError)`, so nushell wouldn't show extra error message.
|
(Err(error), true) => input = PipelineData::Value(Value::Error { error }, None),
|
||||||
if eval_result.1 {
|
(output, false) => {
|
||||||
return Ok(input);
|
let output = output?;
|
||||||
|
input = output.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 output.1 {
|
||||||
|
return Ok(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
|
@ -5999,19 +5999,38 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
|
|||||||
if last_token != TokenContents::Pipe && last_token != TokenContents::OutGreaterThan
|
if last_token != TokenContents::Pipe && last_token != TokenContents::OutGreaterThan
|
||||||
{
|
{
|
||||||
if !curr_command.is_empty() {
|
if !curr_command.is_empty() {
|
||||||
if last_connector == TokenContents::OutGreaterThan
|
match last_connector {
|
||||||
|| last_connector == TokenContents::ErrGreaterThan
|
TokenContents::OutGreaterThan => {
|
||||||
|| last_connector == TokenContents::OutErrGreaterThan
|
curr_pipeline.push(LiteElement::Redirection(
|
||||||
{
|
last_connector_span.expect(
|
||||||
curr_pipeline.push(LiteElement::Redirection(
|
"internal error: redirection missing span information",
|
||||||
last_connector_span
|
),
|
||||||
.expect("internal error: redirection missing span information"),
|
Redirection::Stdout,
|
||||||
Redirection::Stdout,
|
curr_command,
|
||||||
curr_command,
|
));
|
||||||
));
|
}
|
||||||
} else {
|
TokenContents::ErrGreaterThan => {
|
||||||
curr_pipeline
|
curr_pipeline.push(LiteElement::Redirection(
|
||||||
.push(LiteElement::Command(last_connector_span, curr_command));
|
last_connector_span.expect(
|
||||||
|
"internal error: redirection missing span information",
|
||||||
|
),
|
||||||
|
Redirection::Stderr,
|
||||||
|
curr_command,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
TokenContents::OutErrGreaterThan => {
|
||||||
|
curr_pipeline.push(LiteElement::Redirection(
|
||||||
|
last_connector_span.expect(
|
||||||
|
"internal error: redirection missing span information",
|
||||||
|
),
|
||||||
|
Redirection::StdoutAndStderr,
|
||||||
|
curr_command,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
curr_pipeline
|
||||||
|
.push(LiteElement::Command(last_connector_span, curr_command));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
curr_command = LiteCommand::new();
|
curr_command = LiteCommand::new();
|
||||||
@ -6033,18 +6052,35 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
|
|||||||
}
|
}
|
||||||
TokenContents::Semicolon => {
|
TokenContents::Semicolon => {
|
||||||
if !curr_command.is_empty() {
|
if !curr_command.is_empty() {
|
||||||
if last_connector == TokenContents::OutGreaterThan
|
match last_connector {
|
||||||
|| last_connector == TokenContents::ErrGreaterThan
|
TokenContents::OutGreaterThan => {
|
||||||
|| last_connector == TokenContents::OutErrGreaterThan
|
curr_pipeline.push(LiteElement::Redirection(
|
||||||
{
|
last_connector_span
|
||||||
curr_pipeline.push(LiteElement::Redirection(
|
.expect("internal error: redirection missing span information"),
|
||||||
last_connector_span
|
Redirection::Stdout,
|
||||||
.expect("internal error: redirection missing span information"),
|
curr_command,
|
||||||
Redirection::Stdout,
|
));
|
||||||
curr_command,
|
}
|
||||||
));
|
TokenContents::ErrGreaterThan => {
|
||||||
} else {
|
curr_pipeline.push(LiteElement::Redirection(
|
||||||
curr_pipeline.push(LiteElement::Command(last_connector_span, curr_command));
|
last_connector_span
|
||||||
|
.expect("internal error: redirection missing span information"),
|
||||||
|
Redirection::Stderr,
|
||||||
|
curr_command,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
TokenContents::OutErrGreaterThan => {
|
||||||
|
curr_pipeline.push(LiteElement::Redirection(
|
||||||
|
last_connector_span
|
||||||
|
.expect("internal error: redirection missing span information"),
|
||||||
|
Redirection::StdoutAndStderr,
|
||||||
|
curr_command,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
curr_pipeline
|
||||||
|
.push(LiteElement::Command(last_connector_span, curr_command));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
curr_command = LiteCommand::new();
|
curr_command = LiteCommand::new();
|
||||||
@ -6054,6 +6090,8 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
|
|||||||
block.push(curr_pipeline);
|
block.push(curr_pipeline);
|
||||||
|
|
||||||
curr_pipeline = LitePipeline::new();
|
curr_pipeline = LitePipeline::new();
|
||||||
|
last_connector = TokenContents::Pipe;
|
||||||
|
last_connector_span = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
last_token = TokenContents::Semicolon;
|
last_token = TokenContents::Semicolon;
|
||||||
|
Loading…
Reference in New Issue
Block a user