forked from extern/nushell
tweaked the error handling to show specific errors (#3367)
This commit is contained in:
parent
3792562046
commit
a8f555856a
@ -199,247 +199,250 @@ fn spawn(
|
|||||||
trace!(target: "nu::run::external", "built command {:?}", process);
|
trace!(target: "nu::run::external", "built command {:?}", process);
|
||||||
|
|
||||||
// TODO Switch to async_std::process once it's stabilized
|
// TODO Switch to async_std::process once it's stabilized
|
||||||
if let Ok(mut child) = process.spawn() {
|
match process.spawn() {
|
||||||
let (tx, rx) = mpsc::sync_channel(0);
|
Ok(mut child) => {
|
||||||
|
let (tx, rx) = mpsc::sync_channel(0);
|
||||||
|
|
||||||
let mut stdin = child.stdin.take();
|
let mut stdin = child.stdin.take();
|
||||||
|
|
||||||
let stdin_write_tx = tx.clone();
|
let stdin_write_tx = tx.clone();
|
||||||
let stdout_read_tx = tx;
|
let stdout_read_tx = tx;
|
||||||
let stdin_name_tag = command.name_tag.clone();
|
let stdin_name_tag = command.name_tag.clone();
|
||||||
let stdout_name_tag = command.name_tag;
|
let stdout_name_tag = command.name_tag;
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
if !input.is_empty() {
|
if !input.is_empty() {
|
||||||
let mut stdin_write = stdin
|
let mut stdin_write = stdin
|
||||||
.take()
|
.take()
|
||||||
.expect("Internal error: could not get stdin pipe for external command");
|
.expect("Internal error: could not get stdin pipe for external command");
|
||||||
|
|
||||||
for value in input {
|
for value in input {
|
||||||
match &value.value {
|
match &value.value {
|
||||||
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
||||||
UntaggedValue::Primitive(Primitive::String(s)) => {
|
UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||||
if stdin_write.write(s.as_bytes()).is_err() {
|
if stdin_write.write(s.as_bytes()).is_err() {
|
||||||
// Other side has closed, so exit
|
// Other side has closed, so exit
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
|
||||||
}
|
|
||||||
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
|
||||||
if stdin_write.write(b).is_err() {
|
|
||||||
// Other side has closed, so exit
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unsupported => {
|
|
||||||
println!("Unsupported: {:?}", unsupported);
|
|
||||||
let _ = stdin_write_tx.send(Ok(Value {
|
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
|
||||||
format!(
|
|
||||||
"Received unexpected type from pipeline ({})",
|
|
||||||
unsupported.type_name()
|
|
||||||
),
|
|
||||||
"expected a string",
|
|
||||||
stdin_name_tag.clone(),
|
|
||||||
)),
|
|
||||||
tag: stdin_name_tag,
|
|
||||||
}));
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
if external_redirection == ExternalRedirection::Stdout
|
|
||||||
|| external_redirection == ExternalRedirection::StdoutAndStderr
|
|
||||||
{
|
|
||||||
let stdout = if let Some(stdout) = child.stdout.take() {
|
|
||||||
stdout
|
|
||||||
} else {
|
|
||||||
let _ = stdout_read_tx.send(Ok(Value {
|
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
|
||||||
"Can't redirect the stdout for external command",
|
|
||||||
"can't redirect stdout",
|
|
||||||
&stdout_name_tag,
|
|
||||||
)),
|
|
||||||
tag: stdout_name_tag,
|
|
||||||
}));
|
|
||||||
return Err(());
|
|
||||||
};
|
|
||||||
|
|
||||||
// let file = futures::io::AllowStdIo::new(stdout);
|
|
||||||
// let stream = FramedRead::new(file, MaybeTextCodec::default());
|
|
||||||
let buf_read = BufReader::new(stdout);
|
|
||||||
let buf_codec = BufCodecReader::new(buf_read, MaybeTextCodec::default());
|
|
||||||
|
|
||||||
for line in buf_codec {
|
|
||||||
match line {
|
|
||||||
Ok(line) => match line {
|
|
||||||
StringOrBinary::String(s) => {
|
|
||||||
let result = stdout_read_tx.send(Ok(Value {
|
|
||||||
value: UntaggedValue::Primitive(Primitive::String(s.clone())),
|
|
||||||
tag: stdout_name_tag.clone(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
if result.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StringOrBinary::Binary(b) => {
|
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
||||||
let result = stdout_read_tx.send(Ok(Value {
|
if stdin_write.write(b).is_err() {
|
||||||
value: UntaggedValue::Primitive(Primitive::Binary(
|
// Other side has closed, so exit
|
||||||
b.into_iter().collect(),
|
return Ok(());
|
||||||
)),
|
|
||||||
tag: stdout_name_tag.clone(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
if result.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
unsupported => {
|
||||||
Err(e) => {
|
println!("Unsupported: {:?}", unsupported);
|
||||||
// If there's an exit status, it makes sense that we may error when
|
let _ = stdin_write_tx.send(Ok(Value {
|
||||||
// trying to read from its stdout pipe (likely been closed). In that
|
|
||||||
// case, don't emit an error.
|
|
||||||
let should_error = match child.wait() {
|
|
||||||
Ok(exit_status) => !exit_status.success(),
|
|
||||||
Err(_) => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if should_error {
|
|
||||||
let _ = stdout_read_tx.send(Ok(Value {
|
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||||
format!("Unable to read from stdout ({})", e),
|
format!(
|
||||||
"unable to read from stdout",
|
"Received unexpected type from pipeline ({})",
|
||||||
&stdout_name_tag,
|
unsupported.type_name()
|
||||||
|
),
|
||||||
|
"expected a string",
|
||||||
|
stdin_name_tag.clone(),
|
||||||
)),
|
)),
|
||||||
tag: stdout_name_tag.clone(),
|
tag: stdin_name_tag,
|
||||||
}));
|
}));
|
||||||
|
return Err(());
|
||||||
}
|
}
|
||||||
|
};
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if external_redirection == ExternalRedirection::Stderr
|
|
||||||
|| external_redirection == ExternalRedirection::StdoutAndStderr
|
|
||||||
{
|
|
||||||
let stderr = if let Some(stderr) = child.stderr.take() {
|
|
||||||
stderr
|
|
||||||
} else {
|
|
||||||
let _ = stdout_read_tx.send(Ok(Value {
|
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
|
||||||
"Can't redirect the stderr for external command",
|
|
||||||
"can't redirect stderr",
|
|
||||||
&stdout_name_tag,
|
|
||||||
)),
|
|
||||||
tag: stdout_name_tag,
|
|
||||||
}));
|
|
||||||
return Err(());
|
|
||||||
};
|
|
||||||
|
|
||||||
// let file = futures::io::AllowStdIo::new(stderr);
|
Ok(())
|
||||||
// let stream = FramedRead::new(file, MaybeTextCodec::default());
|
});
|
||||||
let buf_reader = BufReader::new(stderr);
|
|
||||||
let buf_codec = BufCodecReader::new(buf_reader, MaybeTextCodec::default());
|
|
||||||
|
|
||||||
for line in buf_codec {
|
std::thread::spawn(move || {
|
||||||
match line {
|
if external_redirection == ExternalRedirection::Stdout
|
||||||
Ok(line) => match line {
|
|| external_redirection == ExternalRedirection::StdoutAndStderr
|
||||||
StringOrBinary::String(s) => {
|
{
|
||||||
let result = stdout_read_tx.send(Ok(Value {
|
let stdout = if let Some(stdout) = child.stdout.take() {
|
||||||
value: UntaggedValue::Error(
|
stdout
|
||||||
ShellError::untagged_runtime_error(s),
|
} else {
|
||||||
),
|
|
||||||
tag: stdout_name_tag.clone(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
if result.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StringOrBinary::Binary(_) => {
|
|
||||||
let result = stdout_read_tx.send(Ok(Value {
|
|
||||||
value: UntaggedValue::Error(
|
|
||||||
ShellError::untagged_runtime_error("<binary stderr>"),
|
|
||||||
),
|
|
||||||
tag: stdout_name_tag.clone(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
if result.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
// If there's an exit status, it makes sense that we may error when
|
|
||||||
// trying to read from its stdout pipe (likely been closed). In that
|
|
||||||
// case, don't emit an error.
|
|
||||||
let should_error = match child.wait() {
|
|
||||||
Ok(exit_status) => !exit_status.success(),
|
|
||||||
Err(_) => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if should_error {
|
|
||||||
let _ = stdout_read_tx.send(Ok(Value {
|
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
|
||||||
format!("Unable to read from stdout ({})", e),
|
|
||||||
"unable to read from stdout",
|
|
||||||
&stdout_name_tag,
|
|
||||||
)),
|
|
||||||
tag: stdout_name_tag.clone(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can give an error when we see a non-zero exit code, but this is different
|
|
||||||
// than what other shells will do.
|
|
||||||
let external_failed = match child.wait() {
|
|
||||||
Err(_) => true,
|
|
||||||
Ok(exit_status) => !exit_status.success(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if external_failed {
|
|
||||||
let cfg = nu_data::config::config(Tag::unknown());
|
|
||||||
if let Ok(cfg) = cfg {
|
|
||||||
if cfg.contains_key("nonzero_exit_errors") {
|
|
||||||
let _ = stdout_read_tx.send(Ok(Value {
|
let _ = stdout_read_tx.send(Ok(Value {
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||||
"External command failed",
|
"Can't redirect the stdout for external command",
|
||||||
"command failed",
|
"can't redirect stdout",
|
||||||
&stdout_name_tag,
|
&stdout_name_tag,
|
||||||
)),
|
)),
|
||||||
tag: stdout_name_tag.clone(),
|
tag: stdout_name_tag,
|
||||||
}));
|
}));
|
||||||
|
return Err(());
|
||||||
|
};
|
||||||
|
|
||||||
|
// let file = futures::io::AllowStdIo::new(stdout);
|
||||||
|
// let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||||
|
let buf_read = BufReader::new(stdout);
|
||||||
|
let buf_codec = BufCodecReader::new(buf_read, MaybeTextCodec::default());
|
||||||
|
|
||||||
|
for line in buf_codec {
|
||||||
|
match line {
|
||||||
|
Ok(line) => match line {
|
||||||
|
StringOrBinary::String(s) => {
|
||||||
|
let result = stdout_read_tx.send(Ok(Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::String(
|
||||||
|
s.clone(),
|
||||||
|
)),
|
||||||
|
tag: stdout_name_tag.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringOrBinary::Binary(b) => {
|
||||||
|
let result = stdout_read_tx.send(Ok(Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::Binary(
|
||||||
|
b.into_iter().collect(),
|
||||||
|
)),
|
||||||
|
tag: stdout_name_tag.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
// If there's an exit status, it makes sense that we may error when
|
||||||
|
// trying to read from its stdout pipe (likely been closed). In that
|
||||||
|
// case, don't emit an error.
|
||||||
|
let should_error = match child.wait() {
|
||||||
|
Ok(exit_status) => !exit_status.success(),
|
||||||
|
Err(_) => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_error {
|
||||||
|
let _ = stdout_read_tx.send(Ok(Value {
|
||||||
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||||
|
format!("Unable to read from stdout ({})", e),
|
||||||
|
"unable to read from stdout",
|
||||||
|
&stdout_name_tag,
|
||||||
|
)),
|
||||||
|
tag: stdout_name_tag.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _ = stdout_read_tx.send(Ok(Value {
|
if external_redirection == ExternalRedirection::Stderr
|
||||||
value: UntaggedValue::Error(ShellError::external_non_zero()),
|
|| external_redirection == ExternalRedirection::StdoutAndStderr
|
||||||
tag: stdout_name_tag,
|
{
|
||||||
}));
|
let stderr = if let Some(stderr) = child.stderr.take() {
|
||||||
}
|
stderr
|
||||||
|
} else {
|
||||||
|
let _ = stdout_read_tx.send(Ok(Value {
|
||||||
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||||
|
"Can't redirect the stderr for external command",
|
||||||
|
"can't redirect stderr",
|
||||||
|
&stdout_name_tag,
|
||||||
|
)),
|
||||||
|
tag: stdout_name_tag,
|
||||||
|
}));
|
||||||
|
return Err(());
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
// let file = futures::io::AllowStdIo::new(stderr);
|
||||||
});
|
// let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||||
|
let buf_reader = BufReader::new(stderr);
|
||||||
|
let buf_codec = BufCodecReader::new(buf_reader, MaybeTextCodec::default());
|
||||||
|
|
||||||
let stream = ChannelReceiver::new(rx);
|
for line in buf_codec {
|
||||||
Ok(stream.to_input_stream())
|
match line {
|
||||||
} else {
|
Ok(line) => match line {
|
||||||
Err(ShellError::labeled_error(
|
StringOrBinary::String(s) => {
|
||||||
"Failed to spawn process",
|
let result = stdout_read_tx.send(Ok(Value {
|
||||||
|
value: UntaggedValue::Error(
|
||||||
|
ShellError::untagged_runtime_error(s),
|
||||||
|
),
|
||||||
|
tag: stdout_name_tag.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringOrBinary::Binary(_) => {
|
||||||
|
let result = stdout_read_tx.send(Ok(Value {
|
||||||
|
value: UntaggedValue::Error(
|
||||||
|
ShellError::untagged_runtime_error("<binary stderr>"),
|
||||||
|
),
|
||||||
|
tag: stdout_name_tag.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
// If there's an exit status, it makes sense that we may error when
|
||||||
|
// trying to read from its stdout pipe (likely been closed). In that
|
||||||
|
// case, don't emit an error.
|
||||||
|
let should_error = match child.wait() {
|
||||||
|
Ok(exit_status) => !exit_status.success(),
|
||||||
|
Err(_) => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_error {
|
||||||
|
let _ = stdout_read_tx.send(Ok(Value {
|
||||||
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||||
|
format!("Unable to read from stdout ({})", e),
|
||||||
|
"unable to read from stdout",
|
||||||
|
&stdout_name_tag,
|
||||||
|
)),
|
||||||
|
tag: stdout_name_tag.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can give an error when we see a non-zero exit code, but this is different
|
||||||
|
// than what other shells will do.
|
||||||
|
let external_failed = match child.wait() {
|
||||||
|
Err(_) => true,
|
||||||
|
Ok(exit_status) => !exit_status.success(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if external_failed {
|
||||||
|
let cfg = nu_data::config::config(Tag::unknown());
|
||||||
|
if let Ok(cfg) = cfg {
|
||||||
|
if cfg.contains_key("nonzero_exit_errors") {
|
||||||
|
let _ = stdout_read_tx.send(Ok(Value {
|
||||||
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||||
|
"External command failed",
|
||||||
|
"command failed",
|
||||||
|
&stdout_name_tag,
|
||||||
|
)),
|
||||||
|
tag: stdout_name_tag.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = stdout_read_tx.send(Ok(Value {
|
||||||
|
value: UntaggedValue::Error(ShellError::external_non_zero()),
|
||||||
|
tag: stdout_name_tag,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
let stream = ChannelReceiver::new(rx);
|
||||||
|
Ok(stream.to_input_stream())
|
||||||
|
}
|
||||||
|
Err(e) => Err(ShellError::labeled_error(
|
||||||
|
format!("{}", e),
|
||||||
"failed to spawn",
|
"failed to spawn",
|
||||||
&command.name_tag,
|
&command.name_tag,
|
||||||
))
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user