From aadbcf5ce8bd4be0a5e84fc989a7a420204b20f3 Mon Sep 17 00:00:00 2001 From: Alexander James <32351579+DangerFunPants@users.noreply.github.com> Date: Sat, 23 May 2020 18:08:39 -0600 Subject: [PATCH] Issue 1787 (#1827) --- crates/nu-cli/src/commands/sum.rs | 41 +++++- crates/nu-cli/src/utils/data_processing.rs | 17 ++- crates/nu-cli/tests/commands/sum.rs | 50 +++++++ crates/nu-protocol/src/value.rs | 3 +- tests/fixtures/formats/sample-ps-output.json | 1 + tests/fixtures/formats/sample-sys-output.json | 125 ++++++++++++++++++ 6 files changed, 227 insertions(+), 10 deletions(-) create mode 100644 tests/fixtures/formats/sample-ps-output.json create mode 100644 tests/fixtures/formats/sample-sys-output.json diff --git a/crates/nu-cli/src/commands/sum.rs b/crates/nu-cli/src/commands/sum.rs index 6e64cbe58..d929b702f 100644 --- a/crates/nu-cli/src/commands/sum.rs +++ b/crates/nu-cli/src/commands/sum.rs @@ -2,9 +2,11 @@ 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, UntaggedValue, Value}; +use nu_protocol::{Dictionary, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value}; use num_traits::identities::Zero; +use indexmap::map::IndexMap; + pub struct Sum; impl WholeStreamCommand for Sum { @@ -54,13 +56,40 @@ impl WholeStreamCommand for Sum { fn sum(RunnableContext { mut input, .. }: RunnableContext) -> Result { let stream = async_stream! { - let mut values = input.drain_vec().await; - + let mut values: Vec = 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), + if values.iter().all(|v| if let UntaggedValue::Primitive(_) = v.value {true} else {false}) { + let total = action(Value::zero(), values)?; + yield ReturnSuccess::value(total) + } 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 { + let sum = action(Value::zero(), col_vals); + match sum { + Ok(value) => { + column_totals.insert(col_name, value); + }, + Err(err) => yield Err(err), + }; + } + yield ReturnSuccess::value( + UntaggedValue::Row(Dictionary {entries: column_totals}).into_untagged_value()) } }; diff --git a/crates/nu-cli/src/utils/data_processing.rs b/crates/nu-cli/src/utils/data_processing.rs index abe06f9df..2bda079b8 100644 --- a/crates/nu-cli/src/utils/data_processing.rs +++ b/crates/nu-cli/src/utils/data_processing.rs @@ -198,9 +198,20 @@ pub fn evaluate( } pub fn sum(data: Vec) -> Result { - Ok(data - .into_iter() - .fold(Value::zero(), |acc: Value, value| acc + value)) + let mut acc = Value::zero(); + for value in data { + match value.value { + UntaggedValue::Primitive(_) => acc = acc + value, + _ => { + return Err(ShellError::labeled_error( + "Attempted to compute the sum of a value that cannot be summed.", + "value appears here", + value.tag.span, + )) + } + } + } + Ok(acc) } fn formula( diff --git a/crates/nu-cli/tests/commands/sum.rs b/crates/nu-cli/tests/commands/sum.rs index 0513a57c5..8aefad85b 100644 --- a/crates/nu-cli/tests/commands/sum.rs +++ b/crates/nu-cli/tests/commands/sum.rs @@ -1,6 +1,7 @@ use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; use nu_test_support::playground::Playground; use nu_test_support::{nu, pipeline}; +use std::str::FromStr; #[test] fn all() { @@ -60,3 +61,52 @@ fn outputs_zero_with_no_input() { assert_eq!(actual.out, "0"); }) } + +#[test] +fn compute_sum_of_individual_row() -> Result<(), String> { + let answers_for_columns = [ + ("cpu", 88.257434), + ("mem", 3032375296.), + ("virtual", 102579965952.), + ]; + for (column_name, expected_value) in answers_for_columns.iter() { + let actual = nu!( + cwd: "tests/fixtures/formats/", + format!("open sample-ps-output.json | select {} | sum | get {}", column_name, column_name) + ); + let result = + f64::from_str(&actual.out).map_err(|_| String::from("Failed to parse float."))?; + assert_eq!(result, *expected_value); + } + Ok(()) +} + +#[test] +fn compute_sum_of_table() -> Result<(), String> { + let answers_for_columns = [ + ("cpu", 88.257434), + ("mem", 3032375296.), + ("virtual", 102579965952.), + ]; + for (column_name, expected_value) in answers_for_columns.iter() { + let actual = nu!( + cwd: "tests/fixtures/formats/", + format!("open sample-ps-output.json | select cpu mem virtual | sum | get {}", column_name) + ); + let result = + f64::from_str(&actual.out).map_err(|_| String::from("Failed to parse float."))?; + assert_eq!(result, *expected_value); + } + Ok(()) +} + +#[test] +fn sum_of_a_row_containing_a_table_is_an_error() { + let actual = nu!( + cwd: "tests/fixtures/formats/", + "open sample-sys-output.json | sum" + ); + assert!(actual + .err + .contains("Attempted to compute the sum of a value that cannot be summed.")); +} diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index c0403982d..48a8911d9 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -531,7 +531,8 @@ impl std::ops::Add for Value { UntaggedValue::from(left.add(right)).into_value(tag) } - (_, _) => unimplemented!("Internal error: can't add non-primitives."), + (_, _) => UntaggedValue::Error(ShellError::unimplemented("Can't add non-primitives.")) + .into_value(tag), } } } diff --git a/tests/fixtures/formats/sample-ps-output.json b/tests/fixtures/formats/sample-ps-output.json new file mode 100644 index 000000000..7ae34d66e --- /dev/null +++ b/tests/fixtures/formats/sample-ps-output.json @@ -0,0 +1 @@ +[{"pid":10390,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":132112384,"virtual":4989624320},{"pid":10461,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":126992384,"virtual":4995346432},{"pid":10530,"name":"kworker/6:1-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":10593,"name":"kworker/1:1-mm_percpu_wq","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":10650,"name":"chrome","status":"Sleeping","cpu":8.026974,"mem":262434816,"virtual":5217419264},{"pid":10803,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":48173056,"virtual":542531584},{"pid":11191,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":124092416,"virtual":4975763456},{"pid":11210,"name":"kworker/7:0-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":11254,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":113070080,"virtual":4971659264},{"pid":11279,"name":"kworker/u16:0-events_unbound","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":11476,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":88993792,"virtual":4937097216},{"pid":12755,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":163397632,"virtual":5034328064},{"pid":12772,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":113561600,"virtual":4985073664},{"pid":14351,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":111861760,"virtual":4962754560},{"pid":17818,"name":"udisksd","status":"Sleeping","cpu":0.0,"mem":14409728,"virtual":402935808},{"pid":17815,"name":".gvfs-udisks2-v","status":"Sleeping","cpu":0.0,"mem":16199680,"virtual":585306112},{"pid":17831,"name":".gvfs-mtp-volum","status":"Sleeping","cpu":0.0,"mem":6393856,"virtual":454680576},{"pid":17836,"name":".gvfs-gphoto2-v","status":"Sleeping","cpu":0.0,"mem":7110656,"virtual":456966144},{"pid":17841,"name":".gvfs-afc-volum","status":"Sleeping","cpu":0.0,"mem":8585216,"virtual":537448448},{"pid":17846,"name":".gvfsd-trash-wr","status":"Sleeping","cpu":0.0,"mem":12767232,"virtual":577998848},{"pid":17856,"name":".gvfsd-network-","status":"Sleeping","cpu":0.0,"mem":13295616,"virtual":654110720},{"pid":17862,"name":".gvfsd-dnssd-wr","status":"Sleeping","cpu":0.0,"mem":7639040,"virtual":533233664},{"pid":17869,"name":"dconf-service","status":"Sleeping","cpu":0.0,"mem":5365760,"virtual":158957568},{"pid":18153,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":183738368,"virtual":5128962048},{"pid":23033,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":166035456,"virtual":5074878464},{"pid":24101,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":101224448,"virtual":4956262400},{"pid":24832,"name":"kworker/7:2-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":24912,"name":"kworker/5:2-events_power_efficient","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":25228,"name":"kworker/4:3-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":25678,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":117522432,"virtual":4970983424},{"pid":25706,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":30760960,"virtual":528375808},{"pid":26080,"name":"kworker/1:0-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":26818,"name":"kworker/2:0-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":26827,"name":"kworker/6:2-mm_percpu_wq","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":26832,"name":"kworker/0:2-mm_percpu_wq","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":26843,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":116621312,"virtual":4982403072},{"pid":27163,"name":"kworker/3:2-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":27800,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":128200704,"virtual":4965363712},{"pid":27820,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":54960128,"virtual":4895596544},{"pid":27898,"name":"kworker/3:0-mm_percpu_wq","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":27977,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":141930496,"virtual":4982546432},{"pid":28035,"name":"kworker/u16:1-events_unbound","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":28104,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":126853120,"virtual":5003902976},{"pid":28158,"name":"nu","status":"Sleeping","cpu":0.0,"mem":27344896,"virtual":870764544},{"pid":28236,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":450560000,"virtual":5389582336},{"pid":29186,"name":"kworker/5:0-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":30140,"name":"kworker/u16:2-events_unbound","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":30142,"name":"nu_plugin_core_","status":"Zombie","cpu":0.0,"mem":0,"virtual":0},{"pid":30356,"name":"sh","status":"Sleeping","cpu":0.0,"mem":3743744,"virtual":224092160},{"pid":30360,"name":"nu_plugin_core_ps","status":"Sleeping","cpu":80.23046000000001,"mem":6422528,"virtual":633016320}] \ No newline at end of file diff --git a/tests/fixtures/formats/sample-sys-output.json b/tests/fixtures/formats/sample-sys-output.json new file mode 100644 index 000000000..122fd8594 --- /dev/null +++ b/tests/fixtures/formats/sample-sys-output.json @@ -0,0 +1,125 @@ +{ + "host": { + "name": "Linux", + "release": "5.4.33", + "version": "#1-NixOS SMP Fri Apr 17 08:50:26 UTC 2020", + "hostname": "nixos", + "arch": "x86_64", + "uptime": 105126, + "sessions": [ + "alexj" + ] + }, + "cpu": { + "cores": 8, + "current ghz": 2.4200000000000004, + "min ghz": 0.39999999999999997, + "max ghz": 3.4000000000000004 + }, + "disks": [ + { + "device": "/dev/disk/by-uuid/e9adff48-c37b-4631-b98b-eaec9b410ba3", + "type": "ext4", + "mount": "/", + "total": 483445473280, + "used": 121866776576, + "free": 336949624832 + }, + { + "device": "/dev/disk/by-uuid/e9adff48-c37b-4631-b98b-eaec9b410ba3", + "type": "ext4", + "mount": "/nix/store", + "total": 483445473280, + "used": 121866776576, + "free": 336949624832 + }, + { + "device": "/dev/sda3", + "type": "vfat", + "mount": "/boot", + "total": 534757376, + "used": 72650752, + "free": 462106624 + } + ], + "mem": { + "total": 16256524000, + "free": 3082268000, + "swap total": 18874344000, + "swap free": 18874344000 + }, + "temp": [ + { + "unit": "iwlwifi_1", + "temp": 42.0 + }, + { + "unit": "acpitz", + "temp": 53.00000000000001, + "critical": 103.0 + }, + { + "unit": "coretemp", + "label": "Core 1", + "temp": 52.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "coretemp", + "label": "Core 2", + "temp": 52.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "coretemp", + "label": "Package id 0", + "temp": 52.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "coretemp", + "label": "Core 3", + "temp": 51.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "coretemp", + "label": "Core 0", + "temp": 51.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "pch_skylake", + "temp": 48.00000000000001 + } + ], + "net": [ + { + "name": "wlp2s0", + "sent": 387642399, + "recv": 15324719784 + }, + { + "name": "lo", + "sent": 2667, + "recv": 2667 + }, + { + "name": "vboxnet0", + "sent": 0, + "recv": 0 + } + ], + "battery": [ + { + "vendor": "ASUSTeK", + "model": "ASUS Battery", + "cycles": 445 + } + ] +}