nushell/src/commands/pivot.rs

141 lines
4.5 KiB
Rust
Raw Normal View History

2019-09-17 09:07:11 +02:00
use crate::commands::WholeStreamCommand;
use crate::data::base::property_get::get_data_by_key;
use crate::data::value;
2019-09-17 09:07:11 +02:00
use crate::prelude::*;
use crate::TaggedDictBuilder;
use nu_errors::ShellError;
2019-11-30 01:21:05 +01:00
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
use nu_source::{SpannedItem, Tagged};
2019-09-17 09:07:11 +02:00
pub struct Pivot;
#[derive(Deserialize)]
pub struct PivotArgs {
rest: Vec<Tagged<String>>,
#[serde(rename(deserialize = "header-row"))]
header_row: bool,
#[serde(rename(deserialize = "ignore-titles"))]
ignore_titles: bool,
}
impl WholeStreamCommand for Pivot {
fn name(&self) -> &str {
"pivot"
}
fn signature(&self) -> Signature {
Signature::build("pivot")
2019-10-28 06:15:35 +01:00
.switch("header-row", "treat the first row as column names")
.switch("ignore-titles", "don't pivot the column names into values")
.rest(
SyntaxShape::String,
"the names to give columns once pivoted",
)
2019-09-17 09:07:11 +02:00
}
fn usage(&self) -> &str {
"Pivots the table contents so rows become columns and columns become rows."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, pivot)?.run()
}
}
fn merge_descriptors(values: &[Value]) -> Vec<String> {
2019-09-17 09:07:11 +02:00
let mut ret = vec![];
for value in values {
for desc in value.data_descriptors() {
if !ret.contains(&desc) {
ret.push(desc);
}
}
}
ret
}
pub fn pivot(args: PivotArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
2019-09-17 09:07:11 +02:00
let input = context.input.into_vec().await;
let descs = merge_descriptors(&input);
let mut headers: Vec<String> = vec![];
2019-09-17 09:07:11 +02:00
if args.rest.len() > 0 && args.header_row {
yield Err(ShellError::labeled_error("Can not provide header names and use header row", "using header row", context.name));
return;
}
if args.header_row {
for i in input.clone() {
if let Some(desc) = descs.get(0) {
match get_data_by_key(&i, desc[..].spanned_unknown()) {
2019-09-17 09:07:11 +02:00
Some(x) => {
if let Ok(s) = x.as_string() {
headers.push(s.to_string());
2019-09-17 09:07:11 +02:00
} else {
yield Err(ShellError::labeled_error("Header row needs string headers", "used non-string headers", context.name));
return;
}
}
_ => {
yield Err(ShellError::labeled_error("Header row is incomplete and can't be used", "using incomplete header row", context.name));
return;
}
}
} else {
yield Err(ShellError::labeled_error("Header row is incomplete and can't be used", "using incomplete header row", context.name));
return;
}
}
} else {
for i in 0..input.len()+1 {
if let Some(name) = args.rest.get(i) {
headers.push(name.to_string())
} else {
headers.push(format!("Column{}", i));
}
}
}
let descs: Vec<_> = if args.header_row {
descs.iter().skip(1).collect()
} else {
descs.iter().collect()
};
for desc in descs {
let mut column_num: usize = 0;
let mut dict = TaggedDictBuilder::new(&context.name);
2019-09-17 09:07:11 +02:00
if !args.ignore_titles && !args.header_row {
dict.insert_untagged(headers[column_num].clone(), value::string(desc.clone()));
2019-09-17 09:07:11 +02:00
column_num += 1
}
for i in input.clone() {
match get_data_by_key(&i, desc[..].spanned_unknown()) {
2019-09-17 09:07:11 +02:00
Some(x) => {
dict.insert_value(headers[column_num].clone(), x.clone());
2019-09-17 09:07:11 +02:00
}
_ => {
dict.insert_untagged(headers[column_num].clone(), value::nothing());
2019-09-17 09:07:11 +02:00
}
}
column_num += 1;
}
yield ReturnSuccess::value(dict.into_value());
2019-09-17 09:07:11 +02:00
}
};
Ok(OutputStream::new(stream))
}