Improve shelling out (#1273)

Improvements to shelling out
This commit is contained in:
Jonathan Turner 2020-01-24 08:24:31 +13:00 committed by GitHub
parent 2b37ae3e81
commit d38a63473b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 81 deletions

23
Cargo.lock generated
View file

@ -552,12 +552,6 @@ dependencies = [
"crossbeam-utils 0.7.0", "crossbeam-utils 0.7.0",
] ]
[[package]]
name = "crossbeam-utils"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015"
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.6.6" version = "0.6.6"
@ -2301,6 +2295,7 @@ dependencies = [
"unicode-xid", "unicode-xid",
"url", "url",
"users", "users",
"which",
] ]
[[package]] [[package]]
@ -3802,11 +3797,11 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]] [[package]]
name = "subprocess" name = "subprocess"
version = "0.1.18" version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28fc0f40f0c0da73339d347aa7d6d2b90341a95683a47722bc4eebed71ff3c00" checksum = "68713fc0f9d941642c1e020d622e6421dfe09e8891ddd4bfa2109fda9a40431d"
dependencies = [ dependencies = [
"crossbeam-utils 0.5.0", "crossbeam-utils 0.7.0",
"libc", "libc",
"winapi 0.3.8", "winapi 0.3.8",
] ]
@ -4452,6 +4447,16 @@ dependencies = [
"nom 4.2.3", "nom 4.2.3",
] ]
[[package]]
name = "which"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5475d47078209a02e60614f7ba5e645ef3ed60f771920ac1906d7c1cc65024c8"
dependencies = [
"failure",
"libc",
]
[[package]] [[package]]
name = "widestring" name = "widestring"
version = "0.4.0" version = "0.4.0"

View file

@ -101,10 +101,11 @@ nom_locate = "1.0.0"
nom-tracable = "0.4.1" nom-tracable = "0.4.1"
unicode-xid = "0.2.0" unicode-xid = "0.2.0"
serde_ini = "0.2.0" serde_ini = "0.2.0"
subprocess = "0.1.18" subprocess = "0.1.20"
pretty-hex = "0.1.1" pretty-hex = "0.1.1"
hex = "0.4" hex = "0.4"
tempfile = "3.1.0" tempfile = "3.1.0"
which = "3.1.0"
ichwh = "0.3" ichwh = "0.3"
textwrap = {version = "0.11.0", features = ["term_size"]} textwrap = {version = "0.11.0", features = ["term_size"]}
shellexpand = "1.1.1" shellexpand = "1.1.1"

View file

