diff --git a/Cargo.lock b/Cargo.lock index 51c744a846..223338906a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2166,7 +2166,6 @@ dependencies = [ "nu-source", "nu-test-support", "nu-value-ext", - "nu_plugin_average", "nu_plugin_binaryview", "nu_plugin_fetch", "nu_plugin_inc", @@ -2414,17 +2413,6 @@ dependencies = [ "num-traits 0.2.11", ] -[[package]] -name = "nu_plugin_average" -version = "0.14.1" -dependencies = [ - "nu-build", - "nu-errors", - "nu-plugin", - "nu-protocol", - "nu-source", -] - [[package]] name = "nu_plugin_binaryview" version = "0.14.1" diff --git a/Cargo.toml b/Cargo.toml index 671341589c..3c78745c2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ nu-protocol = { version = "0.14.1", path = "./crates/nu-protocol" } nu-errors = { version = "0.14.1", path = "./crates/nu-errors" } nu-parser = { version = "0.14.1", path = "./crates/nu-parser" } nu-value-ext = { version = "0.14.1", path = "./crates/nu-value-ext" } -nu_plugin_average = { version = "0.14.1", path = "./crates/nu_plugin_average", optional=true } nu_plugin_binaryview = { version = "0.14.1", path = "./crates/nu_plugin_binaryview", optional=true } nu_plugin_fetch = { version = "0.14.1", path = "./crates/nu_plugin_fetch", optional=true } nu_plugin_inc = { version = "0.14.1", path = "./crates/nu_plugin_inc", optional=true } @@ -60,7 +59,7 @@ nu-build = { version = "0.14.1", path = "./crates/nu-build" } [features] default = ["sys", "ps", "textview", "inc"] -stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "parse", "post", "fetch", "clipboard-cli", "trash-support", "start"] +stable = ["default", "starship-prompt", "binaryview", "match", "tree", "parse", "post", "fetch", "clipboard-cli", "trash-support", "start"] # Default textview = ["crossterm", "syntect", "url", "nu_plugin_textview"] @@ -69,7 +68,6 @@ ps = ["nu_plugin_ps"] inc = ["semver", "nu_plugin_inc"] # Stable -average = ["nu_plugin_average"] binaryview = ["nu_plugin_binaryview"] fetch = ["nu_plugin_fetch"] match = ["nu_plugin_match"] @@ -107,11 +105,6 @@ path = "src/plugins/nu_plugin_core_sys.rs" required-features = ["sys"] # Stable plugins -[[bin]] -name = "nu_plugin_stable_average" -path = "src/plugins/nu_plugin_stable_average.rs" -required-features = ["average"] - [[bin]] name = "nu_plugin_stable_fetch" path = "src/plugins/nu_plugin_stable_fetch.rs" diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index f93a248db1..ccc109fbba 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -344,6 +344,7 @@ pub fn create_default_context( whole_stream_command(Headers), // Data processing whole_stream_command(Histogram), + whole_stream_command(Average), whole_stream_command(Sum), // File format output whole_stream_command(To), diff --git a/crates/nu-cli/src/commands.rs b/crates/nu-cli/src/commands.rs index 0279d40bf0..71bdea9e6f 100644 --- a/crates/nu-cli/src/commands.rs +++ b/crates/nu-cli/src/commands.rs @@ -8,6 +8,7 @@ pub(crate) mod alias; pub(crate) mod append; pub(crate) mod args; pub(crate) mod autoview; +pub(crate) mod average; pub(crate) mod build_string; pub(crate) mod cal; pub(crate) mod calc; @@ -133,6 +134,7 @@ pub(crate) use command::{ pub(crate) use alias::Alias; pub(crate) use append::Append; +pub(crate) use average::Average; pub(crate) use build_string::BuildString; pub(crate) use cal::Cal; pub(crate) use calc::Calc; diff --git a/crates/nu-cli/src/commands/average.rs b/crates/nu-cli/src/commands/average.rs new file mode 100644 index 0000000000..f3cbde194c --- /dev/null +++ b/crates/nu-cli/src/commands/average.rs @@ -0,0 +1,172 @@ +use crate::commands::WholeStreamCommand; +use crate::prelude::*; +use crate::utils::data_processing::{reducer_for, Reduce}; +use bigdecimal::FromPrimitive; +use nu_errors::ShellError; +use nu_protocol::hir::{convert_number_to_u64, Number, Operator}; +use nu_protocol::{ + Dictionary, Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value, +}; +use num_traits::identities::Zero; + +use indexmap::map::IndexMap; + +pub struct Average; + +#[async_trait] +impl WholeStreamCommand for Average { + fn name(&self) -> &str { + "average" + } + + fn signature(&self) -> Signature { + Signature::build("average") + } + + fn usage(&self) -> &str { + "Average the values." + } + + async fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + average(RunnableContext { + input: args.input, + registry: registry.clone(), + shell_manager: args.shell_manager, + host: args.host, + ctrl_c: args.ctrl_c, + name: args.call_info.name_tag, + raw_input: args.raw_input, + }) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Average a list of numbers", + example: "echo [100 0 100 0] | average", + result: Some(vec![UntaggedValue::decimal(50).into()]), + }] + } +} + +fn average( + RunnableContext { + mut input, name, .. + }: RunnableContext, +) -> Result { + let stream = async_stream! { + let mut values: Vec = input.drain_vec().await; + let action = reducer_for(Reduce::Sum); + + if values.iter().all(|v| if let UntaggedValue::Primitive(_) = v.value {true} else {false}) { + match avg(&values, name) { + Ok(result) => yield ReturnSuccess::value(result), + Err(err) => yield Err(err), + } + } else { + let mut column_values = IndexMap::new(); + for value in values { + match value.value { + UntaggedValue::Row(row_dict) => { + for (key, value) in row_dict.entries.iter() { + column_values + .entry(key.clone()) + .and_modify(|v: &mut Vec| v.push(value.clone())) + .or_insert(vec![value.clone()]); + } + }, + table => {}, + }; + } + + let mut column_totals = IndexMap::new(); + for (col_name, col_vals) in column_values { + match avg(&col_vals, &name) { + Ok(result) => { + column_totals.insert(col_name, result); + } + Err(err) => yield Err(err), + } + } + yield ReturnSuccess::value( + UntaggedValue::Row(Dictionary {entries: column_totals}).into_untagged_value()) + } + }; + + let stream: BoxStream<'static, ReturnValue> = stream.boxed(); + + Ok(stream.to_output_stream()) +} + +fn avg(values: &[Value], name: impl Into) -> Result { + let name = name.into(); + + let sum = reducer_for(Reduce::Sum); + + let number = BigDecimal::from_usize(values.len()).expect("expected a usize-sized bigdecimal"); + + let total_rows = UntaggedValue::decimal(number); + let total = sum(Value::zero(), values.to_vec())?; + + match total { + Value { + value: UntaggedValue::Primitive(Primitive::Bytes(num)), + .. + } => { + let left = UntaggedValue::from(Primitive::Int(num.into())); + let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows); + + match result { + Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => { + let number = Number::Decimal(result); + let number = convert_number_to_u64(&number); + Ok(UntaggedValue::bytes(number).into_value(name)) + } + Ok(_) => Err(ShellError::labeled_error( + "could not calculate average of non-integer or unrelated types", + "source", + name, + )), + Err((left_type, right_type)) => Err(ShellError::coerce_error( + left_type.spanned(name.span), + right_type.spanned(name.span), + )), + } + } + Value { + value: UntaggedValue::Primitive(other), + .. + } => { + let left = UntaggedValue::from(other); + let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows); + + match result { + Ok(value) => Ok(value.into_value(name)), + Err((left_type, right_type)) => Err(ShellError::coerce_error( + left_type.spanned(name.span), + right_type.spanned(name.span), + )), + } + } + _ => Err(ShellError::labeled_error( + "could not calculate average of non-integer or unrelated types", + "source", + name, + )), + } +} + +#[cfg(test)] +mod tests { + use super::Average; + + #[test] + fn examples_work_as_expected() { + use crate::examples::test as test_examples; + + test_examples(Average {}) + } +} diff --git a/tests/plugins/decommission_average.rs b/crates/nu-cli/tests/commands/average.rs similarity index 100% rename from tests/plugins/decommission_average.rs rename to crates/nu-cli/tests/commands/average.rs diff --git a/crates/nu-cli/tests/commands/mod.rs b/crates/nu-cli/tests/commands/mod.rs index d31304f188..e131e19b21 100644 --- a/crates/nu-cli/tests/commands/mod.rs +++ b/crates/nu-cli/tests/commands/mod.rs @@ -1,5 +1,6 @@ mod alias; mod append; +mod average; mod cal; mod calc; mod cd; diff --git a/crates/nu-protocol/src/hir.rs b/crates/nu-protocol/src/hir.rs index 62d04cda26..f831f23d96 100644 --- a/crates/nu-protocol/src/hir.rs +++ b/crates/nu-protocol/src/hir.rs @@ -435,7 +435,7 @@ impl PrettyDebug for Unit { } } -fn convert_number_to_u64(number: &Number) -> u64 { +pub fn convert_number_to_u64(number: &Number) -> u64 { match number { Number::Int(big_int) => { if let Some(x) = big_int.to_u64() { diff --git a/crates/nu_plugin_average/Cargo.toml b/crates/nu_plugin_average/Cargo.toml deleted file mode 100644 index 30429cec61..0000000000 --- a/crates/nu_plugin_average/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "nu_plugin_average" -version = "0.14.1" -authors = ["The Nu Project Contributors"] -edition = "2018" -description = "An average value plugin for Nushell" -license = "MIT" - -[lib] -doctest = false - -[dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.14.1" } -nu-protocol = { path = "../nu-protocol", version = "0.14.1" } -nu-source = { path = "../nu-source", version = "0.14.1" } -nu-errors = { path = "../nu-errors", version = "0.14.1" } - -[build-dependencies] -nu-build = { version = "0.14.1", path = "../nu-build" } diff --git a/crates/nu_plugin_average/build.rs b/crates/nu_plugin_average/build.rs deleted file mode 100644 index b7511cfc6a..0000000000 --- a/crates/nu_plugin_average/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() -> Result<(), Box> { - nu_build::build() -} diff --git a/crates/nu_plugin_average/src/average.rs b/crates/nu_plugin_average/src/average.rs deleted file mode 100644 index b7092e1d3e..0000000000 --- a/crates/nu_plugin_average/src/average.rs +++ /dev/null @@ -1,67 +0,0 @@ -use nu_errors::ShellError; -use nu_protocol::{Primitive, UntaggedValue, Value}; - -#[derive(Debug, Default)] -pub struct Average { - pub total: Option, - pub count: u64, -} - -impl Average { - pub fn new() -> Average { - Average { - total: None, - count: 0, - } - } - - pub fn average(&mut self, value: Value) -> Result<(), ShellError> { - match &value.value { - UntaggedValue::Primitive(Primitive::Int(i)) => match &self.total { - Some(Value { - value: UntaggedValue::Primitive(Primitive::Int(j)), - tag, - }) => { - self.total = Some(UntaggedValue::int(i + j).into_value(tag)); - self.count += 1; - Ok(()) - } - None => { - self.total = Some(value.clone()); - self.count += 1; - Ok(()) - } - _ => Err(ShellError::labeled_error( - "Could calculate average of 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, - }) => { - self.total = Some(UntaggedValue::bytes(b + j).into_value(tag)); - self.count += 1; - Ok(()) - } - None => { - self.total = Some(value); - self.count += 1; - Ok(()) - } - _ => Err(ShellError::labeled_error( - "Could calculate average of 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/crates/nu_plugin_average/src/lib.rs b/crates/nu_plugin_average/src/lib.rs deleted file mode 100644 index 187b241706..0000000000 --- a/crates/nu_plugin_average/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod average; -mod nu; - -pub use average::Average; diff --git a/crates/nu_plugin_average/src/main.rs b/crates/nu_plugin_average/src/main.rs deleted file mode 100644 index a48f82c9f5..0000000000 --- a/crates/nu_plugin_average/src/main.rs +++ /dev/null @@ -1,6 +0,0 @@ -use nu_plugin::serve_plugin; -use nu_plugin_average::Average; - -fn main() { - serve_plugin(&mut Average::new()) -} diff --git a/crates/nu_plugin_average/src/nu/mod.rs b/crates/nu_plugin_average/src/nu/mod.rs deleted file mode 100644 index 7d256a81d4..0000000000 --- a/crates/nu_plugin_average/src/nu/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::Average; -use nu_errors::{CoerceInto, ShellError}; -use nu_plugin::Plugin; -use nu_protocol::{ - CallInfo, Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value, -}; -use nu_source::TaggedItem; - -impl Plugin for Average { - fn config(&mut self) -> Result { - Ok(Signature::build("average") - .desc("Compute the average of a column of numerical values.") - .filter()) - } - - fn begin_filter(&mut self, _: CallInfo) -> Result, ShellError> { - Ok(vec![]) - } - - fn filter(&mut self, input: Value) -> Result, ShellError> { - self.average(input)?; - Ok(vec![]) - } - - fn end_filter(&mut self) -> Result, ShellError> { - match self.total { - None => Ok(vec![]), - Some(ref inner) => match &inner.value { - UntaggedValue::Primitive(Primitive::Int(i)) => { - let total: u64 = i - .tagged(inner.tag.clone()) - .coerce_into("converting for average")?; - let avg = total as f64 / self.count as f64; - let primitive_value: UntaggedValue = Primitive::from(avg).into(); - let value = primitive_value.into_value(inner.tag.clone()); - Ok(vec![ReturnSuccess::value(value)]) - } - UntaggedValue::Primitive(Primitive::Bytes(bytes)) => { - let avg = *bytes as f64 / self.count as f64; - let primitive_value: UntaggedValue = UntaggedValue::bytes(avg as u64); - let tagged_value = primitive_value.into_value(inner.tag.clone()); - Ok(vec![ReturnSuccess::value(tagged_value)]) - } - _ => Ok(vec![]), - }, - } - } -} diff --git a/src/plugins/nu_plugin_stable_average.rs b/src/plugins/nu_plugin_stable_average.rs deleted file mode 100644 index 9ac6824f28..0000000000 --- a/src/plugins/nu_plugin_stable_average.rs +++ /dev/null @@ -1,6 +0,0 @@ -use nu_plugin::serve_plugin; -use nu_plugin_average::Average; - -fn main() { - serve_plugin(&mut Average::new()); -} diff --git a/tests/plugins/mod.rs b/tests/plugins/mod.rs index 7add8f932b..e3d59b2205 100644 --- a/tests/plugins/mod.rs +++ b/tests/plugins/mod.rs @@ -1,2 +1 @@ mod core_inc; -mod decommission_average;