diff --git a/Cargo.lock b/Cargo.lock index 42bc10a140..befec72a92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2264,7 +2264,6 @@ dependencies = [ "nu_plugin_post", "nu_plugin_ps", "nu_plugin_str", - "nu_plugin_sum", "nu_plugin_sys", "nu_plugin_textview", "nu_plugin_tree", @@ -2642,17 +2641,6 @@ dependencies = [ "regex", ] -[[package]] -name = "nu_plugin_sum" -version = "0.11.0" -dependencies = [ - "nu-build", - "nu-errors", - "nu-plugin", - "nu-protocol", - "nu-source", -] - [[package]] name = "nu_plugin_sys" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index 6682187d8c..b7cfda075c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,6 @@ nu_plugin_match = { version = "0.11.0", path = "./crates/nu_plugin_match", optio nu_plugin_post = { version = "0.11.0", path = "./crates/nu_plugin_post", optional=true } nu_plugin_ps = { version = "0.11.0", path = "./crates/nu_plugin_ps", optional=true } nu_plugin_str = { version = "0.11.0", path = "./crates/nu_plugin_str", optional=true } -nu_plugin_sum = { version = "0.11.0", path = "./crates/nu_plugin_sum", optional=true } nu_plugin_sys = { version = "0.11.0", path = "./crates/nu_plugin_sys", optional=true } nu_plugin_textview = { version = "0.11.0", path = "./crates/nu_plugin_textview", optional=true } nu_plugin_tree = { version = "0.11.0", path = "./crates/nu_plugin_tree", optional=true } @@ -66,7 +65,7 @@ nu-build = { version = "0.11.0", path = "./crates/nu-build" } test-bins = [] default = ["sys", "ps", "textview", "inc", "str"] -stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "sum", "post", "fetch", "clipboard-cli"] +stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "post", "fetch", "clipboard-cli"] # Default textview = ["crossterm", "syntect", "onig_sys", "url", "nu_plugin_textview"] @@ -81,7 +80,6 @@ binaryview = ["nu_plugin_binaryview"] fetch = ["nu_plugin_fetch"] match = ["nu_plugin_match"] post = ["nu_plugin_post"] -sum = ["nu_plugin_sum"] trace = ["nu-parser/trace"] tree = ["nu_plugin_tree"] @@ -167,11 +165,6 @@ name = "nu_plugin_stable_post" path = "src/plugins/nu_plugin_stable_post.rs" required-features = ["post"] -[[bin]] -name = "nu_plugin_stable_sum" -path = "src/plugins/nu_plugin_stable_sum.rs" -required-features = ["sum"] - [[bin]] name = "nu_plugin_stable_tree" path = "src/plugins/nu_plugin_stable_tree.rs" diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index 9391cfd026..664c5419bc 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -313,6 +313,7 @@ pub fn create_default_context( whole_stream_command(Pivot), // Data processing whole_stream_command(Histogram), + whole_stream_command(Sum), // File format output whole_stream_command(ToBSON), whole_stream_command(ToCSV), diff --git a/crates/nu-cli/src/commands.rs b/crates/nu-cli/src/commands.rs index 403e0262d9..c9d363ec55 100644 --- a/crates/nu-cli/src/commands.rs +++ b/crates/nu-cli/src/commands.rs @@ -81,6 +81,7 @@ pub(crate) mod sort_by; pub(crate) mod split_by; pub(crate) mod split_column; pub(crate) mod split_row; +pub(crate) mod sum; #[allow(unused)] pub(crate) mod t_sort_by; pub(crate) mod table; @@ -186,6 +187,7 @@ pub(crate) use sort_by::SortBy; pub(crate) use split_by::SplitBy; pub(crate) use split_column::SplitColumn; pub(crate) use split_row::SplitRow; +pub(crate) use sum::Sum; #[allow(unused_imports)] pub(crate) use t_sort_by::TSortBy; pub(crate) use table::Table; diff --git a/crates/nu-cli/src/commands/sum.rs b/crates/nu-cli/src/commands/sum.rs new file mode 100644 index 0000000000..11ddb6e4b6 --- /dev/null +++ b/crates/nu-cli/src/commands/sum.rs @@ -0,0 +1,55 @@ +use crate::commands::WholeStreamCommand; +use crate::prelude::*; +use crate::utils::data_processing::{reducer_for, Reduce}; +use nu_errors::ShellError; +use nu_protocol::{ReturnSuccess, ReturnValue, Signature, Value}; +use num_traits::identities::Zero; + +pub struct Sum; + +impl WholeStreamCommand for Sum { + fn name(&self) -> &str { + "sum" + } + + fn signature(&self) -> Signature { + Signature::build("sum") + } + + fn usage(&self) -> &str { + "Sums the values." + } + + fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + sum(RunnableContext { + input: args.input, + commands: registry.clone(), + shell_manager: args.shell_manager, + host: args.host, + source: args.call_info.source, + ctrl_c: args.ctrl_c, + name: args.call_info.name_tag, + }) + } +} + +fn sum(RunnableContext { mut input, .. }: RunnableContext) -> Result { + let stream = async_stream! { + let mut values = input.drain_vec().await; + + let action = reducer_for(Reduce::Sum); + + match action(Value::zero(), values) { + Ok(total) => yield ReturnSuccess::value(total), + Err(err) => yield Err(err), + } + }; + + let stream: BoxStream<'static, ReturnValue> = stream.boxed(); + + Ok(stream.to_output_stream()) +} diff --git a/crates/nu-cli/src/utils/data_processing.rs b/crates/nu-cli/src/utils/data_processing.rs index e88a321a05..7fefeb7c7d 100644 --- a/crates/nu-cli/src/utils/data_processing.rs +++ b/crates/nu-cli/src/utils/data_processing.rs @@ -1,10 +1,11 @@ +use crate::data::value::compare_values; use crate::data::TaggedListBuilder; use chrono::{DateTime, NaiveDate, Utc}; use nu_errors::ShellError; +use nu_parser::CompareOperator; use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value}; use nu_source::{SpannedItem, Tag, Tagged, TaggedItem}; use nu_value_ext::{get_data_by_key, ValueExt}; -use num_bigint::BigInt; use num_traits::Zero; pub fn columns_sorted( @@ -196,44 +197,31 @@ pub fn evaluate( Ok(results) } -fn sum(data: Vec) -> Result { - let total = data +pub fn sum(data: Vec) -> Result { + Ok(data .into_iter() - .fold(Zero::zero(), |acc: BigInt, value| match value { - Value { - value: UntaggedValue::Primitive(Primitive::Int(n)), - .. - } => acc + n, - _ => acc, - }); - - Ok(UntaggedValue::int(total).into_untagged_value()) + .fold(Value::zero(), |acc: Value, value| acc + value)) } fn formula( - acc_begin: BigInt, - calculator: Box) -> Result + 'static>, -) -> Box) -> Result + 'static> { + acc_begin: Value, + calculator: Box) -> Result + Send + Sync + 'static>, +) -> Box) -> Result + Send + Sync + 'static> { Box::new(move |acc, datax| -> Result { let result = acc * acc_begin.clone(); - if let Ok(Value { - value: UntaggedValue::Primitive(Primitive::Int(computed)), - .. - }) = calculator(datax) - { - return Ok(UntaggedValue::int(result + computed).into_untagged_value()); + match calculator(datax) { + Ok(total) => Ok(result + total), + Err(reason) => Err(reason), } - - Ok(UntaggedValue::int(0).into_untagged_value()) }) } pub fn reducer_for( command: Reduce, -) -> Box) -> Result + 'static> { +) -> Box) -> Result + Send + Sync + 'static> { match command { - Reduce::Sum | Reduce::Default => Box::new(formula(Zero::zero(), Box::new(sum))), + Reduce::Sum | Reduce::Default => Box::new(formula(Value::zero(), Box::new(sum))), } } @@ -262,7 +250,7 @@ pub fn reduce( let datasets: Vec<_> = datasets .iter() .map(|subsets| { - let acc: BigInt = Zero::zero(); + let acc = Value::zero(); match subsets { Value { value: UntaggedValue::Table(data), @@ -318,37 +306,48 @@ pub fn map_max( value: UntaggedValue::Table(datasets), .. } => { - let datasets: Vec<_> = datasets + let datasets: Vec = datasets .iter() .map(|subsets| match subsets { Value { value: UntaggedValue::Table(data), .. - } => { - let data: BigInt = - data.iter().fold(Zero::zero(), |acc, value| match value { - Value { - value: UntaggedValue::Primitive(Primitive::Int(n)), - .. - } if *n > acc => n.clone(), - _ => acc, - }); - UntaggedValue::int(data).into_value(&tag) - } + } => data.iter().fold(Value::zero(), |acc, value| { + let left = &value.value; + let right = &acc.value; + + if let Ok(is_greater_than) = + compare_values(CompareOperator::GreaterThan, left, right) + { + if is_greater_than { + value.clone() + } else { + acc + } + } else { + acc + } + }), _ => UntaggedValue::int(0).into_value(&tag), }) .collect(); - let datasets: BigInt = datasets - .iter() - .fold(Zero::zero(), |max, value| match value { - Value { - value: UntaggedValue::Primitive(Primitive::Int(n)), - .. - } if *n > max => n.clone(), - _ => max, - }); - UntaggedValue::int(datasets).into_value(&tag) + datasets.into_iter().fold(Value::zero(), |max, value| { + let left = &value.value; + let right = &max.value; + + if let Ok(is_greater_than) = + compare_values(CompareOperator::GreaterThan, left, right) + { + if is_greater_than { + value + } else { + max + } + } else { + max + } + }) } _ => UntaggedValue::int(-1).into_value(&tag), }; @@ -573,7 +572,7 @@ mod tests { let action = reducer_for(Reduce::Sum); - assert_eq!(action(Zero::zero(), subject)?, int(3)); + assert_eq!(action(Value::zero(), subject)?, int(3)); Ok(()) } diff --git a/crates/nu-cli/tests/commands/mod.rs b/crates/nu-cli/tests/commands/mod.rs index 197c10af62..49826b1f37 100644 --- a/crates/nu-cli/tests/commands/mod.rs +++ b/crates/nu-cli/tests/commands/mod.rs @@ -29,6 +29,7 @@ mod save; mod sort_by; mod split_by; mod split_column; +mod sum; mod touch; mod uniq; mod where_; diff --git a/crates/nu-cli/tests/commands/sum.rs b/crates/nu-cli/tests/commands/sum.rs new file mode 100644 index 0000000000..b4972148bf --- /dev/null +++ b/crates/nu-cli/tests/commands/sum.rs @@ -0,0 +1,30 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn all() { + Playground::setup("sum_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "meals.csv", + r#" + description,calories + "1 large egg",90 + "1 cup white rice",250 + "1 tablespoon fish oil",108 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open meals.csv + | get calories + | sum + | echo $it + "# + )); + + assert_eq!(actual, "448"); + }) +} diff --git a/crates/nu-cli/tests/commands/wrap.rs b/crates/nu-cli/tests/commands/wrap.rs index eab43cad43..60e90183e3 100644 --- a/crates/nu-cli/tests/commands/wrap.rs +++ b/crates/nu-cli/tests/commands/wrap.rs @@ -4,7 +4,7 @@ use nu_test_support::{nu, pipeline}; #[test] fn wrap_rows_into_a_row() { - Playground::setup("embed_test_1", |dirs, sandbox| { + Playground::setup("wrap_test_1", |dirs, sandbox| { sandbox.with_files(vec![FileWithContentToBeTrimmed( "los_tres_caballeros.txt", r#" @@ -34,7 +34,7 @@ fn wrap_rows_into_a_row() { #[test] fn wrap_rows_into_a_table() { - Playground::setup("embed_test_2", |dirs, sandbox| { + Playground::setup("wrap_test_2", |dirs, sandbox| { sandbox.with_files(vec![FileWithContentToBeTrimmed( "los_tres_caballeros.txt", r#" diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index de3f0f62f3..d6d8007e40 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -379,6 +379,60 @@ impl From for UntaggedValue { } } +impl num_traits::Zero for Value { + fn zero() -> Self { + Value { + value: UntaggedValue::Primitive(Primitive::zero()), + tag: Tag::unknown(), + } + } + + fn is_zero(&self) -> bool { + match &self.value { + UntaggedValue::Primitive(primitive) => primitive.is_zero(), + UntaggedValue::Row(row) => row.entries.is_empty(), + UntaggedValue::Table(rows) => rows.is_empty(), + _ => false, + } + } +} + +impl std::ops::Mul for Value { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + let tag = self.tag.clone(); + + match (&*self, &*rhs) { + (UntaggedValue::Primitive(left), UntaggedValue::Primitive(right)) => { + let left = left.clone(); + let right = right.clone(); + + UntaggedValue::from(left.mul(right)).into_value(tag) + } + (_, _) => unimplemented!("Internal error: can't multiply non-primitives."), + } + } +} + +impl std::ops::Add for Value { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + let tag = self.tag.clone(); + + match (&*self, &*rhs) { + (UntaggedValue::Primitive(left), UntaggedValue::Primitive(right)) => { + let left = left.clone(); + let right = right.clone(); + + UntaggedValue::from(left.add(right)).into_value(tag) + } + (_, _) => unimplemented!("Internal error: can't add non-primitives."), + } + } +} + pub fn merge_descriptors(values: &[Value]) -> Vec { let mut ret: Vec = vec![]; let value_column = "".to_string(); diff --git a/crates/nu-protocol/src/value/primitive.rs b/crates/nu-protocol/src/value/primitive.rs index d3a3c41643..e3a7207610 100644 --- a/crates/nu-protocol/src/value/primitive.rs +++ b/crates/nu-protocol/src/value/primitive.rs @@ -8,6 +8,7 @@ use nu_errors::{ExpectedRange, ShellError}; use nu_source::{PrettyDebug, Span, SpannedItem}; use num_bigint::BigInt; use num_traits::cast::{FromPrimitive, ToPrimitive}; +use num_traits::identities::Zero; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -75,6 +76,82 @@ impl Primitive { } } +impl num_traits::Zero for Primitive { + fn zero() -> Self { + Primitive::Int(BigInt::zero()) + } + + fn is_zero(&self) -> bool { + match self { + Primitive::Int(int) => int.is_zero(), + Primitive::Decimal(decimal) => decimal.is_zero(), + Primitive::Bytes(size) => size.is_zero(), + Primitive::Nothing => true, + _ => false, + } + } +} + +impl std::ops::Add for Primitive { + type Output = Primitive; + + fn add(self, rhs: Self) -> Self { + match (self, rhs) { + (Primitive::Int(left), Primitive::Int(right)) => Primitive::Int(left + right), + (Primitive::Int(left), Primitive::Decimal(right)) => { + Primitive::Decimal(BigDecimal::from(left) + right) + } + (Primitive::Decimal(left), Primitive::Decimal(right)) => { + Primitive::Decimal(left + right) + } + (Primitive::Decimal(left), Primitive::Int(right)) => { + Primitive::Decimal(left + BigDecimal::from(right)) + } + (Primitive::Bytes(left), right) => match right { + Primitive::Bytes(right) => Primitive::Bytes(left + right), + Primitive::Int(right) => { + Primitive::Bytes(left + right.to_u64().unwrap_or_else(|| 0 as u64)) + } + Primitive::Decimal(right) => { + Primitive::Bytes(left + right.to_u64().unwrap_or_else(|| 0 as u64)) + } + _ => Primitive::Bytes(left), + }, + (left, Primitive::Bytes(right)) => match left { + Primitive::Bytes(left) => Primitive::Bytes(left + right), + Primitive::Int(left) => { + Primitive::Bytes(left.to_u64().unwrap_or_else(|| 0 as u64) + right) + } + Primitive::Decimal(left) => { + Primitive::Bytes(left.to_u64().unwrap_or_else(|| 0 as u64) + right) + } + _ => Primitive::Bytes(right), + }, + _ => Primitive::zero(), + } + } +} + +impl std::ops::Mul for Primitive { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + match (self, rhs) { + (Primitive::Int(left), Primitive::Int(right)) => Primitive::Int(left * right), + (Primitive::Int(left), Primitive::Decimal(right)) => { + Primitive::Decimal(BigDecimal::from(left) * right) + } + (Primitive::Decimal(left), Primitive::Decimal(right)) => { + Primitive::Decimal(left * right) + } + (Primitive::Decimal(left), Primitive::Int(right)) => { + Primitive::Decimal(left * BigDecimal::from(right)) + } + _ => unimplemented!("Internal error: can't multiply incompatible primitives."), + } + } +} + impl From for Primitive { /// Helper to convert from decimals to a Primitive value fn from(decimal: BigDecimal) -> Primitive { @@ -82,6 +159,13 @@ impl From for Primitive { } } +impl From for Primitive { + /// Helper to convert from integers to a Primitive value + fn from(int: BigInt) -> Primitive { + Primitive::Int(int) + } +} + impl From for Primitive { /// Helper to convert from 64-bit float to a Primitive value fn from(float: f64) -> Primitive { diff --git a/crates/nu_plugin_sum/Cargo.toml b/crates/nu_plugin_sum/Cargo.toml deleted file mode 100644 index 4fb9984e5b..0000000000 --- a/crates/nu_plugin_sum/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "nu_plugin_sum" -version = "0.11.0" -authors = ["Yehuda Katz ", "Jonathan Turner ", "Andrés N. Robalino "] -edition = "2018" -description = "A simple summation plugin for Nushell" -license = "MIT" - -[lib] -doctest = false - -[dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.11.0" } -nu-protocol = { path = "../nu-protocol", version = "0.11.0" } -nu-source = { path = "../nu-source", version = "0.11.0" } -nu-errors = { path = "../nu-errors", version = "0.11.0" } - -[build-dependencies] -nu-build = { version = "0.11.0", path = "../nu-build" } diff --git a/crates/nu_plugin_sum/build.rs b/crates/nu_plugin_sum/build.rs deleted file mode 100644 index b7511cfc6a..0000000000 --- a/crates/nu_plugin_sum/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() -> Result<(), Box> { - nu_build::build() -} diff --git a/crates/nu_plugin_sum/src/lib.rs b/crates/nu_plugin_sum/src/lib.rs deleted file mode 100644 index 64f3f91612..0000000000 --- a/crates/nu_plugin_sum/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod nu; -mod sum; - -pub use sum::Sum; diff --git a/crates/nu_plugin_sum/src/main.rs b/crates/nu_plugin_sum/src/main.rs deleted file mode 100644 index 8d9e3cfc68..0000000000 --- a/crates/nu_plugin_sum/src/main.rs +++ /dev/null @@ -1,6 +0,0 @@ -use nu_plugin::serve_plugin; -use nu_plugin_sum::Sum; - -fn main() { - serve_plugin(&mut Sum::new()); -} diff --git a/crates/nu_plugin_sum/src/nu/mod.rs b/crates/nu_plugin_sum/src/nu/mod.rs deleted file mode 100644 index f4384fee53..0000000000 --- a/crates/nu_plugin_sum/src/nu/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -use nu_errors::ShellError; -use nu_plugin::Plugin; -use nu_protocol::{CallInfo, ReturnSuccess, ReturnValue, Signature, Value}; - -use crate::Sum; - -impl Plugin for Sum { - fn config(&mut self) -> Result { - Ok(Signature::build("sum") - .desc("Sum a column of values.") - .filter()) - } - - fn begin_filter(&mut self, _: CallInfo) -> Result, ShellError> { - Ok(vec![]) - } - - fn filter(&mut self, input: Value) -> Result, ShellError> { - self.sum(input)?; - Ok(vec![]) - } - - fn end_filter(&mut self) -> Result, ShellError> { - match self.total { - None => Ok(vec![]), - Some(ref v) => Ok(vec![ReturnSuccess::value(v.clone())]), - } - } -} diff --git a/crates/nu_plugin_sum/src/sum.rs b/crates/nu_plugin_sum/src/sum.rs deleted file mode 100644 index dc7a7b277a..0000000000 --- a/crates/nu_plugin_sum/src/sum.rs +++ /dev/null @@ -1,66 +0,0 @@ -use nu_errors::ShellError; -use nu_protocol::{Primitive, UntaggedValue, Value}; - -#[derive(Default)] -pub struct Sum { - pub total: Option, -} - -impl Sum { - pub fn new() -> Sum { - Sum { total: None } - } - - pub fn sum(&mut self, value: Value) -> Result<(), ShellError> { - match &value.value { - UntaggedValue::Primitive(Primitive::Nothing) => Ok(()), - UntaggedValue::Primitive(Primitive::Int(i)) => { - match &self.total { - Some(Value { - value: UntaggedValue::Primitive(Primitive::Int(j)), - tag, - }) => { - //TODO: handle overflow - self.total = Some(UntaggedValue::int(i + j).into_value(tag)); - Ok(()) - } - None => { - self.total = Some(value.clone()); - Ok(()) - } - _ => Err(ShellError::labeled_error( - "Could not sum non-integer or unrelated types", - "source", - value.tag, - )), - } - } - UntaggedValue::Primitive(Primitive::Bytes(b)) => { - match &self.total { - Some(Value { - value: UntaggedValue::Primitive(Primitive::Bytes(j)), - tag, - }) => { - //TODO: handle overflow - self.total = Some(UntaggedValue::bytes(b + j).into_value(tag)); - Ok(()) - } - None => { - self.total = Some(value); - Ok(()) - } - _ => Err(ShellError::labeled_error( - "Could not sum non-integer or unrelated types", - "source", - value.tag, - )), - } - } - x => Err(ShellError::labeled_error( - format!("Unrecognized type in stream: {:?}", x), - "source", - value.tag, - )), - } - } -} diff --git a/src/plugins/nu_plugin_stable_sum.rs b/src/plugins/nu_plugin_stable_sum.rs deleted file mode 100644 index 8d9e3cfc68..0000000000 --- a/src/plugins/nu_plugin_stable_sum.rs +++ /dev/null @@ -1,6 +0,0 @@ -use nu_plugin::serve_plugin; -use nu_plugin_sum::Sum; - -fn main() { - serve_plugin(&mut Sum::new()); -}