use crate::commands::WholeStreamCommand; use crate::data::{TaggedDictBuilder, TaggedListBuilder}; use crate::errors::ShellError; use crate::prelude::*; use chrono::{DateTime, NaiveDate, Utc}; pub struct TSortBy; #[derive(Deserialize)] pub struct TSortByArgs { #[serde(rename(deserialize = "show-columns"))] show_columns: bool, group_by: Option>, #[allow(unused)] split_by: Option, } impl WholeStreamCommand for TSortBy { fn name(&self) -> &str { "t-sort-by" } fn signature(&self) -> Signature { Signature::build("t-sort-by") .switch("show-columns", "Displays the column names sorted") .named( "group_by", SyntaxShape::String, "the name of the column to group by", ) .named( "split_by", SyntaxShape::String, "the name of the column within the grouped by table to split by", ) } fn usage(&self) -> &str { "Sort by the given columns." } fn run( &self, args: CommandArgs, registry: &CommandRegistry, ) -> Result { args.process(registry, t_sort_by)?.run() } } fn t_sort_by( TSortByArgs { show_columns, group_by, .. }: TSortByArgs, RunnableContext { input, name, .. }: RunnableContext, ) -> Result { Ok(OutputStream::new(async_stream! { let values: Vec> = input.values.collect().await; let column_grouped_by_name = if let Some(grouped_by) = group_by { Some(grouped_by.item().clone()) } else { None }; if show_columns { for label in columns_sorted(column_grouped_by_name, &values[0], &name).iter() { yield ReturnSuccess::value(label.clone()); } } else { match t_sort(column_grouped_by_name, None, &values[0], name) { Ok(sorted) => yield ReturnSuccess::value(sorted), Err(err) => yield Err(err) } } })) } pub fn columns_sorted( _group_by_name: Option, value: &Tagged, tag: impl Into, ) -> Vec> { let origin_tag = tag.into(); match value { Tagged { item: Value::Row(rows), .. } => { let mut keys: Vec> = rows.entries .keys() .map(|s| s.as_ref()) .map(|k: &str| { let date = NaiveDate::parse_from_str(k, "%B %d-%Y"); let date = match date { Ok(parsed) => Value::Primitive(Primitive::Date( DateTime::::from_utc(parsed.and_hms(12, 34, 56), Utc), )), Err(_) => Value::string(k), }; date.tagged_unknown() }) .collect(); keys.sort(); let keys: Vec = keys .into_iter() .map(|k| { Value::string(match k { Tagged { item: Value::Primitive(Primitive::Date(d)), .. } => format!("{}", d.format("%B %d-%Y")), _ => k.as_string().unwrap(), }) }) .collect(); keys.into_iter().map(|k| k.tagged(&origin_tag)).collect() } _ => vec![Value::string("default").tagged(&origin_tag)], } } pub fn t_sort( group_by_name: Option, split_by_name: Option, value: &Tagged, tag: impl Into, ) -> Result, ShellError> { let origin_tag = tag.into(); match group_by_name { Some(column_name) => { let sorted_labels = columns_sorted(Some(column_name), value, &origin_tag); match split_by_name { None => { let mut dataset = TaggedDictBuilder::new(&origin_tag); dataset.insert_tagged("default", value.clone()); let dataset = dataset.into_tagged_value(); let split_labels = match &dataset { Tagged { item: Value::Row(rows), .. } => { let mut keys: Vec> = rows .entries .keys() .map(|s| s.as_ref()) .map(|k: &str| { let date = NaiveDate::parse_from_str(k, "%B %d-%Y"); let date = match date { Ok(parsed) => Value::Primitive(Primitive::Date( DateTime::::from_utc( parsed.and_hms(12, 34, 56), Utc, ), )), Err(_) => Value::string(k), }; date.tagged_unknown() }) .collect(); keys.sort(); let keys: Vec = keys .into_iter() .map(|k| { Value::string(match k { Tagged { item: Value::Primitive(Primitive::Date(d)), .. } => format!("{}", d.format("%B %d-%Y")), _ => k.as_string().unwrap(), }) }) .collect(); keys.into_iter().map(|k| k.tagged(&origin_tag)).collect() } _ => vec![], }; let results: Vec>> = split_labels .into_iter() .map(|split| { let groups = dataset.get_data_by_key(&split.as_string().unwrap()); sorted_labels .clone() .into_iter() .map(|label| { let label = label.as_string().unwrap(); match groups { Some(Tagged { item: Value::Row(dict), .. }) => dict.get_data_by_key(&label).unwrap().clone(), _ => Value::Table(vec![]).tagged(&origin_tag), } }) .collect() }) .collect(); let mut outer = TaggedListBuilder::new(&origin_tag); for i in results { outer.insert_tagged(Value::Table(i).tagged(&origin_tag)); } return Ok(Value::Table(outer.list).tagged(&origin_tag)); } Some(_) => return Ok(Value::nothing().tagged(&origin_tag)), } } None => return Ok(Value::nothing().tagged(&origin_tag)), } } #[cfg(test)] mod tests { use crate::commands::group_by::group; use crate::commands::t_sort_by::{columns_sorted, t_sort}; use crate::data::meta::*; use crate::Value; use indexmap::IndexMap; fn string(input: impl Into) -> Tagged { Value::string(input.into()).tagged_unknown() } fn row(entries: IndexMap>) -> Tagged { Value::row(entries).tagged_unknown() } fn table(list: &Vec>) -> Tagged { Value::table(list).tagged_unknown() } fn nu_releases_grouped_by_date() -> Tagged { let key = String::from("date").tagged_unknown(); group(&key, nu_releases_commiters(), Tag::unknown()).unwrap() } fn nu_releases_commiters() -> Vec> { vec![ row( indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}, ), row( indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")}, ), row( indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")}, ), row( indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("September 24-2019")}, ), row( indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")}, ), row( indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("September 24-2019")}, ), row( indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")}, ), row( indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("September 24-2019")}, ), row( indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")}, ), ] } #[test] fn show_columns_sorted_given_a_column_to_sort_by() { let by_column = String::from("date"); assert_eq!( columns_sorted( Some(by_column), &nu_releases_grouped_by_date(), Tag::unknown() ), vec![ string("August 23-2019"), string("September 24-2019"), string("October 10-2019") ] ) } #[test] fn sorts_the_tables() { let group_by = String::from("date"); assert_eq!( t_sort( Some(group_by), None, &nu_releases_grouped_by_date(), Tag::unknown() ) .unwrap(), table(&vec![table(&vec![ table(&vec![ row( indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")} ), row( indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")} ), row( indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")} ) ]), table(&vec![ row( indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("September 24-2019")} ), row( indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("September 24-2019")} ), row( indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("September 24-2019")} ) ]), table(&vec![ row( indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")} ), row( indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")} ), row( indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")} ) ]), ]),]) ); } }