Merge 494d65d95e
into 802bfed173
This commit is contained in:
commit
9afd8f10b2
|
@ -214,7 +214,8 @@ pub fn eval_config_contents(
|
||||||
let prev_file = engine_state.file.take();
|
let prev_file = engine_state.file.take();
|
||||||
engine_state.file = Some(config_path.clone());
|
engine_state.file = Some(config_path.clone());
|
||||||
|
|
||||||
eval_source(
|
// TODO: ignore this error?
|
||||||
|
let _ = eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&contents,
|
&contents,
|
||||||
|
|
|
@ -92,11 +92,7 @@ pub fn evaluate_commands(
|
||||||
t_mode.coerce_str()?.parse().unwrap_or_default();
|
t_mode.coerce_str()?.parse().unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(status) = pipeline.print(engine_state, stack, no_newline, false)? {
|
pipeline.print(engine_state, stack, no_newline, false)?;
|
||||||
if status.code() != 0 {
|
|
||||||
std::process::exit(status.code())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
|
|
||||||
|
|
|
@ -118,11 +118,7 @@ pub fn evaluate_file(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Print the pipeline output of the last command of the file.
|
// Print the pipeline output of the last command of the file.
|
||||||
if let Some(status) = pipeline.print(engine_state, stack, true, false)? {
|
pipeline.print(engine_state, stack, true, false)?;
|
||||||
if status.code() != 0 {
|
|
||||||
std::process::exit(status.code())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoke the main command with arguments.
|
// Invoke the main command with arguments.
|
||||||
// Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace.
|
// Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace.
|
||||||
|
@ -140,7 +136,7 @@ pub fn evaluate_file(
|
||||||
};
|
};
|
||||||
|
|
||||||
if exit_code != 0 {
|
if exit_code != 0 {
|
||||||
std::process::exit(exit_code)
|
std::process::exit(exit_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
|
|
|
@ -97,7 +97,7 @@ pub fn evaluate_repl(
|
||||||
Value::string("0823", Span::unknown()),
|
Value::string("0823", Span::unknown()),
|
||||||
);
|
);
|
||||||
|
|
||||||
unique_stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
unique_stack.set_last_exit_code(0, Span::unknown());
|
||||||
|
|
||||||
let mut line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
let mut line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
||||||
let temp_file = temp_dir().join(format!("{}.nu", uuid::Uuid::new_v4()));
|
let temp_file = temp_dir().join(format!("{}.nu", uuid::Uuid::new_v4()));
|
||||||
|
@ -837,7 +837,7 @@ fn do_auto_cd(
|
||||||
"NUSHELL_LAST_SHELL".into(),
|
"NUSHELL_LAST_SHELL".into(),
|
||||||
Value::int(last_shell as i64, span),
|
Value::int(last_shell as i64, span),
|
||||||
);
|
);
|
||||||
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
stack.set_last_exit_code(0, Span::unknown());
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
|
@ -210,18 +210,19 @@ pub fn eval_source(
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
|
|
||||||
let exit_code = match evaluate_source(engine_state, stack, source, fname, input, allow_return) {
|
let exit_code = match evaluate_source(engine_state, stack, source, fname, input, allow_return) {
|
||||||
Ok(code) => code.unwrap_or(0),
|
Ok(failed) => {
|
||||||
|
let code = failed.into();
|
||||||
|
stack.set_last_exit_code(code, Span::unknown());
|
||||||
|
code
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
1
|
let code = err.exit_code();
|
||||||
|
stack.set_last_error(&err);
|
||||||
|
code
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
stack.add_env_var(
|
|
||||||
"LAST_EXIT_CODE".to_string(),
|
|
||||||
Value::int(exit_code.into(), Span::unknown()),
|
|
||||||
);
|
|
||||||
|
|
||||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
|
@ -244,7 +245,7 @@ fn evaluate_source(
|
||||||
fname: &str,
|
fname: &str,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
allow_return: bool,
|
allow_return: bool,
|
||||||
) -> Result<Option<i32>, ShellError> {
|
) -> Result<bool, ShellError> {
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
let output = parse(
|
let output = parse(
|
||||||
|
@ -259,7 +260,7 @@ fn evaluate_source(
|
||||||
|
|
||||||
if let Some(err) = working_set.parse_errors.first() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_error(&working_set, err);
|
||||||
return Ok(Some(1));
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(err) = working_set.compile_errors.first() {
|
if let Some(err) = working_set.compile_errors.first() {
|
||||||
|
@ -278,10 +279,9 @@ fn evaluate_source(
|
||||||
eval_block::<WithoutDebug>(engine_state, stack, &block, input)
|
eval_block::<WithoutDebug>(engine_state, stack, &block, input)
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let status = if let PipelineData::ByteStream(..) = pipeline {
|
if let PipelineData::ByteStream(..) = pipeline {
|
||||||
pipeline.print(engine_state, stack, false, false)?
|
pipeline.print(engine_state, stack, false, false)
|
||||||
} else {
|
} else if let Some(hook) = engine_state.get_config().hooks.display_output.clone() {
|
||||||
if let Some(hook) = engine_state.get_config().hooks.display_output.clone() {
|
|
||||||
let pipeline = eval_hook(
|
let pipeline = eval_hook(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
|
@ -293,10 +293,9 @@ fn evaluate_source(
|
||||||
pipeline.print(engine_state, stack, false, false)
|
pipeline.print(engine_state, stack, false, false)
|
||||||
} else {
|
} else {
|
||||||
pipeline.print(engine_state, stack, true, false)
|
pipeline.print(engine_state, stack, true, false)
|
||||||
}?
|
}?;
|
||||||
};
|
|
||||||
|
|
||||||
Ok(status.map(|status| status.code()))
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use nu_engine::{command_prelude::*, get_eval_block_with_early_return, redirect_env};
|
use nu_engine::{command_prelude::*, get_eval_block_with_early_return, redirect_env};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Closure,
|
engine::Closure,
|
||||||
process::{ChildPipe, ChildProcess, ExitStatus},
|
process::{ChildPipe, ChildProcess},
|
||||||
ByteStream, ByteStreamSource, OutDest,
|
ByteStream, ByteStreamSource, OutDest,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -147,13 +147,7 @@ impl Command for Do {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if child.wait()? != ExitStatus::Exited(0) {
|
child.wait()?;
|
||||||
return Err(ShellError::ExternalCommand {
|
|
||||||
label: "External command failed".to_string(),
|
|
||||||
help: stderr_msg,
|
|
||||||
span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut child = ChildProcess::from_raw(None, None, None, span);
|
let mut child = ChildProcess::from_raw(None, None, None, span);
|
||||||
if let Some(stdout) = stdout {
|
if let Some(stdout) = stdout {
|
||||||
|
|
|
@ -93,25 +93,10 @@ impl Command for For {
|
||||||
stack.add_var(var_id, x);
|
stack.add_var(var_id, x);
|
||||||
|
|
||||||
match eval_block(&engine_state, stack, block, PipelineData::empty()) {
|
match eval_block(&engine_state, stack, block, PipelineData::empty()) {
|
||||||
Err(ShellError::Break { .. }) => {
|
Err(ShellError::Break { .. }) => break,
|
||||||
break;
|
Err(ShellError::Continue { .. }) => continue,
|
||||||
}
|
Err(err) => return Err(err),
|
||||||
Err(ShellError::Continue { .. }) => {
|
Ok(data) => data.drain()?,
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
Ok(data) => {
|
|
||||||
if let Some(status) = data.drain()? {
|
|
||||||
let code = status.code();
|
|
||||||
if code != 0 {
|
|
||||||
return Ok(
|
|
||||||
PipelineData::new_external_stream_with_only_exit_code(code),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,25 +106,10 @@ impl Command for For {
|
||||||
stack.add_var(var_id, x);
|
stack.add_var(var_id, x);
|
||||||
|
|
||||||
match eval_block(&engine_state, stack, block, PipelineData::empty()) {
|
match eval_block(&engine_state, stack, block, PipelineData::empty()) {
|
||||||
Err(ShellError::Break { .. }) => {
|
Err(ShellError::Break { .. }) => break,
|
||||||
break;
|
Err(ShellError::Continue { .. }) => continue,
|
||||||
}
|
Err(err) => return Err(err),
|
||||||
Err(ShellError::Continue { .. }) => {
|
Ok(data) => data.drain()?,
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
Ok(data) => {
|
|
||||||
if let Some(status) = data.drain()? {
|
|
||||||
let code = status.code();
|
|
||||||
if code != 0 {
|
|
||||||
return Ok(
|
|
||||||
PipelineData::new_external_stream_with_only_exit_code(code),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,10 +127,9 @@ impl Command for If {
|
||||||
eval_block(engine_state, stack, block, input)
|
eval_block(engine_state, stack, block, input)
|
||||||
} else {
|
} else {
|
||||||
eval_expression_with_input(engine_state, stack, else_expr, input)
|
eval_expression_with_input(engine_state, stack, else_expr, input)
|
||||||
.map(|res| res.0)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eval_expression_with_input(engine_state, stack, else_case, input).map(|res| res.0)
|
eval_expression_with_input(engine_state, stack, else_case, input)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
|
|
|
@ -56,23 +56,10 @@ impl Command for Loop {
|
||||||
engine_state.signals().check(head)?;
|
engine_state.signals().check(head)?;
|
||||||
|
|
||||||
match eval_block(engine_state, stack, block, PipelineData::empty()) {
|
match eval_block(engine_state, stack, block, PipelineData::empty()) {
|
||||||
Err(ShellError::Break { .. }) => {
|
Err(ShellError::Break { .. }) => break,
|
||||||
break;
|
Err(ShellError::Continue { .. }) => continue,
|
||||||
}
|
Err(err) => return Err(err),
|
||||||
Err(ShellError::Continue { .. }) => {
|
Ok(data) => data.drain()?,
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
Ok(data) => {
|
|
||||||
if let Some(status) = data.drain()? {
|
|
||||||
let code = status.code();
|
|
||||||
if code != 0 {
|
|
||||||
return Ok(PipelineData::new_external_stream_with_only_exit_code(code));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
|
|
|
@ -81,7 +81,7 @@ impl Command for Match {
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
eval_block(engine_state, stack, block, input)
|
eval_block(engine_state, stack, block, input)
|
||||||
} else {
|
} else {
|
||||||
eval_expression_with_input(engine_state, stack, expr, input).map(|x| x.0)
|
eval_expression_with_input(engine_state, stack, expr, input)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -47,6 +47,7 @@ impl Command for Try {
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
// This is compiled specially by the IR compiler. The code here is never used when
|
// This is compiled specially by the IR compiler. The code here is never used when
|
||||||
// running in IR mode.
|
// running in IR mode.
|
||||||
let call = call.assert_ast_call()?;
|
let call = call.assert_ast_call()?;
|
||||||
|
@ -61,30 +62,15 @@ impl Command for Try {
|
||||||
let try_block = engine_state.get_block(try_block);
|
let try_block = engine_state.get_block(try_block);
|
||||||
let eval_block = get_eval_block(engine_state);
|
let eval_block = get_eval_block(engine_state);
|
||||||
|
|
||||||
match eval_block(engine_state, stack, try_block, input) {
|
let result = eval_block(engine_state, stack, try_block, input)
|
||||||
Err(error) => {
|
.and_then(|pipeline| pipeline.write_to_out_dests(engine_state, stack));
|
||||||
let error = intercept_block_control(error)?;
|
|
||||||
let err_record = err_to_record(error, call.head);
|
match result {
|
||||||
handle_catch(err_record, catch_block, engine_state, stack, eval_block)
|
Err(err) => run_catch(err, head, catch_block, engine_state, stack, eval_block),
|
||||||
}
|
|
||||||
Ok(PipelineData::Value(Value::Error { error, .. }, ..)) => {
|
Ok(PipelineData::Value(Value::Error { error, .. }, ..)) => {
|
||||||
let error = intercept_block_control(*error)?;
|
run_catch(*error, head, catch_block, engine_state, stack, eval_block)
|
||||||
let err_record = err_to_record(error, call.head);
|
|
||||||
handle_catch(err_record, catch_block, engine_state, stack, eval_block)
|
|
||||||
}
|
|
||||||
// external command may fail to run
|
|
||||||
Ok(pipeline) => {
|
|
||||||
let (pipeline, external_failed) = pipeline.check_external_failed()?;
|
|
||||||
if external_failed {
|
|
||||||
let status = pipeline.drain()?;
|
|
||||||
let code = status.map(|status| status.code()).unwrap_or(0);
|
|
||||||
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(code.into(), call.head));
|
|
||||||
let err_value = Value::nothing(call.head);
|
|
||||||
handle_catch(err_value, catch_block, engine_state, stack, eval_block)
|
|
||||||
} else {
|
|
||||||
Ok(pipeline)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Ok(pipeline) => Ok(pipeline),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,28 +95,33 @@ impl Command for Try {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_catch(
|
fn run_catch(
|
||||||
err_value: Value,
|
error: ShellError,
|
||||||
catch_block: Option<Closure>,
|
span: Span,
|
||||||
|
catch: Option<Closure>,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
eval_block_fn: EvalBlockFn,
|
eval_block: EvalBlockFn,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
if let Some(catch_block) = catch_block {
|
let error = intercept_block_control(error)?;
|
||||||
let catch_block = engine_state.get_block(catch_block.block_id);
|
|
||||||
|
if let Some(catch) = catch {
|
||||||
|
stack.set_last_error(&error);
|
||||||
|
let error = err_to_record(error, span);
|
||||||
|
let block = engine_state.get_block(catch.block_id);
|
||||||
// Put the error value in the positional closure var
|
// Put the error value in the positional closure var
|
||||||
if let Some(var) = catch_block.signature.get_positional(0) {
|
if let Some(var) = block.signature.get_positional(0) {
|
||||||
if let Some(var_id) = &var.var_id {
|
if let Some(var_id) = &var.var_id {
|
||||||
stack.add_var(*var_id, err_value.clone());
|
stack.add_var(*var_id, error.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eval_block_fn(
|
eval_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
catch_block,
|
block,
|
||||||
// Make the error accessible with $in, too
|
// Make the error accessible with $in, too
|
||||||
err_value.into_pipeline_data(),
|
error.into_pipeline_data(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
|
@ -143,23 +134,28 @@ fn handle_catch(
|
||||||
/// `Err` when flow control to bubble up with `?`
|
/// `Err` when flow control to bubble up with `?`
|
||||||
fn intercept_block_control(error: ShellError) -> Result<ShellError, ShellError> {
|
fn intercept_block_control(error: ShellError) -> Result<ShellError, ShellError> {
|
||||||
match error {
|
match error {
|
||||||
nu_protocol::ShellError::Break { .. } => Err(error),
|
ShellError::Break { .. } => Err(error),
|
||||||
nu_protocol::ShellError::Continue { .. } => Err(error),
|
ShellError::Continue { .. } => Err(error),
|
||||||
nu_protocol::ShellError::Return { .. } => Err(error),
|
ShellError::Return { .. } => Err(error),
|
||||||
_ => Ok(error),
|
_ => Ok(error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert from `error` to [`Value::Record`] so the error information can be easily accessed in catch.
|
/// Convert from `error` to [`Value::Record`] so the error information can be easily accessed in catch.
|
||||||
fn err_to_record(error: ShellError, head: Span) -> Value {
|
fn err_to_record(error: ShellError, head: Span) -> Value {
|
||||||
Value::record(
|
let exit_code = error.external_exit_code();
|
||||||
record! {
|
|
||||||
|
let mut record = record! {
|
||||||
"msg" => Value::string(error.to_string(), head),
|
"msg" => Value::string(error.to_string(), head),
|
||||||
"debug" => Value::string(format!("{error:?}"), head),
|
"debug" => Value::string(format!("{error:?}"), head),
|
||||||
"raw" => Value::error(error, head),
|
"raw" => Value::error(error, head),
|
||||||
},
|
};
|
||||||
head,
|
|
||||||
)
|
if let Some(code) = exit_code {
|
||||||
|
record.push("exit_code", Value::int(code.item.into(), code.span));
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::record(record, head)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -73,27 +73,10 @@ impl Command for While {
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
|
|
||||||
match eval_block(engine_state, stack, block, PipelineData::empty()) {
|
match eval_block(engine_state, stack, block, PipelineData::empty()) {
|
||||||
Err(ShellError::Break { .. }) => {
|
Err(ShellError::Break { .. }) => break,
|
||||||
break;
|
Err(ShellError::Continue { .. }) => continue,
|
||||||
}
|
Err(err) => return Err(err),
|
||||||
Err(ShellError::Continue { .. }) => {
|
Ok(data) => data.drain()?,
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
Ok(data) => {
|
|
||||||
if let Some(status) = data.drain()? {
|
|
||||||
let code = status.code();
|
|
||||||
if code != 0 {
|
|
||||||
return Ok(
|
|
||||||
PipelineData::new_external_stream_with_only_exit_code(
|
|
||||||
code,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -59,7 +59,7 @@ impl Command for TimeIt {
|
||||||
} else {
|
} else {
|
||||||
let eval_expression_with_input = get_eval_expression_with_input(engine_state);
|
let eval_expression_with_input = get_eval_expression_with_input(engine_state);
|
||||||
let expression = &command_to_run.clone();
|
let expression = &command_to_run.clone();
|
||||||
eval_expression_with_input(engine_state, stack, expression, input)?.0
|
eval_expression_with_input(engine_state, stack, expression, input)?
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PipelineData::empty()
|
PipelineData::empty()
|
||||||
|
|
|
@ -12,21 +12,21 @@ fn capture_errors_works() {
|
||||||
#[test]
|
#[test]
|
||||||
fn capture_errors_works_for_external() {
|
fn capture_errors_works_for_external() {
|
||||||
let actual = nu!("do -c {nu --testbin fail}");
|
let actual = nu!("do -c {nu --testbin fail}");
|
||||||
assert!(actual.err.contains("External command failed"));
|
assert!(actual.err.contains("exited with code"));
|
||||||
assert_eq!(actual.out, "");
|
assert_eq!(actual.out, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn capture_errors_works_for_external_with_pipeline() {
|
fn capture_errors_works_for_external_with_pipeline() {
|
||||||
let actual = nu!("do -c {nu --testbin fail} | echo `text`");
|
let actual = nu!("do -c {nu --testbin fail} | echo `text`");
|
||||||
assert!(actual.err.contains("External command failed"));
|
assert!(actual.err.contains("exited with code"));
|
||||||
assert_eq!(actual.out, "");
|
assert_eq!(actual.out, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn capture_errors_works_for_external_with_semicolon() {
|
fn capture_errors_works_for_external_with_semicolon() {
|
||||||
let actual = nu!(r#"do -c {nu --testbin fail}; echo `text`"#);
|
let actual = nu!(r#"do -c {nu --testbin fail}; echo `text`"#);
|
||||||
assert!(actual.err.contains("External command failed"));
|
assert!(actual.err.contains("exited with code"));
|
||||||
assert_eq!(actual.out, "");
|
assert_eq!(actual.out, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -250,7 +250,7 @@ pub fn eval_expression_with_input<D: DebugContext>(
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
expr: &Expression,
|
expr: &Expression,
|
||||||
mut input: PipelineData,
|
mut input: PipelineData,
|
||||||
) -> Result<(PipelineData, bool), ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
match &expr.expr {
|
match &expr.expr {
|
||||||
Expr::Call(call) => {
|
Expr::Call(call) => {
|
||||||
input = eval_call::<D>(engine_state, stack, call, input)?;
|
input = eval_call::<D>(engine_state, stack, call, input)?;
|
||||||
|
@ -298,16 +298,7 @@ pub fn eval_expression_with_input<D: DebugContext>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// If input an external command,
|
Ok(input)
|
||||||
// then `might_consume_external_result` will consume `stderr` if `stdout` is `None`.
|
|
||||||
// This should not happen if the user wants to capture stderr.
|
|
||||||
if !matches!(stack.stdout(), OutDest::Pipe | OutDest::Capture)
|
|
||||||
&& matches!(stack.stderr(), OutDest::Capture)
|
|
||||||
{
|
|
||||||
Ok((input, false))
|
|
||||||
} else {
|
|
||||||
input.check_external_failed()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_redirection<D: DebugContext>(
|
fn eval_redirection<D: DebugContext>(
|
||||||
|
@ -401,9 +392,8 @@ fn eval_element_with_input_inner<D: DebugContext>(
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
element: &PipelineElement,
|
element: &PipelineElement,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<(PipelineData, bool), ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let (data, failed) =
|
let data = eval_expression_with_input::<D>(engine_state, stack, &element.expr, input)?;
|
||||||
eval_expression_with_input::<D>(engine_state, stack, &element.expr, input)?;
|
|
||||||
|
|
||||||
if let Some(redirection) = element.redirection.as_ref() {
|
if let Some(redirection) = element.redirection.as_ref() {
|
||||||
let is_external = if let PipelineData::ByteStream(stream, ..) = &data {
|
let is_external = if let PipelineData::ByteStream(stream, ..) = &data {
|
||||||
|
@ -473,7 +463,7 @@ fn eval_element_with_input_inner<D: DebugContext>(
|
||||||
PipelineData::Empty => PipelineData::Empty,
|
PipelineData::Empty => PipelineData::Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((data, failed))
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_element_with_input<D: DebugContext>(
|
fn eval_element_with_input<D: DebugContext>(
|
||||||
|
@ -481,20 +471,11 @@ fn eval_element_with_input<D: DebugContext>(
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
element: &PipelineElement,
|
element: &PipelineElement,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<(PipelineData, bool), ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
D::enter_element(engine_state, element);
|
D::enter_element(engine_state, element);
|
||||||
match eval_element_with_input_inner::<D>(engine_state, stack, element, input) {
|
let result = eval_element_with_input_inner::<D>(engine_state, stack, element, input);
|
||||||
Ok((data, failed)) => {
|
D::leave_element(engine_state, element, &result);
|
||||||
let res = Ok(data);
|
result
|
||||||
D::leave_element(engine_state, element, &res);
|
|
||||||
res.map(|data| (data, failed))
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
let res = Err(err);
|
|
||||||
D::leave_element(engine_state, element, &res);
|
|
||||||
res.map(|data| (data, false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_block_with_early_return<D: DebugContext>(
|
pub fn eval_block_with_early_return<D: DebugContext>(
|
||||||
|
@ -509,7 +490,7 @@ pub fn eval_block_with_early_return<D: DebugContext>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_block<D: DebugContext>(
|
fn eval_block_inner<D: DebugContext>(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
block: &Block,
|
block: &Block,
|
||||||
|
@ -520,8 +501,6 @@ pub fn eval_block<D: DebugContext>(
|
||||||
return eval_ir_block::<D>(engine_state, stack, block, input);
|
return eval_ir_block::<D>(engine_state, stack, block, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
D::enter_block(engine_state, block);
|
|
||||||
|
|
||||||
let num_pipelines = block.len();
|
let num_pipelines = block.len();
|
||||||
|
|
||||||
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
|
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
|
||||||
|
@ -542,14 +521,7 @@ pub fn eval_block<D: DebugContext>(
|
||||||
(next_out.or(Some(OutDest::Pipe)), next_err),
|
(next_out.or(Some(OutDest::Pipe)), next_err),
|
||||||
)?;
|
)?;
|
||||||
let stack = &mut stack.push_redirection(stdout, stderr);
|
let stack = &mut stack.push_redirection(stdout, stderr);
|
||||||
let (output, failed) =
|
input = eval_element_with_input::<D>(engine_state, stack, element, input)?;
|
||||||
eval_element_with_input::<D>(engine_state, stack, element, input)?;
|
|
||||||
if failed {
|
|
||||||
// External command failed.
|
|
||||||
// Don't return `Err(ShellError)`, so nushell won't show an extra error message.
|
|
||||||
return Ok(output);
|
|
||||||
}
|
|
||||||
input = output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if last_pipeline {
|
if last_pipeline {
|
||||||
|
@ -560,13 +532,7 @@ pub fn eval_block<D: DebugContext>(
|
||||||
(stack.pipe_stdout().cloned(), stack.pipe_stderr().cloned()),
|
(stack.pipe_stdout().cloned(), stack.pipe_stderr().cloned()),
|
||||||
)?;
|
)?;
|
||||||
let stack = &mut stack.push_redirection(stdout, stderr);
|
let stack = &mut stack.push_redirection(stdout, stderr);
|
||||||
let (output, failed) = eval_element_with_input::<D>(engine_state, stack, last, input)?;
|
input = eval_element_with_input::<D>(engine_state, stack, last, input)?;
|
||||||
if failed {
|
|
||||||
// External command failed.
|
|
||||||
// Don't return `Err(ShellError)`, so nushell won't show an extra error message.
|
|
||||||
return Ok(output);
|
|
||||||
}
|
|
||||||
input = output;
|
|
||||||
} else {
|
} else {
|
||||||
let (stdout, stderr) = eval_element_redirection::<D>(
|
let (stdout, stderr) = eval_element_redirection::<D>(
|
||||||
engine_state,
|
engine_state,
|
||||||
|
@ -575,40 +541,41 @@ pub fn eval_block<D: DebugContext>(
|
||||||
(None, None),
|
(None, None),
|
||||||
)?;
|
)?;
|
||||||
let stack = &mut stack.push_redirection(stdout, stderr);
|
let stack = &mut stack.push_redirection(stdout, stderr);
|
||||||
let (output, failed) = eval_element_with_input::<D>(engine_state, stack, last, input)?;
|
match eval_element_with_input::<D>(engine_state, stack, last, input)? {
|
||||||
if failed {
|
|
||||||
// External command failed.
|
|
||||||
// Don't return `Err(ShellError)`, so nushell won't show an extra error message.
|
|
||||||
return Ok(output);
|
|
||||||
}
|
|
||||||
input = PipelineData::Empty;
|
|
||||||
match output {
|
|
||||||
PipelineData::ByteStream(stream, ..) => {
|
PipelineData::ByteStream(stream, ..) => {
|
||||||
let span = stream.span();
|
let span = stream.span();
|
||||||
let status = stream.drain()?;
|
if let Err(err) = stream.drain() {
|
||||||
if let Some(status) = status {
|
stack.set_last_error(&err);
|
||||||
stack.add_env_var(
|
return Err(err);
|
||||||
"LAST_EXIT_CODE".into(),
|
} else {
|
||||||
Value::int(status.code().into(), span),
|
stack.set_last_exit_code(0, span);
|
||||||
);
|
|
||||||
if status.code() != 0 {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
PipelineData::ListStream(stream, ..) => stream.drain()?,
|
||||||
PipelineData::ListStream(stream, ..) => {
|
|
||||||
stream.drain()?;
|
|
||||||
}
|
|
||||||
PipelineData::Value(..) | PipelineData::Empty => {}
|
PipelineData::Value(..) | PipelineData::Empty => {}
|
||||||
}
|
}
|
||||||
|
input = PipelineData::Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
D::leave_block(engine_state, block);
|
|
||||||
|
|
||||||
Ok(input)
|
Ok(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn eval_block<D: DebugContext>(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
block: &Block,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
D::enter_block(engine_state, block);
|
||||||
|
let result = eval_block_inner::<D>(engine_state, stack, block, input);
|
||||||
|
D::leave_block(engine_state, block);
|
||||||
|
if let Err(err) = &result {
|
||||||
|
stack.set_last_error(err);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
pub fn eval_collect<D: DebugContext>(
|
pub fn eval_collect<D: DebugContext>(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
|
@ -639,8 +606,7 @@ pub fn eval_collect<D: DebugContext>(
|
||||||
expr,
|
expr,
|
||||||
// We still have to pass it as input
|
// We still have to pass it as input
|
||||||
input.into_pipeline_data_with_metadata(metadata),
|
input.into_pipeline_data_with_metadata(metadata),
|
||||||
)
|
);
|
||||||
.map(|(result, _failed)| result);
|
|
||||||
|
|
||||||
stack.remove_var(var_id);
|
stack.remove_var(var_id);
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,8 @@ pub type EvalBlockWithEarlyReturnFn =
|
||||||
pub type EvalExpressionFn = fn(&EngineState, &mut Stack, &Expression) -> Result<Value, ShellError>;
|
pub type EvalExpressionFn = fn(&EngineState, &mut Stack, &Expression) -> Result<Value, ShellError>;
|
||||||
|
|
||||||
/// Type of eval_expression_with_input() function
|
/// Type of eval_expression_with_input() function
|
||||||
pub type EvalExpressionWithInputFn = fn(
|
pub type EvalExpressionWithInputFn =
|
||||||
&EngineState,
|
fn(&EngineState, &mut Stack, &Expression, PipelineData) -> Result<PipelineData, ShellError>;
|
||||||
&mut Stack,
|
|
||||||
&Expression,
|
|
||||||
PipelineData,
|
|
||||||
) -> Result<(PipelineData, bool), ShellError>;
|
|
||||||
|
|
||||||
/// Type of eval_subexpression() function
|
/// Type of eval_subexpression() function
|
||||||
pub type EvalSubexpressionFn =
|
pub type EvalSubexpressionFn =
|
||||||
|
|
|
@ -207,18 +207,6 @@ fn eval_ir_block_impl<D: DebugContext>(
|
||||||
Ok(InstructionResult::Return(reg_id)) => {
|
Ok(InstructionResult::Return(reg_id)) => {
|
||||||
return Ok(ctx.take_reg(reg_id));
|
return Ok(ctx.take_reg(reg_id));
|
||||||
}
|
}
|
||||||
Ok(InstructionResult::ExitCode(exit_code)) => {
|
|
||||||
if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) {
|
|
||||||
// If an error handler is set, branch there
|
|
||||||
prepare_error_handler(ctx, error_handler, None);
|
|
||||||
pc = error_handler.handler_index;
|
|
||||||
} else {
|
|
||||||
// If not, exit the block with the exit code
|
|
||||||
return Ok(PipelineData::new_external_stream_with_only_exit_code(
|
|
||||||
exit_code,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(
|
Err(
|
||||||
err @ (ShellError::Return { .. }
|
err @ (ShellError::Return { .. }
|
||||||
| ShellError::Continue { .. }
|
| ShellError::Continue { .. }
|
||||||
|
@ -281,7 +269,6 @@ enum InstructionResult {
|
||||||
Continue,
|
Continue,
|
||||||
Branch(usize),
|
Branch(usize),
|
||||||
Return(RegId),
|
Return(RegId),
|
||||||
ExitCode(i32),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform an instruction
|
/// Perform an instruction
|
||||||
|
@ -790,7 +777,7 @@ fn eval_instruction<D: DebugContext>(
|
||||||
}
|
}
|
||||||
Instruction::CheckExternalFailed { dst, src } => {
|
Instruction::CheckExternalFailed { dst, src } => {
|
||||||
let data = ctx.take_reg(*src);
|
let data = ctx.take_reg(*src);
|
||||||
let (data, failed) = data.check_external_failed()?;
|
let (data, failed) = todo!(); // data.check_external_failed()?;
|
||||||
ctx.put_reg(*src, data);
|
ctx.put_reg(*src, data);
|
||||||
ctx.put_reg(*dst, Value::bool(failed, *span).into_pipeline_data());
|
ctx.put_reg(*dst, Value::bool(failed, *span).into_pipeline_data());
|
||||||
Ok(Continue)
|
Ok(Continue)
|
||||||
|
@ -1365,20 +1352,20 @@ fn collect(data: PipelineData, fallback_span: Span) -> Result<PipelineData, Shel
|
||||||
/// Helper for drain behavior. Returns `Ok(ExitCode)` on failed external.
|
/// Helper for drain behavior. Returns `Ok(ExitCode)` on failed external.
|
||||||
fn drain(ctx: &mut EvalContext<'_>, data: PipelineData) -> Result<InstructionResult, ShellError> {
|
fn drain(ctx: &mut EvalContext<'_>, data: PipelineData) -> Result<InstructionResult, ShellError> {
|
||||||
use self::InstructionResult::*;
|
use self::InstructionResult::*;
|
||||||
let span = data.span().unwrap_or(Span::unknown());
|
match data {
|
||||||
if let Some(exit_status) = data.drain()? {
|
PipelineData::ByteStream(stream, ..) => {
|
||||||
ctx.stack.add_env_var(
|
let span = stream.span();
|
||||||
"LAST_EXIT_CODE".into(),
|
if let Err(err) = stream.drain() {
|
||||||
Value::int(exit_status.code() as i64, span),
|
ctx.stack.set_last_error(&err);
|
||||||
);
|
return Err(err);
|
||||||
if exit_status.code() == 0 {
|
|
||||||
Ok(Continue)
|
|
||||||
} else {
|
} else {
|
||||||
Ok(ExitCode(exit_status.code()))
|
ctx.stack.set_last_exit_code(0, span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PipelineData::ListStream(stream, ..) => stream.drain()?,
|
||||||
|
PipelineData::Value(..) | PipelineData::Empty => {}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Ok(Continue)
|
Ok(Continue)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RedirectionStream {
|
enum RedirectionStream {
|
||||||
|
|
|
@ -279,6 +279,16 @@ impl Stack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_last_exit_code(&mut self, code: i32, span: Span) {
|
||||||
|
self.add_env_var("LAST_EXIT_CODE".into(), Value::int(code.into(), span));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_last_error(&mut self, error: &ShellError) {
|
||||||
|
if let Some(code) = error.external_exit_code() {
|
||||||
|
self.set_last_exit_code(code.item, code.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn last_overlay_name(&self) -> Result<String, ShellError> {
|
pub fn last_overlay_name(&self) -> Result<String, ShellError> {
|
||||||
self.active_overlays
|
self.active_overlays
|
||||||
.last()
|
.last()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use miette::Diagnostic;
|
use miette::Diagnostic;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::io;
|
use std::{io, num::NonZeroI32};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -639,6 +639,49 @@ pub enum ShellError {
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// An external command exited with a non-zero exit code.
|
||||||
|
///
|
||||||
|
/// ## Resolution
|
||||||
|
///
|
||||||
|
/// Check the external command's error message.
|
||||||
|
#[error("External command had a non-zero exit code")]
|
||||||
|
#[diagnostic(code(nu::shell::non_zero_exit_code))]
|
||||||
|
NonZeroExitCode {
|
||||||
|
exit_code: NonZeroI32,
|
||||||
|
#[label("exited with code {exit_code}")]
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
/// An external command exited due to a signal.
|
||||||
|
///
|
||||||
|
/// ## Resolution
|
||||||
|
///
|
||||||
|
/// Check why the signal was sent or triggered.
|
||||||
|
#[error("External command was terminated by a signal")]
|
||||||
|
#[diagnostic(code(nu::shell::terminated_by_signal))]
|
||||||
|
TerminatedBySignal {
|
||||||
|
signal_name: String,
|
||||||
|
signal: i32,
|
||||||
|
#[label("terminated by {signal_name} ({signal})")]
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
/// An external command core dumped.
|
||||||
|
///
|
||||||
|
/// ## Resolution
|
||||||
|
///
|
||||||
|
/// Check why the core dumped was triggered.
|
||||||
|
#[error("External command core dumped")]
|
||||||
|
#[diagnostic(code(nu::shell::core_dumped))]
|
||||||
|
CoreDumped {
|
||||||
|
signal_name: String,
|
||||||
|
signal: i32,
|
||||||
|
#[label("core dumped with {signal_name} ({signal})")]
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
/// An operation was attempted with an input unsupported for some reason.
|
/// An operation was attempted with an input unsupported for some reason.
|
||||||
///
|
///
|
||||||
/// ## Resolution
|
/// ## Resolution
|
||||||
|
@ -876,21 +919,6 @@ pub enum ShellError {
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
/// An I/O operation failed.
|
|
||||||
///
|
|
||||||
/// ## Resolution
|
|
||||||
///
|
|
||||||
/// This is a generic error. Refer to the specific error message for further details.
|
|
||||||
#[error("program coredump error")]
|
|
||||||
#[diagnostic(code(nu::shell::coredump_error))]
|
|
||||||
CoredumpErrorSpanned {
|
|
||||||
msg: String,
|
|
||||||
signal: i32,
|
|
||||||
#[label("{msg}")]
|
|
||||||
span: Span,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Tried to `cd` to a path that isn't a directory.
|
/// Tried to `cd` to a path that isn't a directory.
|
||||||
///
|
///
|
||||||
/// ## Resolution
|
/// ## Resolution
|
||||||
|
@ -1395,8 +1423,22 @@ On Windows, this would be %USERPROFILE%\AppData\Roaming"#
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement as From trait
|
|
||||||
impl ShellError {
|
impl ShellError {
|
||||||
|
pub fn external_exit_code(&self) -> Option<Spanned<i32>> {
|
||||||
|
let (item, span) = match *self {
|
||||||
|
Self::NonZeroExitCode { exit_code, span } => (exit_code.into(), span),
|
||||||
|
Self::TerminatedBySignal { signal, span, .. }
|
||||||
|
| Self::CoreDumped { signal, span, .. } => (-signal, span),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
Some(Spanned { item, span })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exit_code(&self) -> i32 {
|
||||||
|
self.external_exit_code().map(|e| e.item).unwrap_or(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement as From trait
|
||||||
pub fn wrap(self, working_set: &StateWorkingSet, span: Span) -> ParseError {
|
pub fn wrap(self, working_set: &StateWorkingSet, span: Span) -> ParseError {
|
||||||
let msg = format_error(working_set, &self);
|
let msg = format_error(working_set, &self);
|
||||||
ParseError::LabeledError(
|
ParseError::LabeledError(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Module managing the streaming of raw bytes between pipeline elements
|
//! Module managing the streaming of raw bytes between pipeline elements
|
||||||
use crate::{
|
use crate::{
|
||||||
process::{ChildPipe, ChildProcess, ExitStatus},
|
process::{ChildPipe, ChildProcess},
|
||||||
ErrSpan, IntoSpanned, OutDest, PipelineData, ShellError, Signals, Span, Type, Value,
|
ErrSpan, IntoSpanned, OutDest, PipelineData, ShellError, Signals, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -551,14 +551,14 @@ impl ByteStream {
|
||||||
///
|
///
|
||||||
/// If the source of the [`ByteStream`] is [`ByteStreamSource::Child`],
|
/// If the source of the [`ByteStream`] is [`ByteStreamSource::Child`],
|
||||||
/// then the [`ExitStatus`] of the [`ChildProcess`] is returned.
|
/// then the [`ExitStatus`] of the [`ChildProcess`] is returned.
|
||||||
pub fn drain(self) -> Result<Option<ExitStatus>, ShellError> {
|
pub fn drain(self) -> Result<(), ShellError> {
|
||||||
match self.stream {
|
match self.stream {
|
||||||
ByteStreamSource::Read(read) => {
|
ByteStreamSource::Read(read) => {
|
||||||
copy_with_signals(read, io::sink(), self.span, &self.signals)?;
|
copy_with_signals(read, io::sink(), self.span, &self.signals)?;
|
||||||
Ok(None)
|
Ok(())
|
||||||
}
|
}
|
||||||
ByteStreamSource::File(_) => Ok(None),
|
ByteStreamSource::File(_) => Ok(()),
|
||||||
ByteStreamSource::Child(child) => Ok(Some(child.wait()?)),
|
ByteStreamSource::Child(child) => child.wait(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,7 +566,7 @@ impl ByteStream {
|
||||||
///
|
///
|
||||||
/// If the source of the [`ByteStream`] is [`ByteStreamSource::Child`],
|
/// If the source of the [`ByteStream`] is [`ByteStreamSource::Child`],
|
||||||
/// then the [`ExitStatus`] of the [`ChildProcess`] is returned.
|
/// then the [`ExitStatus`] of the [`ChildProcess`] is returned.
|
||||||
pub fn print(self, to_stderr: bool) -> Result<Option<ExitStatus>, ShellError> {
|
pub fn print(self, to_stderr: bool) -> Result<(), ShellError> {
|
||||||
if to_stderr {
|
if to_stderr {
|
||||||
self.write_to(&mut io::stderr())
|
self.write_to(&mut io::stderr())
|
||||||
} else {
|
} else {
|
||||||
|
@ -578,17 +578,15 @@ impl ByteStream {
|
||||||
///
|
///
|
||||||
/// If the source of the [`ByteStream`] is [`ByteStreamSource::Child`],
|
/// If the source of the [`ByteStream`] is [`ByteStreamSource::Child`],
|
||||||
/// then the [`ExitStatus`] of the [`ChildProcess`] is returned.
|
/// then the [`ExitStatus`] of the [`ChildProcess`] is returned.
|
||||||
pub fn write_to(self, dest: impl Write) -> Result<Option<ExitStatus>, ShellError> {
|
pub fn write_to(self, dest: impl Write) -> Result<(), ShellError> {
|
||||||
let span = self.span;
|
let span = self.span;
|
||||||
let signals = &self.signals;
|
let signals = &self.signals;
|
||||||
match self.stream {
|
match self.stream {
|
||||||
ByteStreamSource::Read(read) => {
|
ByteStreamSource::Read(read) => {
|
||||||
copy_with_signals(read, dest, span, signals)?;
|
copy_with_signals(read, dest, span, signals)?;
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
ByteStreamSource::File(file) => {
|
ByteStreamSource::File(file) => {
|
||||||
copy_with_signals(file, dest, span, signals)?;
|
copy_with_signals(file, dest, span, signals)?;
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
ByteStreamSource::Child(mut child) => {
|
ByteStreamSource::Child(mut child) => {
|
||||||
// All `OutDest`s except `OutDest::Capture` will cause `stderr` to be `None`.
|
// All `OutDest`s except `OutDest::Capture` will cause `stderr` to be `None`.
|
||||||
|
@ -606,26 +604,25 @@ impl ByteStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Some(child.wait()?))
|
child.wait()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn write_to_out_dests(
|
pub(crate) fn write_to_out_dests(
|
||||||
self,
|
self,
|
||||||
stdout: &OutDest,
|
stdout: &OutDest,
|
||||||
stderr: &OutDest,
|
stderr: &OutDest,
|
||||||
) -> Result<Option<ExitStatus>, ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let span = self.span;
|
let span = self.span;
|
||||||
let signals = &self.signals;
|
let signals = &self.signals;
|
||||||
|
|
||||||
match self.stream {
|
match self.stream {
|
||||||
ByteStreamSource::Read(read) => {
|
ByteStreamSource::Read(read) => {
|
||||||
write_to_out_dest(read, stdout, true, span, signals)?;
|
write_to_out_dest(read, stdout, true, span, signals)?;
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
ByteStreamSource::File(file) => {
|
ByteStreamSource::File(file) => match stdout {
|
||||||
match stdout {
|
|
||||||
OutDest::Pipe | OutDest::Capture | OutDest::Null => {}
|
OutDest::Pipe | OutDest::Capture | OutDest::Null => {}
|
||||||
OutDest::Inherit => {
|
OutDest::Inherit => {
|
||||||
copy_with_signals(file, io::stdout(), span, signals)?;
|
copy_with_signals(file, io::stdout(), span, signals)?;
|
||||||
|
@ -633,9 +630,7 @@ impl ByteStream {
|
||||||
OutDest::File(f) => {
|
OutDest::File(f) => {
|
||||||
copy_with_signals(file, f.as_ref(), span, signals)?;
|
copy_with_signals(file, f.as_ref(), span, signals)?;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
ByteStreamSource::Child(mut child) => {
|
ByteStreamSource::Child(mut child) => {
|
||||||
match (child.stdout.take(), child.stderr.take()) {
|
match (child.stdout.take(), child.stderr.take()) {
|
||||||
(Some(out), Some(err)) => {
|
(Some(out), Some(err)) => {
|
||||||
|
@ -682,9 +677,11 @@ impl ByteStream {
|
||||||
}
|
}
|
||||||
(None, None) => {}
|
(None, None) => {}
|
||||||
}
|
}
|
||||||
Ok(Some(child.wait()?))
|
child.wait()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{Call, PathMember},
|
ast::{Call, PathMember},
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
process::{ChildPipe, ChildProcess, ExitStatus},
|
ByteStream, ByteStreamType, Config, ListStream, OutDest, PipelineMetadata, Range, ShellError,
|
||||||
ByteStream, ByteStreamType, Config, ErrSpan, ListStream, OutDest, PipelineMetadata, Range,
|
Signals, Span, Type, Value,
|
||||||
ShellError, Signals, Span, Type, Value,
|
|
||||||
};
|
};
|
||||||
use nu_utils::{stderr_write_all_and_flush, stdout_write_all_and_flush};
|
use nu_utils::{stderr_write_all_and_flush, stdout_write_all_and_flush};
|
||||||
use std::io::{Cursor, Read, Write};
|
use std::io::Write;
|
||||||
|
|
||||||
const LINE_ENDING_PATTERN: &[char] = &['\r', '\n'];
|
const LINE_ENDING_PATTERN: &[char] = &['\r', '\n'];
|
||||||
|
|
||||||
|
@ -52,16 +51,6 @@ impl PipelineData {
|
||||||
PipelineData::Empty
|
PipelineData::Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
/// create a `PipelineData::ByteStream` with proper exit_code
|
|
||||||
///
|
|
||||||
/// It's useful to break running without raising error at user level.
|
|
||||||
pub fn new_external_stream_with_only_exit_code(exit_code: i32) -> PipelineData {
|
|
||||||
let span = Span::unknown();
|
|
||||||
let mut child = ChildProcess::from_raw(None, None, None, span);
|
|
||||||
child.set_exit_code(exit_code);
|
|
||||||
PipelineData::ByteStream(ByteStream::child(child, span), None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn metadata(&self) -> Option<PipelineMetadata> {
|
pub fn metadata(&self) -> Option<PipelineMetadata> {
|
||||||
match self {
|
match self {
|
||||||
PipelineData::Empty => None,
|
PipelineData::Empty => None,
|
||||||
|
@ -156,14 +145,10 @@ impl PipelineData {
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
match (self, stack.stdout()) {
|
match (self, stack.stdout()) {
|
||||||
(PipelineData::ByteStream(stream, ..), stdout) => {
|
|
||||||
if let Some(exit_status) = stream.write_to_out_dests(stdout, stack.stderr())? {
|
|
||||||
return Ok(PipelineData::new_external_stream_with_only_exit_code(
|
|
||||||
exit_status.code(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(data, OutDest::Pipe | OutDest::Capture) => return Ok(data),
|
(data, OutDest::Pipe | OutDest::Capture) => return Ok(data),
|
||||||
|
(PipelineData::ByteStream(stream, ..), stdout) => {
|
||||||
|
stream.write_to_out_dests(stdout, stack.stderr())?;
|
||||||
|
}
|
||||||
(PipelineData::Empty, ..) => {}
|
(PipelineData::Empty, ..) => {}
|
||||||
(PipelineData::Value(..), OutDest::Null) => {}
|
(PipelineData::Value(..), OutDest::Null) => {}
|
||||||
(PipelineData::ListStream(stream, ..), OutDest::Null) => {
|
(PipelineData::ListStream(stream, ..), OutDest::Null) => {
|
||||||
|
@ -193,15 +178,12 @@ impl PipelineData {
|
||||||
Ok(PipelineData::Empty)
|
Ok(PipelineData::Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drain(self) -> Result<Option<ExitStatus>, ShellError> {
|
pub fn drain(self) -> Result<(), ShellError> {
|
||||||
match self {
|
match self {
|
||||||
PipelineData::Empty => Ok(None),
|
PipelineData::Empty => Ok(()),
|
||||||
PipelineData::Value(Value::Error { error, .. }, ..) => Err(*error),
|
PipelineData::Value(Value::Error { error, .. }, ..) => Err(*error),
|
||||||
PipelineData::Value(..) => Ok(None),
|
PipelineData::Value(..) => Ok(()),
|
||||||
PipelineData::ListStream(stream, ..) => {
|
PipelineData::ListStream(stream, ..) => stream.drain(),
|
||||||
stream.drain()?;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
PipelineData::ByteStream(stream, ..) => stream.drain(),
|
PipelineData::ByteStream(stream, ..) => stream.drain(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -462,68 +444,6 @@ impl PipelineData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to catch the external command exit status and detect if it failed.
|
|
||||||
///
|
|
||||||
/// This is useful for external commands with semicolon, we can detect errors early to avoid
|
|
||||||
/// commands after the semicolon running.
|
|
||||||
///
|
|
||||||
/// Returns `self` and a flag that indicates if the external command run failed. If `self` is
|
|
||||||
/// not [`PipelineData::ByteStream`], the flag will be `false`.
|
|
||||||
///
|
|
||||||
/// Currently this will consume an external command to completion.
|
|
||||||
pub fn check_external_failed(self) -> Result<(Self, bool), ShellError> {
|
|
||||||
if let PipelineData::ByteStream(stream, metadata) = self {
|
|
||||||
// Preserve stream attributes
|
|
||||||
let span = stream.span();
|
|
||||||
let type_ = stream.type_();
|
|
||||||
let known_size = stream.known_size();
|
|
||||||
match stream.into_child() {
|
|
||||||
Ok(mut child) => {
|
|
||||||
// Only check children without stdout. This means that nothing
|
|
||||||
// later in the pipeline can possibly consume output from this external command.
|
|
||||||
if child.stdout.is_none() {
|
|
||||||
// Note:
|
|
||||||
// In run-external's implementation detail, the result sender thread
|
|
||||||
// send out stderr message first, then stdout message, then exit_code.
|
|
||||||
//
|
|
||||||
// In this clause, we already make sure that `stdout` is None
|
|
||||||
// But not the case of `stderr`, so if `stderr` is not None
|
|
||||||
// We need to consume stderr message before reading external commands' exit code.
|
|
||||||
//
|
|
||||||
// Or we'll never have a chance to read exit_code if stderr producer produce too much stderr message.
|
|
||||||
// So we consume stderr stream and rebuild it.
|
|
||||||
let stderr = child
|
|
||||||
.stderr
|
|
||||||
.take()
|
|
||||||
.map(|mut stderr| {
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
stderr.read_to_end(&mut buf).err_span(span)?;
|
|
||||||
Ok::<_, ShellError>(buf)
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
let code = child.wait()?.code();
|
|
||||||
let mut child = ChildProcess::from_raw(None, None, None, span);
|
|
||||||
if let Some(stderr) = stderr {
|
|
||||||
child.stderr = Some(ChildPipe::Tee(Box::new(Cursor::new(stderr))));
|
|
||||||
}
|
|
||||||
child.set_exit_code(code);
|
|
||||||
let stream = ByteStream::child(child, span).with_type(type_);
|
|
||||||
Ok((PipelineData::ByteStream(stream, metadata), code != 0))
|
|
||||||
} else {
|
|
||||||
let stream = ByteStream::child(child, span)
|
|
||||||
.with_type(type_)
|
|
||||||
.with_known_size(known_size);
|
|
||||||
Ok((PipelineData::ByteStream(stream, metadata), false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(stream) => Ok((PipelineData::ByteStream(stream, metadata), false)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok((self, false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to convert Value from Value::Range to Value::List.
|
/// Try to convert Value from Value::Range to Value::List.
|
||||||
/// This is useful to expand Value::Range into array notation, specifically when
|
/// This is useful to expand Value::Range into array notation, specifically when
|
||||||
/// converting `to json` or `to nuon`.
|
/// converting `to json` or `to nuon`.
|
||||||
|
@ -579,7 +499,7 @@ impl PipelineData {
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
no_newline: bool,
|
no_newline: bool,
|
||||||
to_stderr: bool,
|
to_stderr: bool,
|
||||||
) -> Result<Option<ExitStatus>, ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
match self {
|
match self {
|
||||||
// Print byte streams directly as long as they aren't binary.
|
// Print byte streams directly as long as they aren't binary.
|
||||||
PipelineData::ByteStream(stream, ..) if stream.type_() != ByteStreamType::Binary => {
|
PipelineData::ByteStream(stream, ..) if stream.type_() != ByteStreamType::Binary => {
|
||||||
|
@ -609,7 +529,7 @@ impl PipelineData {
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
no_newline: bool,
|
no_newline: bool,
|
||||||
to_stderr: bool,
|
to_stderr: bool,
|
||||||
) -> Result<Option<ExitStatus>, ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
if let PipelineData::ByteStream(stream, ..) = self {
|
if let PipelineData::ByteStream(stream, ..) = self {
|
||||||
// Copy ByteStreams directly
|
// Copy ByteStreams directly
|
||||||
stream.print(to_stderr)
|
stream.print(to_stderr)
|
||||||
|
@ -633,7 +553,7 @@ impl PipelineData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,21 +23,16 @@ impl ExitStatusFuture {
|
||||||
ExitStatusFuture::Finished(Err(err)) => Err(err.as_ref().clone()),
|
ExitStatusFuture::Finished(Err(err)) => Err(err.as_ref().clone()),
|
||||||
ExitStatusFuture::Running(receiver) => {
|
ExitStatusFuture::Running(receiver) => {
|
||||||
let code = match receiver.recv() {
|
let code = match receiver.recv() {
|
||||||
Ok(Ok(status)) => {
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
if let ExitStatus::Signaled {
|
Ok(Ok(
|
||||||
signal,
|
status @ ExitStatus::Signaled {
|
||||||
core_dumped: true,
|
core_dumped: true, ..
|
||||||
} = status
|
},
|
||||||
{
|
)) => {
|
||||||
return Err(ShellError::CoredumpErrorSpanned {
|
status.check_ok(span)?;
|
||||||
msg: format!("coredump detected. received signal: {signal}"),
|
|
||||||
signal,
|
|
||||||
span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
|
Ok(Ok(status)) => Ok(status),
|
||||||
Ok(Err(err)) => Err(ShellError::IOErrorSpanned {
|
Ok(Err(err)) => Err(ShellError::IOErrorSpanned {
|
||||||
msg: format!("failed to get exit code: {err:?}"),
|
msg: format!("failed to get exit code: {err:?}"),
|
||||||
span,
|
span,
|
||||||
|
@ -187,13 +182,12 @@ impl ChildProcess {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: check exit_status
|
self.exit_status.wait(self.span)?.check_ok(self.span)?;
|
||||||
self.exit_status.wait(self.span)?;
|
|
||||||
|
|
||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait(mut self) -> Result<ExitStatus, ShellError> {
|
pub fn wait(mut self) -> Result<(), ShellError> {
|
||||||
if let Some(stdout) = self.stdout.take() {
|
if let Some(stdout) = self.stdout.take() {
|
||||||
let stderr = self
|
let stderr = self
|
||||||
.stderr
|
.stderr
|
||||||
|
@ -229,7 +223,7 @@ impl ChildProcess {
|
||||||
consume_pipe(stderr).err_span(self.span)?;
|
consume_pipe(stderr).err_span(self.span)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.exit_status.wait(self.span)
|
self.exit_status.wait(self.span)?.check_ok(self.span)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_wait(&mut self) -> Result<Option<ExitStatus>, ShellError> {
|
pub fn try_wait(&mut self) -> Result<Option<ExitStatus>, ShellError> {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::{ShellError, Span};
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -18,6 +19,47 @@ impl ExitStatus {
|
||||||
ExitStatus::Signaled { signal, .. } => -signal,
|
ExitStatus::Signaled { signal, .. } => -signal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_ok(self, span: Span) -> Result<(), ShellError> {
|
||||||
|
match self {
|
||||||
|
ExitStatus::Exited(exit_code) => {
|
||||||
|
if let Ok(exit_code) = exit_code.try_into() {
|
||||||
|
Err(ShellError::NonZeroExitCode { exit_code, span })
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
ExitStatus::Signaled {
|
||||||
|
signal,
|
||||||
|
core_dumped,
|
||||||
|
} => {
|
||||||
|
use nix::sys::signal::Signal;
|
||||||
|
|
||||||
|
let sig = Signal::try_from(signal);
|
||||||
|
|
||||||
|
if sig == Ok(Signal::SIGPIPE) {
|
||||||
|
// Processes often exit with SIGPIPE, but this is not an error condition.
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let signal_name = sig.map(Signal::as_str).unwrap_or("unknown signal").into();
|
||||||
|
Err(if core_dumped {
|
||||||
|
ShellError::CoreDumped {
|
||||||
|
signal_name,
|
||||||
|
signal,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ShellError::TerminatedBySignal {
|
||||||
|
signal_name,
|
||||||
|
signal,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|
|
@ -52,7 +52,7 @@ fn filesize_format_auto_metric_false() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fancy_default_errors() {
|
fn fancy_default_errors() {
|
||||||
let actual = nu!(nu_repl_code(&[
|
let code = nu_repl_code(&[
|
||||||
r#"def force_error [x] {
|
r#"def force_error [x] {
|
||||||
error make {
|
error make {
|
||||||
msg: "oh no!"
|
msg: "oh no!"
|
||||||
|
@ -62,8 +62,10 @@ fn fancy_default_errors() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}"#,
|
}"#,
|
||||||
r#"force_error "My error""#
|
r#"force_error "My error""#,
|
||||||
]));
|
]);
|
||||||
|
|
||||||
|
let actual = nu!(format!("try {{ {code} }}"));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual.err,
|
actual.err,
|
||||||
|
@ -73,7 +75,7 @@ fn fancy_default_errors() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn narratable_errors() {
|
fn narratable_errors() {
|
||||||
let actual = nu!(nu_repl_code(&[
|
let code = nu_repl_code(&[
|
||||||
r#"$env.config = { error_style: "plain" }"#,
|
r#"$env.config = { error_style: "plain" }"#,
|
||||||
r#"def force_error [x] {
|
r#"def force_error [x] {
|
||||||
error make {
|
error make {
|
||||||
|
@ -85,7 +87,9 @@ fn narratable_errors() {
|
||||||
}
|
}
|
||||||
}"#,
|
}"#,
|
||||||
r#"force_error "my error""#,
|
r#"force_error "my error""#,
|
||||||
]));
|
]);
|
||||||
|
|
||||||
|
let actual = nu!(format!("try {{ {code} }}"));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual.err,
|
actual.err,
|
||||||
|
|
32
src/run.rs
32
src/run.rs
|
@ -85,7 +85,7 @@ pub(crate) fn run_commands(
|
||||||
engine_state.generate_nu_constant();
|
engine_state.generate_nu_constant();
|
||||||
|
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
if let Err(err) = evaluate_commands(
|
let result = evaluate_commands(
|
||||||
commands,
|
commands,
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut stack,
|
&mut stack,
|
||||||
|
@ -95,11 +95,13 @@ pub(crate) fn run_commands(
|
||||||
error_style: parsed_nu_cli_args.error_style,
|
error_style: parsed_nu_cli_args.error_style,
|
||||||
no_newline: parsed_nu_cli_args.no_newline.is_some(),
|
no_newline: parsed_nu_cli_args.no_newline.is_some(),
|
||||||
},
|
},
|
||||||
) {
|
);
|
||||||
report_error_new(engine_state, &err);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
perf!("evaluate_commands", start_time, use_color);
|
perf!("evaluate_commands", start_time, use_color);
|
||||||
|
|
||||||
|
if let Err(err) = result {
|
||||||
|
report_error_new(engine_state, &err);
|
||||||
|
std::process::exit(err.exit_code());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn run_file(
|
pub(crate) fn run_file(
|
||||||
|
@ -158,29 +160,19 @@ pub(crate) fn run_file(
|
||||||
engine_state.generate_nu_constant();
|
engine_state.generate_nu_constant();
|
||||||
|
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
if let Err(err) = evaluate_file(
|
let result = evaluate_file(
|
||||||
script_name,
|
script_name,
|
||||||
&args_to_script,
|
&args_to_script,
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut stack,
|
&mut stack,
|
||||||
input,
|
input,
|
||||||
) {
|
);
|
||||||
report_error_new(engine_state, &err);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
perf!("evaluate_file", start_time, use_color);
|
perf!("evaluate_file", start_time, use_color);
|
||||||
|
|
||||||
let start_time = std::time::Instant::now();
|
if let Err(err) = result {
|
||||||
let last_exit_code = stack.get_env_var(&*engine_state, "LAST_EXIT_CODE");
|
report_error_new(engine_state, &err);
|
||||||
if let Some(last_exit_code) = last_exit_code {
|
std::process::exit(err.exit_code());
|
||||||
let value = last_exit_code.as_int();
|
|
||||||
if let Ok(value) = value {
|
|
||||||
if value != 0 {
|
|
||||||
std::process::exit(value as i32);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
perf!("get exit code", start_time, use_color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn run_repl(
|
pub(crate) fn run_repl(
|
||||||
|
|
|
@ -173,13 +173,6 @@ fn basic_outerr_pipe_works(#[case] redirection: &str) {
|
||||||
assert_eq!(actual.out, "7");
|
assert_eq!(actual.out, "7");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn err_pipe_with_failed_external_works() {
|
|
||||||
let actual =
|
|
||||||
nu!(r#"with-env { FOO: "bar" } { nu --testbin echo_env_stderr_fail FOO e>| str length }"#);
|
|
||||||
assert_eq!(actual.out, "3");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dont_run_glob_if_pass_variable_to_external() {
|
fn dont_run_glob_if_pass_variable_to_external() {
|
||||||
Playground::setup("dont_run_glob", |dirs, sandbox| {
|
Playground::setup("dont_run_glob", |dirs, sandbox| {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user