From fc8ee8e4b9a4e67fb8ff082d3531447ffb09e747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Fri, 15 May 2020 04:16:09 -0500 Subject: [PATCH] Extracted grouping by date to it's own subcommand. (#1792) --- crates/nu-cli/src/cli.rs | 1 + crates/nu-cli/src/commands.rs | 2 + crates/nu-cli/src/commands/group_by.rs | 72 +++----------- crates/nu-cli/src/commands/group_by_date.rs | 103 ++++++++++++++++++++ crates/nu-cli/src/utils/data/group.rs | 12 ++- crates/nu-value-ext/src/lib.rs | 1 + 6 files changed, 129 insertions(+), 62 deletions(-) create mode 100644 crates/nu-cli/src/commands/group_by_date.rs diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index 65abe93a70..530bfa179c 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -298,6 +298,7 @@ pub fn create_default_context( whole_stream_command(Prepend), whole_stream_command(SortBy), whole_stream_command(GroupBy), + whole_stream_command(GroupByDate), whole_stream_command(First), whole_stream_command(Last), whole_stream_command(Nth), diff --git a/crates/nu-cli/src/commands.rs b/crates/nu-cli/src/commands.rs index 8e1b4fec27..0b7ef21f7b 100644 --- a/crates/nu-cli/src/commands.rs +++ b/crates/nu-cli/src/commands.rs @@ -50,6 +50,7 @@ pub(crate) mod from_xml; pub(crate) mod from_yaml; pub(crate) mod get; pub(crate) mod group_by; +pub(crate) mod group_by_date; pub(crate) mod headers; pub(crate) mod help; pub(crate) mod histogram; @@ -178,6 +179,7 @@ pub(crate) use from_yaml::FromYAML; pub(crate) use from_yaml::FromYML; pub(crate) use get::Get; pub(crate) use group_by::GroupBy; +pub(crate) use group_by_date::GroupByDate; pub(crate) use headers::Headers; pub(crate) use help::Help; pub(crate) use histogram::Histogram; diff --git a/crates/nu-cli/src/commands/group_by.rs b/crates/nu-cli/src/commands/group_by.rs index 2ce86f392c..235365c062 100644 --- a/crates/nu-cli/src/commands/group_by.rs +++ b/crates/nu-cli/src/commands/group_by.rs @@ -8,9 +8,7 @@ pub struct GroupBy; #[derive(Deserialize)] pub struct GroupByArgs { - column_name: Tagged, - date: Tagged, - format: Option>, + column_name: Option>, } impl WholeStreamCommand for GroupBy { @@ -19,19 +17,11 @@ impl WholeStreamCommand for GroupBy { } fn signature(&self) -> Signature { - Signature::build("group-by") - .required( - "column_name", - SyntaxShape::String, - "the name of the column to group by", - ) - .named( - "format", - SyntaxShape::String, - "Specify date and time formatting", - Some('f'), - ) - .switch("date", "by date", Some('d')) + Signature::build("group-by").optional( + "column_name", + SyntaxShape::String, + "the name of the column to group by", + ) } fn usage(&self) -> &str { @@ -54,17 +44,8 @@ impl WholeStreamCommand for GroupBy { } } -enum Grouper { - Default, - ByDate(Option), -} - pub fn group_by( - GroupByArgs { - column_name, - date, - format, - }: GroupByArgs, + GroupByArgs { column_name }: GroupByArgs, RunnableContext { input, name, .. }: RunnableContext, ) -> Result { let stream = async_stream! { @@ -74,42 +55,15 @@ pub fn group_by( yield Err(ShellError::labeled_error( "Expected table from pipeline", "requires a table input", - column_name.span() + name )) } else { - let grouper = if let Tagged { item: true, tag } = date { - if let Some(Tagged { item: fmt, tag }) = format { - Grouper::ByDate(Some(fmt)) - } else { - Grouper::ByDate(None) - } - } else { - Grouper::Default - }; - - match grouper { - Grouper::Default => { - match crate::utils::data::group(column_name, &values, None, &name) { - Ok(grouped) => yield ReturnSuccess::value(grouped), - Err(err) => yield Err(err), - } - } - Grouper::ByDate(None) => { - match crate::utils::data::group(column_name, &values, Some(Box::new(|row: &Value| row.format("%Y-%b-%d"))), &name) { - Ok(grouped) => yield ReturnSuccess::value(grouped), - Err(err) => yield Err(err), - } - } - Grouper::ByDate(Some(fmt)) => { - match crate::utils::data::group(column_name, &values, Some(Box::new(move |row: &Value| { - row.format(&fmt) - })), &name) { - Ok(grouped) => yield ReturnSuccess::value(grouped), - Err(err) => yield Err(err), - } - } + match crate::utils::data::group(column_name, &values, None, &name) { + Ok(grouped) => yield ReturnSuccess::value(grouped), + Err(err) => yield Err(err), } + } }; @@ -121,7 +75,7 @@ pub fn group( values: Vec, tag: impl Into, ) -> Result { - crate::utils::data::group(column_name.clone(), &values, None, tag) + crate::utils::data::group(Some(column_name.clone()), &values, None, tag) } #[cfg(test)] diff --git a/crates/nu-cli/src/commands/group_by_date.rs b/crates/nu-cli/src/commands/group_by_date.rs new file mode 100644 index 0000000000..2bdae9f99a --- /dev/null +++ b/crates/nu-cli/src/commands/group_by_date.rs @@ -0,0 +1,103 @@ +use crate::commands::WholeStreamCommand; +use crate::prelude::*; +use nu_errors::ShellError; +use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value}; +use nu_source::Tagged; + +pub struct GroupByDate; + +#[derive(Deserialize)] +pub struct GroupByDateArgs { + column_name: Option>, + format: Option>, +} + +impl WholeStreamCommand for GroupByDate { + fn name(&self) -> &str { + "group-by date" + } + + fn signature(&self) -> Signature { + Signature::build("group-by date") + .optional( + "column_name", + SyntaxShape::String, + "the name of the column to group by", + ) + .named( + "format", + SyntaxShape::String, + "Specify date and time formatting", + Some('f'), + ) + } + + fn usage(&self) -> &str { + "Creates a new table with the data from the table rows grouped by the column given." + } + + fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + args.process(registry, group_by_date)?.run() + } + + fn examples(&self) -> &[Example] { + &[Example { + description: "Group files by type", + example: "ls | group-by date --fmt '%d/%m/%Y'", + }] + } +} + +enum Grouper { + ByDate(Option), +} + +pub fn group_by_date( + GroupByDateArgs { + column_name, + format, + }: GroupByDateArgs, + RunnableContext { input, name, .. }: RunnableContext, +) -> Result { + let stream = async_stream! { + let values: Vec = input.collect().await; + + if values.is_empty() { + yield Err(ShellError::labeled_error( + "Expected table from pipeline", + "requires a table input", + name + )) + } else { + + let grouper = if let Some(Tagged { item: fmt, tag }) = format { + Grouper::ByDate(Some(fmt)) + } else { + Grouper::ByDate(None) + }; + + match grouper { + Grouper::ByDate(None) => { + match crate::utils::data::group(column_name, &values, Some(Box::new(|row: &Value| row.format("%Y-%b-%d"))), &name) { + Ok(grouped) => yield ReturnSuccess::value(grouped), + Err(err) => yield Err(err), + } + } + Grouper::ByDate(Some(fmt)) => { + match crate::utils::data::group(column_name, &values, Some(Box::new(move |row: &Value| { + row.format(&fmt) + })), &name) { + Ok(grouped) => yield ReturnSuccess::value(grouped), + Err(err) => yield Err(err), + } + } + } + } + }; + + Ok(stream.to_output_stream()) +} diff --git a/crates/nu-cli/src/utils/data/group.rs b/crates/nu-cli/src/utils/data/group.rs index 3c468033a3..e7b6161e05 100644 --- a/crates/nu-cli/src/utils/data/group.rs +++ b/crates/nu-cli/src/utils/data/group.rs @@ -1,12 +1,12 @@ use indexmap::IndexMap; use nu_errors::ShellError; use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value}; -use nu_source::{Tag, Tagged}; +use nu_source::{Tag, Tagged, TaggedItem}; use nu_value_ext::{as_string, get_data_by_key}; #[allow(clippy::type_complexity)] pub fn group( - column_name: Tagged, + column_name: Option>, values: &[Value], grouper: Option Result + Send>>, tag: impl Into, @@ -16,7 +16,11 @@ pub fn group( let mut groups: IndexMap> = IndexMap::new(); for value in values { - let group_key = get_data_by_key(&value, column_name.borrow_spanned()); + let group_key = if let Some(ref column_name) = column_name { + get_data_by_key(&value, column_name.borrow_spanned()) + } else { + Some(value.clone()) + }; if let Some(group_key) = group_key { let group_key = if let Some(ref grouper) = grouper { @@ -27,6 +31,8 @@ pub fn group( let group = groups.entry(group_key?).or_insert(vec![]); group.push((*value).clone()); } else { + let column_name = column_name.unwrap_or_else(|| String::from("").tagged(&tag)); + let possibilities = value.data_descriptors(); let mut possible_matches: Vec<_> = possibilities diff --git a/crates/nu-value-ext/src/lib.rs b/crates/nu-value-ext/src/lib.rs index 6f76d82121..6c0f79db96 100644 --- a/crates/nu-value-ext/src/lib.rs +++ b/crates/nu-value-ext/src/lib.rs @@ -392,6 +392,7 @@ pub fn as_path_member(value: &Value) -> Result { pub fn as_string(value: &Value) -> Result { match &value.value { UntaggedValue::Primitive(Primitive::String(s)) => Ok(s.clone()), + UntaggedValue::Primitive(Primitive::Date(dt)) => Ok(dt.format("%Y-%b-%d").to_string()), UntaggedValue::Primitive(Primitive::Boolean(x)) => Ok(format!("{}", x)), UntaggedValue::Primitive(Primitive::Decimal(x)) => Ok(format!("{}", x)), UntaggedValue::Primitive(Primitive::Int(x)) => Ok(format!("{}", x)),