mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
Fix bug with multiple input objects to an external command.
Previously, we would build a command that looked something like this: <ex_cmd> "$it" "&&" "<ex_cmd>" "$it" So that the "&&" and "<ex_cmd>" would also be arguments to the command, instead of a chained command. This commit builds up a command string that can be passed to an external shell.
This commit is contained in:
parent
d38b8cf851
commit
ee8cd671cb
3 changed files with 110 additions and 40 deletions
|
@ -225,7 +225,6 @@ impl ExternalCommand {
|
||||||
) -> Result<ClassifiedInputStream, ShellError> {
|
) -> Result<ClassifiedInputStream, ShellError> {
|
||||||
let stdin = input.stdin;
|
let stdin = input.stdin;
|
||||||
let inputs: Vec<Tagged<Value>> = input.objects.into_vec().await;
|
let inputs: Vec<Tagged<Value>> = input.objects.into_vec().await;
|
||||||
let name_tag = self.name_tag.clone();
|
|
||||||
|
|
||||||
trace!(target: "nu::run::external", "-> {}", self.name);
|
trace!(target: "nu::run::external", "-> {}", self.name);
|
||||||
trace!(target: "nu::run::external", "inputs = {:?}", inputs);
|
trace!(target: "nu::run::external", "inputs = {:?}", inputs);
|
||||||
|
@ -235,53 +234,47 @@ impl ExternalCommand {
|
||||||
arg_string.push_str(&arg);
|
arg_string.push_str(&arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trace!(target: "nu::run::external", "command = {:?}", self.name);
|
||||||
|
|
||||||
let mut process;
|
let mut process;
|
||||||
|
|
||||||
process = Exec::cmd(&self.name);
|
|
||||||
|
|
||||||
trace!(target: "nu::run::external", "command = {:?}", process);
|
|
||||||
|
|
||||||
if arg_string.contains("$it") {
|
if arg_string.contains("$it") {
|
||||||
let mut first = true;
|
let input_strings = inputs
|
||||||
|
.iter()
|
||||||
for i in &inputs {
|
.map(|i| {
|
||||||
if i.as_string().is_err() {
|
i.as_string().map_err(|_| {
|
||||||
let mut tag = None;
|
let arg = self.args.iter().find(|arg| arg.item.contains("$it"));
|
||||||
for arg in &self.args {
|
if let Some(arg) = arg {
|
||||||
if arg.item.contains("$it") {
|
ShellError::labeled_error(
|
||||||
tag = Some(arg.tag());
|
"External $it needs string data",
|
||||||
|
"given row instead of string data",
|
||||||
|
arg.tag(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ShellError::labeled_error(
|
||||||
|
"Error: $it needs string data",
|
||||||
|
"given something else",
|
||||||
|
self.name_tag.clone(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
if let Some(tag) = tag {
|
})
|
||||||
return Err(ShellError::labeled_error(
|
.collect::<Result<Vec<String>, ShellError>>()?;
|
||||||
"External $it needs string data",
|
|
||||||
"given row instead of string data",
|
|
||||||
tag,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::labeled_error(
|
|
||||||
"Error: $it needs string data",
|
|
||||||
"given something else",
|
|
||||||
name_tag,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !first {
|
|
||||||
process = process.arg("&&");
|
|
||||||
process = process.arg(&self.name);
|
|
||||||
} else {
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for arg in &self.args {
|
let commands = input_strings.iter().map(|i| {
|
||||||
|
let args = self.args.iter().filter_map(|arg| {
|
||||||
if arg.chars().all(|c| c.is_whitespace()) {
|
if arg.chars().all(|c| c.is_whitespace()) {
|
||||||
continue;
|
None
|
||||||
|
} else {
|
||||||
|
Some(arg.replace("$it", &i))
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
process = process.arg(&arg.replace("$it", &i.as_string()?));
|
format!("{} {}", self.name, itertools::join(args, " "))
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
process = Exec::shell(itertools::join(commands, " && "))
|
||||||
} else {
|
} else {
|
||||||
|
process = Exec::cmd(&self.name);
|
||||||
for arg in &self.args {
|
for arg in &self.args {
|
||||||
let arg_chars: Vec<_> = arg.chars().collect();
|
let arg_chars: Vec<_> = arg.chars().collect();
|
||||||
if arg_chars.len() > 1
|
if arg_chars.len() > 1
|
||||||
|
@ -321,6 +314,7 @@ impl ExternalCommand {
|
||||||
|
|
||||||
trace!(target: "nu::run::external", "next = {:?}", stream_next);
|
trace!(target: "nu::run::external", "next = {:?}", stream_next);
|
||||||
|
|
||||||
|
let name_tag = self.name_tag.clone();
|
||||||
if let Ok(mut popen) = popen {
|
if let Ok(mut popen) = popen {
|
||||||
match stream_next {
|
match stream_next {
|
||||||
StreamNext::Last => {
|
StreamNext::Last => {
|
||||||
|
|
|
@ -164,6 +164,28 @@ fn save_figures_out_intelligently_where_to_write_out_with_metadata() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_arg_works_with_many_inputs_to_external_command() {
|
||||||
|
Playground::setup("it_arg_works_with_many_inputs", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![
|
||||||
|
FileWithContent("file1", "text"),
|
||||||
|
FileWithContent("file2", " and more text"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let (stdout, stderr) = nu_combined!(
|
||||||
|
cwd: dirs.test(), h::pipeline(
|
||||||
|
r#"
|
||||||
|
echo file1 file2
|
||||||
|
| split-row " "
|
||||||
|
| cat $it
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!("text and more text", stdout);
|
||||||
|
assert!(!stderr.contains("No such file or directory"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn save_can_write_out_csv() {
|
fn save_can_write_out_csv() {
|
||||||
Playground::setup("save_test_2", |dirs, _| {
|
Playground::setup("save_test_2", |dirs, _| {
|
||||||
|
|
|
@ -155,6 +155,60 @@ macro_rules! nu_error {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! nu_combined {
|
||||||
|
(cwd: $cwd:expr, $path:expr, $($part:expr),*) => {{
|
||||||
|
use $crate::helpers::DisplayPath;
|
||||||
|
|
||||||
|
let path = format!($path, $(
|
||||||
|
$part.display_path()
|
||||||
|
),*);
|
||||||
|
|
||||||
|
nu_combined!($cwd, &path)
|
||||||
|
}};
|
||||||
|
|
||||||
|
(cwd: $cwd:expr, $path:expr) => {{
|
||||||
|
nu_combined!($cwd, $path)
|
||||||
|
}};
|
||||||
|
|
||||||
|
($cwd:expr, $path:expr) => {{
|
||||||
|
pub use std::error::Error;
|
||||||
|
pub use std::io::prelude::*;
|
||||||
|
pub use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
let commands = &*format!(
|
||||||
|
"
|
||||||
|
cd {}
|
||||||
|
{}
|
||||||
|
exit",
|
||||||
|
$crate::helpers::in_directory($cwd),
|
||||||
|
$crate::helpers::DisplayPath::display_path(&$path)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut process = Command::new(helpers::executable_path())
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("couldn't run test");
|
||||||
|
|
||||||
|
let stdin = process.stdin.as_mut().expect("couldn't open stdin");
|
||||||
|
stdin
|
||||||
|
.write_all(commands.as_bytes())
|
||||||
|
.expect("couldn't write to stdin");
|
||||||
|
|
||||||
|
let output = process
|
||||||
|
.wait_with_output()
|
||||||
|
.expect("couldn't read from stdout/stderr");
|
||||||
|
|
||||||
|
let err = String::from_utf8_lossy(&output.stderr).into_owned();
|
||||||
|
let out = String::from_utf8_lossy(&output.stdout).into_owned();
|
||||||
|
let out = out.replace("\r\n", "");
|
||||||
|
let out = out.replace("\n", "");
|
||||||
|
(out, err)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
pub enum Stub<'a> {
|
pub enum Stub<'a> {
|
||||||
FileWithContent(&'a str, &'a str),
|
FileWithContent(&'a str, &'a str),
|
||||||
FileWithContentToBeTrimmed(&'a str, &'a str),
|
FileWithContentToBeTrimmed(&'a str, &'a str),
|
||||||
|
|
Loading…
Reference in a new issue