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;
|
2020-01-07 22:00:01 +01:00
|
|
|
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
|
|
|
use std::io::{Error, ErrorKind, Write};
|
2019-12-06 19:07:53 +01:00
|
|
|
use std::ops::Deref;
|
2019-11-24 23:19:12 +01:00
|
|
|
use subprocess::Exec;
|
|
|
|
|
|
|
|
/// 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-04 20:52:31 +01:00
|
|
|
.map(UntaggedValue::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-04 20:52:31 +01:00
|
|
|
.map(UntaggedValue::string)
|
2019-11-24 23:19:12 +01:00
|
|
|
.map(Some)
|
|
|
|
.map_err(|e| Error::new(ErrorKind::InvalidData, e))
|
|
|
|
}
|
|
|
|
_ => Ok(None),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
pub fn nu_value_to_string(command: &ExternalCommand, from: &Value) -> Result<String, ShellError> {
|
|
|
|
match &from.value {
|
|
|
|
UntaggedValue::Primitive(Primitive::Int(i)) => Ok(i.to_string()),
|
|
|
|
UntaggedValue::Primitive(Primitive::String(s))
|
|
|
|
| UntaggedValue::Primitive(Primitive::Line(s)) => Ok(s.clone()),
|
|
|
|
UntaggedValue::Primitive(Primitive::Path(p)) => Ok(p.to_string_lossy().to_string()),
|
|
|
|
unsupported => Err(ShellError::labeled_error(
|
|
|
|
format!("$it needs string data (given: {})", unsupported.type_name()),
|
|
|
|
"expected a string",
|
|
|
|
&command.name_tag,
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn nu_value_to_string_for_stdin(
|
|
|
|
command: &ExternalCommand,
|
|
|
|
from: &Value,
|
|
|
|
) -> Result<Option<String>, ShellError> {
|
|
|
|
match &from.value {
|
|
|
|
UntaggedValue::Primitive(Primitive::Nothing) => Ok(None),
|
|
|
|
UntaggedValue::Primitive(Primitive::String(s))
|
|
|
|
| UntaggedValue::Primitive(Primitive::Line(s)) => Ok(Some(s.clone())),
|
|
|
|
unsupported => Err(ShellError::labeled_error(
|
|
|
|
format!(
|
|
|
|
"Received unexpected type from pipeline ({})",
|
|
|
|
unsupported.type_name()
|
|
|
|
),
|
|
|
|
"expected a string",
|
|
|
|
&command.name_tag,
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-07 22:00:01 +01:00
|
|
|
pub(crate) async fn run_external_command(
|
|
|
|
command: ExternalCommand,
|
|
|
|
context: &mut Context,
|
2020-01-10 07:31:44 +01:00
|
|
|
input: Option<InputStream>,
|
2020-01-07 22:00:01 +01:00
|
|
|
is_last: bool,
|
2020-01-10 07:31:44 +01:00
|
|
|
) -> Result<Option<InputStream>, ShellError> {
|
2020-01-07 22:00:01 +01:00
|
|
|
trace!(target: "nu::run::external", "-> {}", command.name);
|
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
if command.has_it_argument() {
|
2020-01-07 22:00:01 +01:00
|
|
|
run_with_iterator_arg(command, context, input, is_last).await
|
|
|
|
} else {
|
|
|
|
run_with_stdin(command, context, input, is_last).await
|
|
|
|
}
|
2019-11-24 23:19:12 +01:00
|
|
|
}
|
|
|
|
|
2020-01-07 22:00:01 +01:00
|
|
|
async fn run_with_iterator_arg(
|
2019-11-24 23:19:12 +01:00
|
|
|
command: ExternalCommand,
|
|
|
|
context: &mut Context,
|
2020-01-10 07:31:44 +01:00
|
|
|
input: Option<InputStream>,
|
2020-01-07 22:00:01 +01:00
|
|
|
is_last: bool,
|
2020-01-10 07:31:44 +01:00
|
|
|
) -> Result<Option<InputStream>, ShellError> {
|
2020-01-16 10:05:53 +01:00
|
|
|
let path = context.shell_manager.path()?;
|
|
|
|
|
|
|
|
let mut inputs: InputStream = if let Some(input) = input {
|
|
|
|
trace_stream!(target: "nu::trace_stream::external::it", "input" = input)
|
|
|
|
} else {
|
|
|
|
InputStream::empty()
|
|
|
|
};
|
|
|
|
|
|
|
|
let stream = async_stream! {
|
|
|
|
while let Some(value) = inputs.next().await {
|
|
|
|
let name = command.name.clone();
|
|
|
|
let name_tag = command.name_tag.clone();
|
|
|
|
let home_dir = dirs::home_dir();
|
|
|
|
let path = &path;
|
|
|
|
let args = command.args.clone();
|
|
|
|
|
|
|
|
let it_replacement = match nu_value_to_string(&command, &value) {
|
|
|
|
Ok(value) => value,
|
|
|
|
Err(reason) => {
|
|
|
|
yield Ok(Value {
|
|
|
|
value: UntaggedValue::Error(reason),
|
|
|
|
tag: name_tag
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let process_args = args.iter().filter_map(|arg| {
|
|
|
|
if arg.chars().all(|c| c.is_whitespace()) {
|
|
|
|
None
|
2020-01-07 22:00:01 +01:00
|
|
|
} else {
|
2020-01-16 10:05:53 +01:00
|
|
|
let arg = if arg.is_it() {
|
|
|
|
let value = it_replacement.to_owned();
|
|
|
|
let value = expand_tilde(&value, || home_dir.as_ref()).as_ref().to_string();
|
|
|
|
let value = {
|
|
|
|
if argument_contains_whitespace(&value) && !argument_is_quoted(&value) {
|
|
|
|
add_quotes(&value)
|
|
|
|
} else {
|
|
|
|
value
|
|
|
|
}
|
|
|
|
};
|
|
|
|
value
|
|
|
|
} else {
|
|
|
|
arg.to_string()
|
|
|
|
};
|
|
|
|
|
|
|
|
Some(arg)
|
2020-01-07 22:00:01 +01:00
|
|
|
}
|
2020-01-16 10:05:53 +01:00
|
|
|
}).collect::<Vec<String>>();
|
|
|
|
|
|
|
|
match spawn(&command, &path, &process_args[..], None, is_last).await {
|
|
|
|
Ok(res) => {
|
|
|
|
if let Some(mut res) = res {
|
|
|
|
while let Some(item) = res.next().await {
|
|
|
|
yield Ok(item)
|
2020-01-16 01:38:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-01-16 10:05:53 +01:00
|
|
|
Err(reason) => {
|
|
|
|
yield Ok(Value {
|
|
|
|
value: UntaggedValue::Error(reason),
|
|
|
|
tag: name_tag
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2020-01-07 22:00:01 +01:00
|
|
|
}
|
2020-01-16 10:05:53 +01:00
|
|
|
}
|
|
|
|
};
|
2019-11-28 17:49:48 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
Ok(Some(stream.to_input_stream()))
|
|
|
|
}
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
async fn run_with_stdin(
|
|
|
|
command: ExternalCommand,
|
|
|
|
context: &mut Context,
|
|
|
|
input: Option<InputStream>,
|
|
|
|
is_last: bool,
|
|
|
|
) -> Result<Option<InputStream>, ShellError> {
|
|
|
|
let path = context.shell_manager.path()?;
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
let mut inputs: InputStream = if let Some(input) = input {
|
|
|
|
trace_stream!(target: "nu::trace_stream::external::stdin", "input" = input)
|
2020-01-07 22:00:01 +01:00
|
|
|
} else {
|
2020-01-16 10:05:53 +01:00
|
|
|
InputStream::empty()
|
|
|
|
};
|
2020-01-16 01:38:16 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
let stream = async_stream! {
|
|
|
|
while let Some(value) = inputs.next().await {
|
|
|
|
let name = command.name.clone();
|
|
|
|
let name_tag = command.name_tag.clone();
|
|
|
|
let home_dir = dirs::home_dir();
|
|
|
|
let path = &path;
|
|
|
|
let args = command.args.clone();
|
|
|
|
|
|
|
|
let value_for_stdin = match nu_value_to_string_for_stdin(&command, &value) {
|
|
|
|
Ok(value) => value,
|
|
|
|
Err(reason) => {
|
|
|
|
yield Ok(Value {
|
|
|
|
value: UntaggedValue::Error(reason),
|
|
|
|
tag: name_tag
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
2020-01-12 22:44:22 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
let process_args = args.iter().map(|arg| {
|
|
|
|
let arg = expand_tilde(arg.deref(), || home_dir.as_ref());
|
|
|
|
if let Some(unquoted) = remove_quotes(&arg) {
|
|
|
|
unquoted.to_string()
|
|
|
|
} else {
|
|
|
|
arg.as_ref().to_string()
|
|
|
|
}
|
|
|
|
}).collect::<Vec<String>>();
|
2020-01-16 01:38:16 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
match spawn(&command, &path, &process_args[..], value_for_stdin, is_last).await {
|
|
|
|
Ok(res) => {
|
|
|
|
if let Some(mut res) = res {
|
|
|
|
while let Some(item) = res.next().await {
|
|
|
|
yield Ok(item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(reason) => {
|
|
|
|
yield Ok(Value {
|
|
|
|
value: UntaggedValue::Error(reason),
|
|
|
|
tag: name_tag
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2020-01-12 22:44:22 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
Ok(Some(stream.to_input_stream()))
|
2020-01-12 22:44:22 +01:00
|
|
|
}
|
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
async fn spawn(
|
|
|
|
command: &ExternalCommand,
|
|
|
|
path: &str,
|
|
|
|
args: &[String],
|
|
|
|
stdin_contents: Option<String>,
|
2020-01-07 22:00:01 +01:00
|
|
|
is_last: bool,
|
2020-01-10 07:31:44 +01:00
|
|
|
) -> Result<Option<InputStream>, ShellError> {
|
2020-01-16 10:05:53 +01:00
|
|
|
let command = command.clone();
|
|
|
|
let name_tag = command.name_tag.clone();
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2020-01-07 22:00:01 +01:00
|
|
|
let mut process = Exec::cmd(&command.name);
|
2020-01-12 22:44:22 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
for arg in args {
|
|
|
|
process = process.arg(&arg);
|
2020-01-07 22:00:01 +01:00
|
|
|
}
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
process = process.cwd(path);
|
|
|
|
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
// We want stdout regardless of what
|
|
|
|
// we are doing ($it case or pipe stdin)
|
2020-01-07 22:00:01 +01:00
|
|
|
if !is_last {
|
|
|
|
process = process.stdout(subprocess::Redirection::Pipe);
|
|
|
|
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
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
// open since we have some contents for stdin
|
|
|
|
if stdin_contents.is_some() {
|
2020-01-10 07:31:44 +01:00
|
|
|
process = process.stdin(subprocess::Redirection::Pipe);
|
|
|
|
trace!(target: "nu::run::external", "set up stdin pipe");
|
|
|
|
}
|
2020-01-07 22:00:01 +01:00
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
trace!(target: "nu::run::external", "built process {:?}", process);
|
2019-11-24 23:19:12 +01:00
|
|
|
|
2020-01-07 22:00:01 +01:00
|
|
|
let popen = process.detached().popen();
|
2020-01-16 10:05:53 +01:00
|
|
|
|
2020-01-07 22:00:01 +01:00
|
|
|
if let Ok(mut popen) = popen {
|
|
|
|
let stream = async_stream! {
|
2020-01-16 10:05:53 +01:00
|
|
|
if let Some(mut input) = stdin_contents.as_ref() {
|
|
|
|
let mut stdin_write = popen.stdin
|
2020-01-10 07:31:44 +01:00
|
|
|
.take()
|
|
|
|
.expect("Internal error: could not get stdin pipe for external command");
|
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
if let Err(e) = stdin_write.write(input.as_bytes()) {
|
|
|
|
let message = format!("Unable to write to stdin (error = {})", e);
|
2020-01-10 07:31:44 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
yield Ok(Value {
|
|
|
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
|
|
|
message,
|
|
|
|
"application may have closed before completing pipeline",
|
|
|
|
&name_tag)),
|
|
|
|
tag: name_tag
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2020-01-10 07:31:44 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
drop(stdin_write);
|
|
|
|
}
|
|
|
|
|
|
|
|
if is_last && command.has_it_argument() {
|
|
|
|
if let Ok(status) = popen.wait() {
|
|
|
|
if status.success() {
|
|
|
|
return;
|
2020-01-10 07:31:44 +01:00
|
|
|
}
|
2020-01-07 22:00:01 +01:00
|
|
|
}
|
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
yield Ok(Value {
|
|
|
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
|
|
|
"External command failed",
|
|
|
|
"command failed",
|
|
|
|
&name_tag)),
|
|
|
|
tag: name_tag
|
|
|
|
});
|
|
|
|
return;
|
2020-01-10 07:31:44 +01:00
|
|
|
}
|
2020-01-07 22:00:01 +01:00
|
|
|
|
|
|
|
if !is_last {
|
|
|
|
let stdout = if let Some(stdout) = popen.stdout.take() {
|
|
|
|
stdout
|
|
|
|
} else {
|
|
|
|
yield Ok(Value {
|
2020-01-16 10:05:53 +01:00
|
|
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
|
|
|
"Can't redirect the stdout for external command",
|
|
|
|
"can't redirect stdout",
|
|
|
|
&name_tag)),
|
|
|
|
tag: name_tag
|
2020-01-07 22:00:01 +01:00
|
|
|
});
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
2019-11-24 23:19:12 +01:00
|
|
|
let file = futures::io::AllowStdIo::new(stdout);
|
|
|
|
let stream = Framed::new(file, LinesCodec {});
|
2020-01-16 10:05:53 +01:00
|
|
|
|
2020-01-07 22:00:01 +01:00
|
|
|
let mut stream = stream.map(|line| {
|
2020-01-04 07:44:17 +01:00
|
|
|
if let Ok(line) = line {
|
|
|
|
line.into_value(&name_tag)
|
|
|
|
} else {
|
|
|
|
panic!("Internal error: could not read lines of text from stdin")
|
|
|
|
}
|
|
|
|
});
|
2020-01-07 22:00:01 +01:00
|
|
|
|
|
|
|
loop {
|
|
|
|
match stream.next().await {
|
|
|
|
Some(item) => yield Ok(item),
|
|
|
|
None => break,
|
|
|
|
}
|
|
|
|
}
|
2019-11-24 23:19:12 +01:00
|
|
|
}
|
2020-01-07 22:00:01 +01:00
|
|
|
|
2020-01-13 07:38:58 +01:00
|
|
|
loop {
|
|
|
|
match popen.poll() {
|
2020-01-16 12:27:12 +01:00
|
|
|
None => futures_timer::Delay::new(std::time::Duration::from_millis(10)).await,
|
2020-01-13 07:38:58 +01:00
|
|
|
Some(status) => {
|
|
|
|
if !status.success() {
|
|
|
|
yield Ok(Value {
|
|
|
|
value: UntaggedValue::Error(
|
|
|
|
ShellError::labeled_error(
|
|
|
|
"External command failed",
|
|
|
|
"command failed",
|
|
|
|
&name_tag,
|
|
|
|
)
|
|
|
|
),
|
|
|
|
tag: name_tag,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-01-07 22:00:01 +01:00
|
|
|
};
|
|
|
|
|
2020-01-10 07:31:44 +01:00
|
|
|
Ok(Some(stream.to_input_stream()))
|
2019-11-24 23:19:12 +01:00
|
|
|
} else {
|
2019-12-06 16:28:26 +01:00
|
|
|
Err(ShellError::labeled_error(
|
2019-11-24 23:19:12 +01:00
|
|
|
"Command not found",
|
|
|
|
"command not found",
|
2020-01-16 10:05:53 +01:00
|
|
|
&command.name_tag,
|
2019-12-06 16:28:26 +01:00
|
|
|
))
|
2019-11-26 04:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
2020-01-12 22:44:22 +01:00
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
fn expand_tilde<SI: ?Sized, P, HD>(input: &SI, home_dir: HD) -> std::borrow::Cow<str>
|
|
|
|
where
|
|
|
|
SI: AsRef<str>,
|
|
|
|
P: AsRef<std::path::Path>,
|
|
|
|
HD: FnOnce() -> Option<P>,
|
|
|
|
{
|
|
|
|
shellexpand::tilde_with_context(input, home_dir)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn argument_contains_whitespace(argument: &str) -> bool {
|
|
|
|
argument.chars().any(|c| c.is_whitespace())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn argument_is_quoted(argument: &str) -> bool {
|
|
|
|
if argument.len() < 2 {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
(argument.starts_with('"') && argument.ends_with('"')
|
|
|
|
|| (argument.starts_with('\'') && argument.ends_with('\'')))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_quotes(argument: &str) -> String {
|
|
|
|
format!("'{}'", argument)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn remove_quotes(argument: &str) -> Option<&str> {
|
|
|
|
if !argument_is_quoted(argument) {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
let size = argument.len();
|
|
|
|
|
|
|
|
Some(&argument[1..size - 1])
|
|
|
|
}
|
|
|
|
|
2020-01-12 22:44:22 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-01-16 01:38:16 +01:00
|
|
|
use super::{
|
2020-01-16 10:05:53 +01:00
|
|
|
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
2020-01-16 01:38:16 +01:00
|
|
|
run_external_command, Context, OutputStream,
|
|
|
|
};
|
2020-01-12 22:44:22 +01:00
|
|
|
use futures::executor::block_on;
|
|
|
|
use futures::stream::TryStreamExt;
|
|
|
|
use nu_errors::ShellError;
|
|
|
|
use nu_protocol::{UntaggedValue, Value};
|
2020-01-16 10:05:53 +01:00
|
|
|
use nu_test_support::commands::ExternalBuilder;
|
2020-01-12 22:44:22 +01:00
|
|
|
|
|
|
|
async fn read(mut stream: OutputStream) -> Option<Value> {
|
|
|
|
match stream.try_next().await {
|
|
|
|
Ok(val) => {
|
|
|
|
if let Some(val) = val {
|
|
|
|
val.raw_value()
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(_) => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn non_existent_run() -> Result<(), ShellError> {
|
2020-01-16 10:05:53 +01:00
|
|
|
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
2020-01-12 22:44:22 +01:00
|
|
|
|
|
|
|
let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
|
|
|
|
2020-01-16 10:05:53 +01:00
|
|
|
let stream = run_external_command(cmd, &mut ctx, None, false)
|
|
|
|
.await?
|
|
|
|
.expect("There was a problem running the external command.");
|
|
|
|
|
|
|
|
match read(stream.into()).await {
|
|
|
|
Some(Value {
|
|
|
|
value: UntaggedValue::Error(_),
|
|
|
|
..
|
|
|
|
}) => {}
|
|
|
|
None | _ => panic!("Apparently a command was found (It's not supposed to be found)"),
|
|
|
|
}
|
2020-01-12 22:44:22 +01:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn failure_run() -> Result<(), ShellError> {
|
2020-01-16 10:05:53 +01:00
|
|
|
let cmd = ExternalBuilder::for_name("fail").build();
|
2020-01-12 22:44:22 +01:00
|
|
|
|
|
|
|
let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
|
|
|
let stream = run_external_command(cmd, &mut ctx, None, false)
|
|
|
|
.await?
|
|
|
|
.expect("There was a problem running the external command.");
|
|
|
|
|
|
|
|
match read(stream.into()).await {
|
|
|
|
Some(Value {
|
|
|
|
value: UntaggedValue::Error(_),
|
|
|
|
..
|
|
|
|
}) => {}
|
|
|
|
None | _ => panic!("Command didn't fail."),
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn identifies_command_failed() -> Result<(), ShellError> {
|
|
|
|
block_on(failure_run())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn identifies_command_not_found() -> Result<(), ShellError> {
|
|
|
|
block_on(non_existent_run())
|
|
|
|
}
|
|
|
|
|
2020-01-16 01:38:16 +01:00
|
|
|
#[test]
|
|
|
|
fn checks_contains_whitespace_from_argument_to_be_passed_in() {
|
|
|
|
assert_eq!(argument_contains_whitespace("andrés"), false);
|
|
|
|
assert_eq!(argument_contains_whitespace("and rés"), true);
|
|
|
|
assert_eq!(argument_contains_whitespace(r#"and\ rés"#), true);
|
|
|
|
}
|
|
|
|
|
2020-01-12 22:44:22 +01:00
|
|
|
#[test]
|
|
|
|
fn checks_quotes_from_argument_to_be_passed_in() {
|
2020-01-16 10:05:53 +01:00
|
|
|
assert_eq!(argument_is_quoted(""), false);
|
|
|
|
|
|
|
|
assert_eq!(argument_is_quoted("'"), false);
|
|
|
|
assert_eq!(argument_is_quoted("'a"), false);
|
|
|
|
assert_eq!(argument_is_quoted("a"), false);
|
|
|
|
assert_eq!(argument_is_quoted("a'"), false);
|
|
|
|
assert_eq!(argument_is_quoted("''"), true);
|
|
|
|
|
|
|
|
assert_eq!(argument_is_quoted(r#"""#), false);
|
|
|
|
assert_eq!(argument_is_quoted(r#""a"#), false);
|
|
|
|
assert_eq!(argument_is_quoted(r#"a"#), false);
|
|
|
|
assert_eq!(argument_is_quoted(r#"a""#), false);
|
|
|
|
assert_eq!(argument_is_quoted(r#""""#), true);
|
|
|
|
|
2020-01-12 22:44:22 +01:00
|
|
|
assert_eq!(argument_is_quoted("'andrés"), false);
|
|
|
|
assert_eq!(argument_is_quoted("andrés'"), false);
|
|
|
|
assert_eq!(argument_is_quoted(r#""andrés"#), false);
|
|
|
|
assert_eq!(argument_is_quoted(r#"andrés""#), false);
|
|
|
|
assert_eq!(argument_is_quoted("'andrés'"), true);
|
|
|
|
assert_eq!(argument_is_quoted(r#""andrés""#), true);
|
|
|
|
}
|
|
|
|
|
2020-01-16 01:38:16 +01:00
|
|
|
#[test]
|
|
|
|
fn adds_quotes_to_argument_to_be_passed_in() {
|
|
|
|
assert_eq!(add_quotes("andrés"), "'andrés'");
|
|
|
|
assert_eq!(add_quotes("'andrés'"), "''andrés''");
|
|
|
|
}
|
|
|
|
|
2020-01-12 22:44:22 +01:00
|
|
|
#[test]
|
|
|
|
fn strips_quotes_from_argument_to_be_passed_in() {
|
2020-01-16 10:05:53 +01:00
|
|
|
assert_eq!(remove_quotes(""), None);
|
|
|
|
|
|
|
|
assert_eq!(remove_quotes("'"), None);
|
|
|
|
assert_eq!(remove_quotes("'a"), None);
|
|
|
|
assert_eq!(remove_quotes("a"), None);
|
|
|
|
assert_eq!(remove_quotes("a'"), None);
|
|
|
|
assert_eq!(remove_quotes("''"), Some(""));
|
|
|
|
|
|
|
|
assert_eq!(remove_quotes(r#"""#), None);
|
|
|
|
assert_eq!(remove_quotes(r#""a"#), None);
|
|
|
|
assert_eq!(remove_quotes(r#"a"#), None);
|
|
|
|
assert_eq!(remove_quotes(r#"a""#), None);
|
|
|
|
assert_eq!(remove_quotes(r#""""#), Some(""));
|
|
|
|
|
|
|
|
assert_eq!(remove_quotes("'andrés"), None);
|
|
|
|
assert_eq!(remove_quotes("andrés'"), None);
|
|
|
|
assert_eq!(remove_quotes(r#""andrés"#), None);
|
|
|
|
assert_eq!(remove_quotes(r#"andrés""#), None);
|
|
|
|
assert_eq!(remove_quotes("'andrés'"), Some("andrés"));
|
|
|
|
assert_eq!(remove_quotes(r#""andrés""#), Some("andrés"));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn expands_tilde_if_starts_with_tilde_character() {
|
|
|
|
assert_eq!(
|
|
|
|
expand_tilde("~", || Some(std::path::Path::new("the_path_to_nu_light"))),
|
|
|
|
"the_path_to_nu_light"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn does_not_expand_tilde_if_tilde_is_not_first_character() {
|
|
|
|
assert_eq!(
|
|
|
|
expand_tilde("1~1", || Some(std::path::Path::new("the_path_to_nu_light"))),
|
|
|
|
"1~1"
|
|
|
|
);
|
2020-01-12 22:44:22 +01:00
|
|
|
}
|
|
|
|
}
|