Tighten how input streams handle nothing, and related (#2847)

This commit is contained in:
Jonathan Turner 2021-01-03 14:22:44 +13:00 committed by GitHub
parent a5f7600f6f
commit 77f915befe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 99 additions and 63 deletions

View File

@ -2,9 +2,7 @@ use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
hir::CapturedBlock, hir::ExternalRedirection, ReturnSuccess, Signature, SyntaxShape, Value,
};
use nu_protocol::{hir::CapturedBlock, hir::ExternalRedirection, Signature, SyntaxShape, Value};
pub struct Do;
@ -48,7 +46,7 @@ impl WholeStreamCommand for Do {
Example {
description: "Run the block and ignore errors",
example: r#"do -i { thisisnotarealcommand }"#,
result: Some(vec![Value::nothing()]),
result: Some(vec![]),
},
]
}
@ -98,7 +96,7 @@ async fn do_(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
context.clear_errors();
Ok(futures::stream::iter(output).to_output_stream())
}
Err(_) => Ok(OutputStream::one(ReturnSuccess::value(Value::nothing()))),
Err(_) => Ok(OutputStream::empty()),
}
} else {
result.map(|x| x.to_output_stream())

View File

@ -50,37 +50,48 @@ pub async fn eval(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.span;
let (SubCommandArgs { expression }, input) = args.process().await?;
Ok(input
.map(move |x| {
if let Some(Tagged {
tag,
item: expression,
}) = &expression
{
UntaggedValue::string(expression).into_value(tag)
} else {
x
}
})
.map(move |input| {
if let Ok(string) = input.as_string() {
match parse(&string, &input.tag) {
Ok(value) => ReturnSuccess::value(value),
Err(err) => Err(ShellError::labeled_error(
"Math evaluation error",
err,
&input.tag.span,
)),
if let Some(string) = expression {
match parse(&string, &string.tag) {
Ok(value) => Ok(OutputStream::one(ReturnSuccess::value(value))),
Err(err) => Err(ShellError::labeled_error(
"Math evaluation error",
err,
&string.tag.span,
)),
}
} else {
Ok(input
.map(move |x| {
if let Some(Tagged {
tag,
item: expression,
}) = &expression
{
UntaggedValue::string(expression).into_value(tag)
} else {
x
}
} else {
Err(ShellError::labeled_error(
"Expected a string from pipeline",
"requires string input",
name,
))
}
})
.to_output_stream())
})
.map(move |input| {
if let Ok(string) = input.as_string() {
match parse(&string, &input.tag) {
Ok(value) => ReturnSuccess::value(value),
Err(err) => Err(ShellError::labeled_error(
"Math evaluation error",
err,
&input.tag.span,
)),
}
} else {
Err(ShellError::labeled_error(
"Expected a string from pipeline",
"requires string input",
name,
))
}
})
.to_output_stream())
}
}
pub fn parse<T: Into<Tag>>(math_expression: &str, tag: T) -> Result<Value, String> {

View File

@ -73,7 +73,11 @@ pub fn summation(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let sum = reducer_for(Reduce::Summation);
let first = values.get(0).ok_or_else(|| {
ShellError::unexpected("Cannot perform aggregate math operation on empty data")
ShellError::labeled_error(
"Cannot perform aggregate math operation on empty data",
"expected input",
name.span,
)
})?;
match first {

View File

@ -6,7 +6,7 @@ use crate::{CommandArgs, Example, OutputStream};
use futures::stream::once;
use nu_errors::ShellError;
use nu_parser::ParserScope;
use nu_protocol::{hir::CapturedBlock, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{hir::CapturedBlock, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct Reduce;
@ -93,22 +93,25 @@ async fn process_row(
}
async fn reduce(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
let span = raw_args.call_info.name_tag.span;
let context = Arc::new(EvaluationContext::from_raw(&raw_args));
let (reduce_args, mut input): (ReduceArgs, _) = raw_args.process().await?;
let block = Arc::new(reduce_args.block);
let (ioffset, start) = match reduce_args.fold {
None => {
let first = input
.next()
.await
.expect("empty stream expected to contain Primitive::Nothing");
if let UntaggedValue::Primitive(Primitive::Nothing) = first.value {
return Err(ShellError::missing_value(None, "empty input"));
}
let (ioffset, start) = if !input.is_empty() {
match reduce_args.fold {
None => {
let first = input.next().await.expect("non-empty stream");
(1, first)
(1, first)
}
Some(acc) => (0, acc),
}
Some(acc) => (0, acc),
} else {
return Err(ShellError::labeled_error(
"Expected input",
"needs input",
span,
));
};
if reduce_args.numbered.item {

View File

@ -49,12 +49,39 @@ pub async fn collect(args: CommandArgs) -> Result<OutputStream, ShellError> {
let strings: Vec<Result<String, ShellError>> =
input.map(|value| value.as_string()).collect().await;
let strings: Vec<String> = strings.into_iter().collect::<Result<_, _>>()?;
let output = strings.join(&separator);
let strings: Result<Vec<_>, _> = strings.into_iter().collect::<Result<_, _>>();
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(tag),
)))
match strings {
Ok(strings) => {
let output = strings.join(&separator);
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(tag),
)))
}
Err(err) => match err.error {
nu_errors::ProximateShellError::TypeError { actual, .. } => {
if let Some(item) = actual.item {
Err(ShellError::labeled_error_with_secondary(
"could not convert to string",
format!("tried to convert '{}' in input to a string", item),
tag.span,
format!("'{}' value originated here", item),
actual.span,
))
} else {
Err(ShellError::labeled_error_with_secondary(
"could not convert to string",
"failed to convert input to strings",
tag.span,
"non-string found here",
actual.span,
))
}
}
_ => Err(err),
},
}
}
#[cfg(test)]

View File

@ -33,13 +33,6 @@ fn all() {
})
}
#[test]
fn outputs_zero_with_no_input() {
let actual = nu!(cwd: ".", "math sum");
assert_eq!(actual.out, "0");
}
#[test]
#[allow(clippy::unreadable_literal)]
#[allow(clippy::float_cmp)]

View File

@ -103,5 +103,5 @@ fn error_reduce_empty() {
)
);
assert!(actual.err.contains("empty input"));
assert!(actual.err.contains("needs input"));
}

View File

@ -1,5 +1,5 @@
use crate::prelude::*;
use futures::stream::{iter, once};
use futures::stream::iter;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Type, UntaggedValue, Value};
use nu_source::{PrettyDebug, Tag, Tagged, TaggedItem};
@ -14,7 +14,7 @@ pub struct InputStream {
impl InputStream {
pub fn empty() -> InputStream {
InputStream {
values: once(async { UntaggedValue::nothing().into_untagged_value() }).boxed(),
values: futures::stream::empty().boxed(),
empty: true,
}
}