From f6b82e4c0c033d19e797f8f22b881ff9ecdb042f Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Tue, 17 Sep 2019 19:07:11 +1200 Subject: [PATCH] Replace vtable with pivot command --- src/cli.rs | 2 +- src/commands.rs | 4 +- src/commands/pivot.rs | 133 +++++++++++++++++++++++++++++++++++++++++ src/commands/vtable.rs | 47 --------------- src/format.rs | 2 - src/format/vtable.rs | 81 ------------------------- 6 files changed, 136 insertions(+), 133 deletions(-) create mode 100644 src/commands/pivot.rs delete mode 100644 src/commands/vtable.rs delete mode 100644 src/format/vtable.rs diff --git a/src/cli.rs b/src/cli.rs index 531ffc1f54..c02b919066 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -270,13 +270,13 @@ pub async fn cli() -> Result<(), Box> { per_item_command(Help), whole_stream_command(Exit), whole_stream_command(Autoview), + whole_stream_command(Pivot), per_item_command(Cpy), whole_stream_command(Date), per_item_command(Mkdir), per_item_command(Move), whole_stream_command(Save), whole_stream_command(Table), - whole_stream_command(VTable), whole_stream_command(Version), whole_stream_command(Which), ]); diff --git a/src/commands.rs b/src/commands.rs index af612d5752..5f9b0e5d5e 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -37,6 +37,7 @@ pub(crate) mod next; pub(crate) mod nth; pub(crate) mod open; pub(crate) mod pick; +pub(crate) mod pivot; pub(crate) mod plugin; pub(crate) mod post; pub(crate) mod prev; @@ -62,7 +63,6 @@ pub(crate) mod to_tsv; pub(crate) mod to_yaml; pub(crate) mod trim; pub(crate) mod version; -pub(crate) mod vtable; pub(crate) mod where_; pub(crate) mod which_; @@ -105,6 +105,7 @@ pub(crate) use next::Next; pub(crate) use nth::Nth; pub(crate) use open::Open; pub(crate) use pick::Pick; +pub(crate) use pivot::Pivot; pub(crate) use post::Post; pub(crate) use prev::Previous; pub(crate) use pwd::PWD; @@ -130,6 +131,5 @@ pub(crate) use to_tsv::ToTSV; pub(crate) use to_yaml::ToYAML; pub(crate) use trim::Trim; pub(crate) use version::Version; -pub(crate) use vtable::VTable; pub(crate) use where_::Where; pub(crate) use which_::Which; diff --git a/src/commands/pivot.rs b/src/commands/pivot.rs new file mode 100644 index 0000000000..0232f2d59e --- /dev/null +++ b/src/commands/pivot.rs @@ -0,0 +1,133 @@ +use crate::commands::WholeStreamCommand; +use crate::errors::ShellError; +use crate::prelude::*; +use crate::TaggedDictBuilder; + +pub struct Pivot; + +#[derive(Deserialize)] +pub struct PivotArgs { + rest: Vec>, + #[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") + .switch("header-row") + .switch("ignore-titles") + .rest(SyntaxShape::String) + } + + fn usage(&self) -> &str { + "Pivots the table contents so rows become columns and columns become rows." + } + + fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + args.process(registry, pivot)?.run() + } +} + +fn merge_descriptors(values: &[Tagged]) -> Vec { + 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 { + let stream = async_stream_block! { + let input = context.input.into_vec().await; + + let descs = merge_descriptors(&input); + + let mut headers = vec![]; + + 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 i.get_data_by_key(&desc) { + Some(x) => { + if let Ok(s) = x.as_string() { + headers.push(s); + } 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); + + if !args.ignore_titles && !args.header_row { + dict.insert(headers[column_num].clone(), Value::string(desc.clone())); + column_num += 1 + } + + for i in input.clone() { + match i.get_data_by_key(&desc) { + Some(x) => { + dict.insert_tagged(headers[column_num].clone(), x.clone()); + } + _ => { + dict.insert(headers[column_num].clone(), Value::nothing()); + } + } + column_num += 1; + } + + yield ReturnSuccess::value(dict.into_tagged_value()); + } + + + }; + + Ok(OutputStream::new(stream)) +} diff --git a/src/commands/vtable.rs b/src/commands/vtable.rs deleted file mode 100644 index 5abd4c6d1f..0000000000 --- a/src/commands/vtable.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::commands::WholeStreamCommand; -use crate::errors::ShellError; -use crate::format::VTableView; -use crate::prelude::*; - -pub struct VTable; - -#[derive(Deserialize)] -pub struct VTableArgs {} - -impl WholeStreamCommand for VTable { - fn name(&self) -> &str { - "vtable" - } - - fn signature(&self) -> Signature { - Signature::build("vtable") - } - - fn usage(&self) -> &str { - "View the contents of the pipeline as a vertical (rotated) table." - } - - fn run( - &self, - args: CommandArgs, - registry: &CommandRegistry, - ) -> Result { - args.process(registry, vtable)?.run() - } -} - -pub fn vtable(_args: VTableArgs, context: RunnableContext) -> Result { - let stream = async_stream_block! { - let input = context.input.into_vec().await; - - if input.len() > 0 { - let mut host = context.host.lock().unwrap(); - let view = VTableView::from_list(&input); - if let Some(view) = view { - handle_unexpected(&mut *host, |host| crate::format::print_view(&view, host)); - } - } - }; - - Ok(OutputStream::new(stream)) -} diff --git a/src/format.rs b/src/format.rs index 10b92000b9..6cdd5b256e 100644 --- a/src/format.rs +++ b/src/format.rs @@ -2,14 +2,12 @@ pub(crate) mod entries; pub(crate) mod generic; pub(crate) mod list; pub(crate) mod table; -pub(crate) mod vtable; use crate::prelude::*; pub(crate) use entries::EntriesView; pub(crate) use table::TableView; -pub(crate) use vtable::VTableView; pub(crate) trait RenderView { fn render_view(&self, host: &mut dyn Host) -> Result<(), ShellError>; diff --git a/src/format/vtable.rs b/src/format/vtable.rs deleted file mode 100644 index fe151224f4..0000000000 --- a/src/format/vtable.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::data::Value; -use crate::format::RenderView; -use crate::prelude::*; -use derive_new::new; - -use prettytable::format::{FormatBuilder, LinePosition, LineSeparator}; -use prettytable::{color, Attr, Cell, Row, Table}; - -#[derive(new)] -pub struct VTableView { - entries: Vec>, -} - -impl VTableView { - pub fn from_list(values: &[Tagged]) -> Option { - if values.len() == 0 { - return None; - } - - let item = &values[0]; - let headers = item.data_descriptors(); - - if headers.len() == 0 { - return None; - } - - let mut entries = vec![]; - - for header in headers { - let mut row = vec![]; - - row.push(header.clone()); - for value in values { - row.push(value.get_data(&header).borrow().format_leaf(Some(&header))); - } - entries.push(row); - } - - Some(VTableView { entries }) - } -} - -impl RenderView for VTableView { - fn render_view(&self, host: &mut dyn Host) -> Result<(), ShellError> { - if self.entries.len() == 0 { - return Ok(()); - } - - let mut table = Table::new(); - table.set_format( - FormatBuilder::new() - .column_separator('│') - .separator(LinePosition::Top, LineSeparator::new('━', '┯', ' ', ' ')) - .separator(LinePosition::Title, LineSeparator::new('─', '┼', ' ', ' ')) - .separator(LinePosition::Bottom, LineSeparator::new('━', '┷', ' ', ' ')) - .padding(1, 1) - .build(), - ); - - for row in &self.entries { - table.add_row(Row::new( - row.iter() - .enumerate() - .map(|(idx, h)| { - if idx == 0 { - Cell::new(h) - .with_style(Attr::ForegroundColor(color::GREEN)) - .with_style(Attr::Bold) - } else { - Cell::new(h) - } - }) - .collect(), - )); - } - - table.print_term(&mut *host.out_terminal()).unwrap(); - - Ok(()) - } -}