@ -89,6 +89,14 @@ pub(crate) async fn run_external_command(
) -> Result<Option<InputStream>, ShellError> { ) -> Result<Option<InputStream>, ShellError> {
trace!(target: "nu::run::external", "-> {}", command.name); trace!(target: "nu::run::external", "-> {}", command.name);
if !did_find_command(&command.name) {
return Err(ShellError::labeled_error(
"Command not found",
"command not found",
&command.name_tag,
));
}
if command.has_it_argument() { if command.has_it_argument() {
run_with_iterator_arg(command, context, input, is_last).await run_with_iterator_arg(command, context, input, is_last).await
} else { } else {
@ -333,13 +341,23 @@ async fn spawn(
} }
} }
// We can give an error when we see a non-zero exit code, but this is different
// than what other shells will do.
let cfg = crate::data::config::config(Tag::unknown());
if let Ok(cfg) = cfg {
if cfg.contains_key("nonzero_exit_errors") {
yield Ok(Value { yield Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error( value: UntaggedValue::Error(
ShellError::labeled_error(
"External command failed", "External command failed",
"command failed", "command failed",
&name_tag)), &name_tag,
tag: name_tag )
),
tag: name_tag,
}); });
}
}
return; return;
} }
@ -381,6 +399,11 @@ async fn spawn(
None => futures_timer::Delay::new(std::time::Duration::from_millis(10)).await, None => futures_timer::Delay::new(std::time::Duration::from_millis(10)).await,
Some(status) => { Some(status) => {
if !status.success() { if !status.success() {
// We can give an error when we see a non-zero exit code, but this is different
// than what other shells will do.
let cfg = crate::data::config::config(Tag::unknown());
if let Ok(cfg) = cfg {
if cfg.contains_key("nonzero_exit_errors") {
yield Ok(Value { yield Ok(Value {
value: UntaggedValue::Error( value: UntaggedValue::Error(
ShellError::labeled_error( ShellError::labeled_error(
@ -392,6 +415,8 @@ async fn spawn(
tag: name_tag, tag: name_tag,
}); });
} }
}
}
break; break;
} }
} }
@ -408,6 +433,27 @@ async fn spawn(
} }
} }
fn did_find_command(name: &str) -> bool {
#[cfg(not(windows))]
{
which::which(name).is_ok()
}
#[cfg(windows)]
{
if which::which(name).is_ok() {
true
} else {
let cmd_builtins = [
"call", "cls", "color", "date", "dir", "echo", "find", "hostname", "pause",
"start", "time", "title", "ver", "copy", "mkdir", "rename", "rd", "rmdir", "type",
];
cmd_builtins.contains(&name)
}
}
}
fn expand_tilde<SI: ?Sized, P, HD>(input: &SI, home_dir: HD) -> std::borrow::Cow<str> fn expand_tilde<SI: ?Sized, P, HD>(input: &SI, home_dir: HD) -> std::borrow::Cow<str>
where where
SI: AsRef<str>, SI: AsRef<str>,
@ -459,70 +505,60 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
mod tests { mod tests {
use super::{ use super::{
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes, add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
run_external_command, Context, OutputStream, run_external_command, Context,
}; };
use futures::executor::block_on; use futures::executor::block_on;
use futures::stream::TryStreamExt;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
use nu_test_support::commands::ExternalBuilder; use nu_test_support::commands::ExternalBuilder;
async fn read(mut stream: OutputStream) -> Option<Value> { // async fn read(mut stream: OutputStream) -> Option<Value> {
match stream.try_next().await { // match stream.try_next().await {
Ok(val) => { // Ok(val) => {
if let Some(val) = val { // if let Some(val) = val {
val.raw_value() // val.raw_value()
} else { // } else {
None // None
} // }
} // }
Err(_) => None, // Err(_) => None,
} // }
} // }
async fn non_existent_run() -> Result<(), ShellError> { async fn non_existent_run() -> Result<(), ShellError> {
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build(); let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
let mut ctx = Context::basic().expect("There was a problem creating a basic context."); let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
let stream = run_external_command(cmd, &mut ctx, None, false) assert!(run_external_command(cmd, &mut ctx, None, false)
.await? .await
.expect("There was a problem running the external command."); .is_err());
match read(stream.into()).await {
Some(Value {
value: UntaggedValue::Error(_),
..
}) => {}
None | _ => panic!("Apparently a command was found (It's not supposed to be found)"),
}
Ok(()) Ok(())
} }
async fn failure_run() -> Result<(), ShellError> { // async fn failure_run() -> Result<(), ShellError> {
let cmd = ExternalBuilder::for_name("fail").build(); // let cmd = ExternalBuilder::for_name("fail").build();
let mut ctx = Context::basic().expect("There was a problem creating a basic context."); // let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
let stream = run_external_command(cmd, &mut ctx, None, false) // let stream = run_external_command(cmd, &mut ctx, None, false)
.await? // .await?
.expect("There was a problem running the external command."); // .expect("There was a problem running the external command.");
match read(stream.into()).await { // match read(stream.into()).await {
Some(Value { // Some(Value {
value: UntaggedValue::Error(_), // value: UntaggedValue::Error(_),
.. // ..
}) => {} // }) => {}
None | _ => panic!("Command didn't fail."), // None | _ => panic!("Command didn't fail."),
} // }
Ok(()) // Ok(())
} // }
#[test] // #[test]
fn identifies_command_failed() -> Result<(), ShellError> { // fn identifies_command_failed() -> Result<(), ShellError> {
block_on(failure_run()) // block_on(failure_run())
} // }
#[test] #[test]
fn identifies_command_not_found() -> Result<(), ShellError> { fn identifies_command_not_found() -> Result<(), ShellError> {

View file

@ -1,14 +1,14 @@
use nu_test_support::{nu, nu_error}; use nu_test_support::{nu, nu_error};
#[test] // #[test]
fn shows_error_for_command_that_fails() { // fn shows_error_for_command_that_fails() {
let actual = nu_error!( // let actual = nu_error!(
cwd: ".", // cwd: ".",
"fail" // "fail"
); // );
assert!(actual.contains("External command failed")); // assert!(actual.contains("External command failed"));
} // }
#[test] #[test]
fn shows_error_for_command_not_found() { fn shows_error_for_command_not_found() {
@ -17,7 +17,7 @@ fn shows_error_for_command_not_found() {
"ferris_is_not_here.exe" "ferris_is_not_here.exe"
); );
assert!(actual.contains("External command failed")); assert!(actual.contains("Command not found"));
} }
mod it_evaluation { mod it_evaluation {