nushell/src/commands/t_sort_by.rs

333 lines
12 KiB
Rust
Raw Normal View History

2019-11-12 08:07:43 +01:00
use crate::commands::WholeStreamCommand;
use crate::data::base::property_get::get_data_by_key;
use crate::data::{value, TaggedDictBuilder, TaggedListBuilder};
2019-11-12 08:07:43 +01:00
use crate::prelude::*;
use chrono::{DateTime, NaiveDate, Utc};
use nu_errors::ShellError;
2019-11-30 01:21:05 +01:00
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
2019-11-12 08:07:43 +01:00
pub struct TSortBy;
#[derive(Deserialize)]
pub struct TSortByArgs {
#[serde(rename(deserialize = "show-columns"))]
show_columns: bool,
group_by: Option<Tagged<String>>,
#[allow(unused)]
split_by: Option<String>,
}
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<OutputStream, ShellError> {
args.process(registry, t_sort_by)?.run()
}
}
fn t_sort_by(
TSortByArgs {
show_columns,
group_by,
..
}: TSortByArgs,
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
Ok(OutputStream::new(async_stream! {
let values: Vec<Value> = input.values.collect().await;
2019-11-12 08:07:43 +01:00
let column_grouped_by_name = if let Some(grouped_by) = group_by {
Some(grouped_by.item().clone())
} else {
None
};
2019-11-12 08:07:43 +01:00
if show_columns {
2019-11-04 16:47:03 +01:00
for label in columns_sorted(column_grouped_by_name, &values[0], &name).into_iter() {
yield ReturnSuccess::value(value::string(label.item).into_value(label.tag));
}
} else {
match t_sort(column_grouped_by_name, None, &values[0], name) {
Ok(sorted) => yield ReturnSuccess::value(sorted),
Err(err) => yield Err(err)
2019-11-12 08:07:43 +01:00
}
}
}))
2019-11-12 08:07:43 +01:00
}
pub fn columns_sorted(
_group_by_name: Option<String>,
value: &Value,
2019-11-12 08:07:43 +01:00
tag: impl Into<Tag>,
2019-11-04 16:47:03 +01:00
) -> Vec<Tagged<String>> {
2019-11-12 08:07:43 +01:00
let origin_tag = tag.into();
match value {
Value {
value: UntaggedValue::Row(rows),
2019-11-12 08:07:43 +01:00
..
} => {
let mut keys: Vec<Value> = 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) => UntaggedValue::Primitive(Primitive::Date(
DateTime::<Utc>::from_utc(parsed.and_hms(12, 34, 56), Utc),
)),
Err(_) => value::string(k),
};
date.into_untagged_value()
})
.collect();
2019-11-12 08:07:43 +01:00
keys.sort();
2019-11-04 16:47:03 +01:00
let keys: Vec<String> = keys
2019-11-12 08:07:43 +01:00
.into_iter()
2019-11-04 16:47:03 +01:00
.map(|k| match k {
Value {
value: UntaggedValue::Primitive(Primitive::Date(d)),
2019-11-04 16:47:03 +01:00
..
} => format!("{}", d.format("%B %d-%Y")),
_ => k.as_string().unwrap().to_string(),
2019-11-12 08:07:43 +01:00
})
.collect();
keys.into_iter().map(|k| k.tagged(&origin_tag)).collect()
}
2019-11-04 16:47:03 +01:00
_ => vec![format!("default").tagged(&origin_tag)],
2019-11-12 08:07:43 +01:00
}
}
pub fn t_sort(
group_by_name: Option<String>,
split_by_name: Option<String>,
value: &Value,
2019-11-12 08:07:43 +01:00
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
2019-11-12 08:07:43 +01:00
let origin_tag = tag.into();
match group_by_name {
Some(column_name) => {
2019-11-04 16:47:03 +01:00
let sorted_labels: Vec<Tagged<String>> =
columns_sorted(Some(column_name), value, &origin_tag);
2019-11-12 08:07:43 +01:00
match split_by_name {
None => {
let mut dataset = TaggedDictBuilder::new(&origin_tag);
dataset.insert_value("default", value.clone());
let dataset = dataset.into_value();
2019-11-12 08:07:43 +01:00
2019-11-04 16:47:03 +01:00
let split_labels: Vec<Tagged<String>> = match &dataset {
Value {
value: UntaggedValue::Row(rows),
2019-11-12 08:07:43 +01:00
..
} => {
2019-11-04 16:47:03 +01:00
let mut keys: Vec<Tagged<String>> = rows
2019-11-12 08:07:43 +01:00
.entries
.keys()
2019-11-04 16:47:03 +01:00
.map(|k| k.clone().tagged_unknown())
2019-11-12 08:07:43 +01:00
.collect();
keys.sort();
2019-11-04 16:47:03 +01:00
keys
2019-11-12 08:07:43 +01:00
}
_ => vec![],
};
let results: Vec<Vec<Value>> = split_labels
2019-11-04 16:47:03 +01:00
.iter()
2019-11-12 08:07:43 +01:00
.map(|split| {
let groups = get_data_by_key(&dataset, split.borrow_spanned());
2019-11-12 08:07:43 +01:00
sorted_labels
.clone()
.into_iter()
2019-11-04 16:47:03 +01:00
.map(|label| match &groups {
Some(Value {
value: UntaggedValue::Row(dict),
2019-11-04 16:47:03 +01:00
..
}) => dict
.get_data_by_key(label.borrow_spanned())
.unwrap()
.clone(),
_ => UntaggedValue::Table(vec![]).into_value(&origin_tag),
2019-11-12 08:07:43 +01:00
})
.collect()
})
.collect();
let mut outer = TaggedListBuilder::new(&origin_tag);
for i in results {
outer.push_value(UntaggedValue::Table(i).into_value(&origin_tag));
2019-11-12 08:07:43 +01:00
}
return Ok(UntaggedValue::Table(outer.list).into_value(&origin_tag));
2019-11-12 08:07:43 +01:00
}
Some(_) => return Ok(value::nothing().into_value(&origin_tag)),
2019-11-12 08:07:43 +01:00
}
}
None => return Ok(value::nothing().into_value(&origin_tag)),
2019-11-12 08:07:43 +01:00
}
}
#[cfg(test)]
mod tests {
use crate::commands::group_by::group;
use crate::commands::t_sort_by::{columns_sorted, t_sort};
use crate::data::value;
2019-11-12 08:07:43 +01:00
use indexmap::IndexMap;
use nu_protocol::{UntaggedValue, Value};
use nu_source::*;
fn string(input: impl Into<String>) -> Value {
value::string(input.into()).into_untagged_value()
2019-11-12 08:07:43 +01:00
}
fn row(entries: IndexMap<String, Value>) -> Value {
value::row(entries).into_untagged_value()
2019-11-12 08:07:43 +01:00
}
fn table(list: &Vec<Value>) -> Value {
value::table(list).into_untagged_value()
2019-11-12 08:07:43 +01:00
}
fn nu_releases_grouped_by_date() -> Value {
2019-11-12 08:07:43 +01:00
let key = String::from("date").tagged_unknown();
group(&key, nu_releases_commiters(), Tag::unknown()).unwrap()
}
fn nu_releases_commiters() -> Vec<Value> {
2019-11-12 08:07:43 +01:00
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![
2019-11-04 16:47:03 +01:00
format!("August 23-2019").tagged_unknown(),
format!("September 24-2019").tagged_unknown(),
format!("October 10-2019").tagged_unknown()
2019-11-12 08:07:43 +01:00
]
)
}
2019-11-12 08:07:43 +01:00
#[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")}
)
]),
]),])
);
}
}