Make sum plugin as internal command. (#1501)

This commit is contained in:
Andrés N. Robalino 2020-03-18 18:46:00 -05:00 committed by GitHub
parent 390deb4ff7
commit 21a543a901
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 278 additions and 204 deletions

12
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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),

View File

@ -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;

View File

@ -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<OutputStream, ShellError> {
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<OutputStream, ShellError> {
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())
}

View File

@ -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<Value>) -> Result<Value, ShellError> {
let total = data
pub fn sum(data: Vec<Value>) -> Result<Value, ShellError> {
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<dyn Fn(Vec<Value>) -> Result<Value, ShellError> + 'static>,
) -> Box<dyn Fn(BigInt, Vec<Value>) -> Result<Value, ShellError> + 'static> {
acc_begin: Value,
calculator: Box<dyn Fn(Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static>,
) -> Box<dyn Fn(Value, Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static> {
Box::new(move |acc, datax| -> Result<Value, ShellError> {
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<dyn Fn(BigInt, Vec<Value>) -> Result<Value, ShellError> + 'static> {
) -> Box<dyn Fn(Value, Vec<Value>) -> Result<Value, ShellError> + 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<Value> = 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(())
}

View File

@ -29,6 +29,7 @@ mod save;
mod sort_by;
mod split_by;
mod split_column;
mod sum;
mod touch;
mod uniq;
mod where_;

View File

@ -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");
})
}

View File

@ -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#"

View File

@ -379,6 +379,60 @@ impl From<ShellError> 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<String> {
let mut ret: Vec<String> = vec![];
let value_column = "<value>".to_string();

View File

@ -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<BigDecimal> for Primitive {
/// Helper to convert from decimals to a Primitive value
fn from(decimal: BigDecimal) -> Primitive {
@ -82,6 +159,13 @@ impl From<BigDecimal> for Primitive {
}
}
impl From<BigInt> for Primitive {
/// Helper to convert from integers to a Primitive value
fn from(int: BigInt) -> Primitive {
Primitive::Int(int)
}
}
impl From<f64> for Primitive {
/// Helper to convert from 64-bit float to a Primitive value
fn from(float: f64) -> Primitive {

View File

@ -1,19 +0,0 @@
[package]
name = "nu_plugin_sum"
version = "0.11.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
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" }

View File

@ -1,3 +0,0 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
nu_build::build()
}

View File

@ -1,4 +0,0 @@
mod nu;
mod sum;
pub use sum::Sum;

View File

@ -1,6 +0,0 @@
use nu_plugin::serve_plugin;
use nu_plugin_sum::Sum;
fn main() {
serve_plugin(&mut Sum::new());
}

View File

@ -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<Signature, ShellError> {
Ok(Signature::build("sum")
.desc("Sum a column of values.")
.filter())
}
fn begin_filter(&mut self, _: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
Ok(vec![])
}
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
self.sum(input)?;
Ok(vec![])
}
fn end_filter(&mut self) -> Result<Vec<ReturnValue>, ShellError> {
match self.total {
None => Ok(vec![]),
Some(ref v) => Ok(vec![ReturnSuccess::value(v.clone())]),
}
}
}

View File

@ -1,66 +0,0 @@
use nu_errors::ShellError;
use nu_protocol::{Primitive, UntaggedValue, Value};
#[derive(Default)]
pub struct Sum {
pub total: Option<Value>,
}
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,
)),
}
}
}

View File

@ -1,6 +0,0 @@
use nu_plugin::serve_plugin;
use nu_plugin_sum::Sum;
fn main() {
serve_plugin(&mut Sum::new());
}