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::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{hir::CapturedBlock, hir::ExternalRedirection, Signature, SyntaxShape, Value};
hir::CapturedBlock, hir::ExternalRedirection, ReturnSuccess, Signature, SyntaxShape, Value,
};
pub struct Do; pub struct Do;
@ -48,7 +46,7 @@ impl WholeStreamCommand for Do {
Example { Example {
description: "Run the block and ignore errors", description: "Run the block and ignore errors",
example: r#"do -i { thisisnotarealcommand }"#, 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(); context.clear_errors();
Ok(futures::stream::iter(output).to_output_stream()) Ok(futures::stream::iter(output).to_output_stream())
} }
Err(_) => Ok(OutputStream::one(ReturnSuccess::value(Value::nothing()))), Err(_) => Ok(OutputStream::empty()),
} }
} else { } else {
result.map(|x| x.to_output_stream()) 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 name = args.call_info.name_tag.span;
let (SubCommandArgs { expression }, input) = args.process().await?; let (SubCommandArgs { expression }, input) = args.process().await?;
Ok(input if let Some(string) = expression {
.map(move |x| { match parse(&string, &string.tag) {
if let Some(Tagged { Ok(value) => Ok(OutputStream::one(ReturnSuccess::value(value))),
tag, Err(err) => Err(ShellError::labeled_error(
item: expression, "Math evaluation error",
}) = &expression err,
{ &string.tag.span,
UntaggedValue::string(expression).into_value(tag) )),
} else { }
x } else {
} Ok(input
}) .map(move |x| {
.map(move |input| { if let Some(Tagged {
if let Ok(string) = input.as_string() { tag,
match parse(&string, &input.tag) { item: expression,
Ok(value) => ReturnSuccess::value(value), }) = &expression
Err(err) => Err(ShellError::labeled_error( {
"Math evaluation error", UntaggedValue::string(expression).into_value(tag)
err, } else {
&input.tag.span, x
)),
} }
} else { })
Err(ShellError::labeled_error( .map(move |input| {
"Expected a string from pipeline", if let Ok(string) = input.as_string() {
"requires string input", match parse(&string, &input.tag) {
name, Ok(value) => ReturnSuccess::value(value),
)) Err(err) => Err(ShellError::labeled_error(
} "Math evaluation error",
}) err,
.to_output_stream()) &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> { 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 sum = reducer_for(Reduce::Summation);
let first = values.get(0).ok_or_else(|| { 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 { match first {

View File

@ -6,7 +6,7 @@ use crate::{CommandArgs, Example, OutputStream};
use futures::stream::once; use futures::stream::once;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::ParserScope; 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; use nu_source::Tagged;
pub struct Reduce; pub struct Reduce;
@ -93,22 +93,25 @@ async fn process_row(
} }
async fn reduce(raw_args: CommandArgs) -> Result<OutputStream, ShellError> { 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 context = Arc::new(EvaluationContext::from_raw(&raw_args));
let (reduce_args, mut input): (ReduceArgs, _) = raw_args.process().await?; let (reduce_args, mut input): (ReduceArgs, _) = raw_args.process().await?;
let block = Arc::new(reduce_args.block); let block = Arc::new(reduce_args.block);
let (ioffset, start) = match reduce_args.fold { let (ioffset, start) = if !input.is_empty() {
None => { match reduce_args.fold {
let first = input None => {
.next() let first = input.next().await.expect("non-empty stream");
.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"));
}
(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 { 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>> = let strings: Vec<Result<String, ShellError>> =
input.map(|value| value.as_string()).collect().await; input.map(|value| value.as_string()).collect().await;
let strings: Vec<String> = strings.into_iter().collect::<Result<_, _>>()?; let strings: Result<Vec<_>, _> = strings.into_iter().collect::<Result<_, _>>();
let output = strings.join(&separator);
Ok(OutputStream::one(ReturnSuccess::value( match strings {
UntaggedValue::string(output).into_value(tag), 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)] #[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] #[test]
#[allow(clippy::unreadable_literal)] #[allow(clippy::unreadable_literal)]
#[allow(clippy::float_cmp)] #[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 crate::prelude::*;
use futures::stream::{iter, once}; use futures::stream::iter;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, Type, UntaggedValue, Value}; use nu_protocol::{Primitive, Type, UntaggedValue, Value};
use nu_source::{PrettyDebug, Tag, Tagged, TaggedItem}; use nu_source::{PrettyDebug, Tag, Tagged, TaggedItem};
@ -14,7 +14,7 @@ pub struct InputStream {
impl InputStream { impl InputStream {
pub fn empty() -> InputStream { pub fn empty() -> InputStream {
InputStream { InputStream {
values: once(async { UntaggedValue::nothing().into_untagged_value() }).boxed(), values: futures::stream::empty().boxed(),
empty: true, empty: true,
} }
} }