2019-11-24 23:19:12 +01:00
|
|
|
use crate::prelude::*;
|
|
|
|
use bytes::{BufMut, BytesMut};
|
|
|
|
use futures::stream::StreamExt;
|
|
|
|
use futures_codec::{Decoder, Encoder, Framed};
|
|
|
|
use log::trace;
|
2019-11-21 18:18:00 +01:00
|
|
|
use nu_errors::ShellError;
|
2019-11-24 23:19:12 +01:00
|
|
|
use nu_parser::ExternalCommand;
|
2019-11-21 18:18:00 +01:00
|
|
|
use nu_protocol::Value;
|
2019-11-24 23:19:12 +01:00
|
|
|
use std::io::{Error, ErrorKind};
|
|
|
|
use subprocess::Exec;
|
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
use super::ClassifiedInputStream;
|
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
/// A simple `Codec` implementation that splits up data into lines.
|
|
|
|
pub struct LinesCodec {}
|
|
|
|
|
|
|
|
impl Encoder for LinesCodec {
|
|
|
|
type Item = String;
|
|
|
|
type Error = Error;
|
|
|
|
|
|
|
|
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
|
|
|
dst.put(item);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Decoder for LinesCodec {
|
2019-12-03 07:44:59 +01:00
|
|
|
type Item = nu_protocol::UntaggedValue;
|
2019-11-24 23:19:12 +01:00
|
|
|
type Error = Error;
|
|
|
|
|
|
|
|
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
|
|
|
match src.iter().position(|b| b == &b'\n') {
|
|
|
|
Some(pos) if !src.is_empty() => {
|
|
|
|
let buf = src.split_to(pos + 1);
|
|
|
|
String::from_utf8(buf.to_vec())
|
2019-12-03 07:44:59 +01:00
|
|
|
.map(value::line)
|
2019-11-24 23:19:12 +01:00
|
|
|
.map(Some)
|
|
|
|
.map_err(|e| Error::new(ErrorKind::InvalidData, e))
|
|
|
|
}
|
|
|
|
_ if !src.is_empty() => {
|
|
|
|
let drained = src.take();
|
|
|
|
String::from_utf8(drained.to_vec())
|
2019-12-03 07:44:59 +01:00
|
|
|
.map(value::string)
|
2019-11-24 23:19:12 +01:00
|
|
|
.map(Some)
|
|
|
|
.map_err(|e| Error::new(ErrorKind::InvalidData, e))
|
|
|
|
}
|
|
|
|
_ => Ok(None),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub(crate) enum StreamNext {
|
|
|
|
Last,
|
|
|
|
External,
|
|
|
|
Internal,
|
|
|
|
}
|
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
pub(crate) async fn run_external_command(
|
|
|
|
command: ExternalCommand,
|
|
|
|
context: &mut Context,
|
|
|
|
input: ClassifiedInputStream,
|
|
|
|
stream_next: StreamNext,
|
|
|
|
) -> Result<ClassifiedInputStream, ShellError> {
|
|
|
|
let stdin = input.stdin;
|
|
|
|
let inputs: Vec<Value> = input.objects.into_vec().await;
|
|
|
|
|
|
|
|
trace!(target: "nu::run::external", "-> {}", command.name);
|
|
|
|
trace!(target: "nu::run::external", "inputs = {:?}", inputs);
|
|
|
|
|
|
|
|
let mut arg_string = format!("{}", command.name);
|
|
|
|
for arg in command.args.iter() {
|
|
|
|
arg_string.push_str(&arg);
|
|
|
|
}
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2019-11-28 17:49:48 +01:00
|
|
|
let home_dir = dirs::home_dir();
|
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
trace!(target: "nu::run::external", "command = {:?}", command.name);
|
|
|
|
|
|
|
|
let mut process;
|
|
|
|
if arg_string.contains("$it") {
|
|
|
|
let input_strings = inputs
|
|
|
|
.iter()
|
|
|
|
.map(|i| {
|
|
|
|
i.as_string().map(|s| s.to_string()).map_err(|_| {
|
|
|
|
let arg = command.args.iter().find(|arg| arg.contains("$it"));
|
|
|
|
if let Some(arg) = arg {
|
|
|
|
ShellError::labeled_error(
|
|
|
|
"External $it needs string data",
|
|
|
|
"given row instead of string data",
|
|
|
|
&arg.tag,
|
|
|
|
)
|
2019-11-24 23:19:12 +01:00
|
|
|
} else {
|
2019-11-24 23:19:12 +01:00
|
|
|
ShellError::labeled_error(
|
|
|
|
"$it needs string data",
|
|
|
|
"given something else",
|
|
|
|
command.name_tag.clone(),
|
|
|
|
)
|
2019-11-24 23:19:12 +01:00
|
|
|
}
|
2019-11-24 23:19:12 +01:00
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<String>, ShellError>>()?;
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
let commands = input_strings.iter().map(|i| {
|
|
|
|
let args = command.args.iter().filter_map(|arg| {
|
|
|
|
if arg.chars().all(|c| c.is_whitespace()) {
|
|
|
|
None
|
2019-11-24 23:19:12 +01:00
|
|
|
} else {
|
2019-11-28 17:49:48 +01:00
|
|
|
// Let's also replace ~ as we shell out
|
|
|
|
let arg = if let Some(ref home_dir) = home_dir {
|
|
|
|
arg.replace("~", home_dir.to_str().unwrap())
|
|
|
|
} else {
|
|
|
|
arg.replace("~", "~")
|
|
|
|
};
|
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
Some(arg.replace("$it", &i))
|
2019-11-24 23:19:12 +01:00
|
|
|
}
|
2019-11-24 23:19:12 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
format!("{} {}", command.name, itertools::join(args, " "))
|
|
|
|
});
|
|
|
|
|
|
|
|
process = Exec::shell(itertools::join(commands, " && "))
|
|
|
|
} else {
|
|
|
|
process = Exec::cmd(&command.name);
|
|
|
|
for arg in command.args.iter() {
|
2019-11-28 17:49:48 +01:00
|
|
|
// Let's also replace ~ as we shell out
|
|
|
|
let arg = if let Some(ref home_dir) = home_dir {
|
|
|
|
arg.replace("~", home_dir.to_str().unwrap())
|
|
|
|
} else {
|
|
|
|
arg.replace("~", "~")
|
|
|
|
};
|
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
let arg_chars: Vec<_> = arg.chars().collect();
|
|
|
|
if arg_chars.len() > 1 && arg_chars[0] == '"' && arg_chars[arg_chars.len() - 1] == '"' {
|
|
|
|
// quoted string
|
|
|
|
let new_arg: String = arg_chars[1..arg_chars.len() - 1].iter().collect();
|
|
|
|
process = process.arg(new_arg);
|
|
|
|
} else {
|
2019-11-28 17:49:48 +01:00
|
|
|
process = process.arg(arg.clone());
|
2019-11-24 23:19:12 +01:00
|
|
|
}
|
|
|
|
}
|
2019-11-24 23:19:12 +01:00
|
|
|
}
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
process = process.cwd(context.shell_manager.path());
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
trace!(target: "nu::run::external", "cwd = {:?}", context.shell_manager.path());
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
let mut process = match stream_next {
|
|
|
|
StreamNext::Last => process,
|
|
|
|
StreamNext::External | StreamNext::Internal => {
|
|
|
|
process.stdout(subprocess::Redirection::Pipe)
|
|
|
|
}
|
|
|
|
};
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
trace!(target: "nu::run::external", "set up stdout pipe");
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
if let Some(stdin) = stdin {
|
|
|
|
process = process.stdin(stdin);
|
|
|
|
}
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
trace!(target: "nu::run::external", "set up stdin pipe");
|
|
|
|
trace!(target: "nu::run::external", "built process {:?}", process);
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
let popen = process.popen();
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
trace!(target: "nu::run::external", "next = {:?}", stream_next);
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
let name_tag = command.name_tag.clone();
|
|
|
|
if let Ok(mut popen) = popen {
|
|
|
|
match stream_next {
|
|
|
|
StreamNext::Last => {
|
|
|
|
let _ = popen.detach();
|
|
|
|
loop {
|
|
|
|
match popen.poll() {
|
|
|
|
None => {
|
|
|
|
let _ = std::thread::sleep(std::time::Duration::new(0, 100000000));
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
let _ = popen.terminate();
|
|
|
|
break;
|
2019-11-24 23:19:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-11-24 23:19:12 +01:00
|
|
|
Ok(ClassifiedInputStream::new())
|
|
|
|
}
|
|
|
|
StreamNext::External => {
|
|
|
|
let _ = popen.detach();
|
|
|
|
let stdout = popen.stdout.take().unwrap();
|
|
|
|
Ok(ClassifiedInputStream::from_stdout(stdout))
|
|
|
|
}
|
|
|
|
StreamNext::Internal => {
|
|
|
|
let _ = popen.detach();
|
|
|
|
let stdout = popen.stdout.take().unwrap();
|
|
|
|
let file = futures::io::AllowStdIo::new(stdout);
|
|
|
|
let stream = Framed::new(file, LinesCodec {});
|
2019-12-03 07:44:59 +01:00
|
|
|
let stream = stream.map(move |line| line.unwrap().into_value(&name_tag));
|
2019-11-24 23:19:12 +01:00
|
|
|
Ok(ClassifiedInputStream::from_input_stream(
|
|
|
|
stream.boxed() as BoxStream<'static, Value>
|
|
|
|
))
|
2019-11-24 23:19:12 +01:00
|
|
|
}
|
|
|
|
}
|
2019-11-24 23:19:12 +01:00
|
|
|
} else {
|
|
|
|
return Err(ShellError::labeled_error(
|
|
|
|
"Command not found",
|
|
|
|
"command not found",
|
|
|
|
name_tag,
|
|
|
|
));
|
2019-11-26 04:25:12 +01:00
|
|
|
}
|
|
|
|
}
|