Replace vtable with pivot command

This commit is contained in:
Jonathan Turner 2019-09-17 19:07:11 +12:00
parent 1b42438e8f
commit f6b82e4c0c
6 changed files with 136 additions and 133 deletions

View file

@ -270,13 +270,13 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
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),
]);

View file

@ -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;

133
src/commands/pivot.rs Normal file
View file

@ -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<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")
.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<OutputStream, ShellError> {
args.process(registry, pivot)?.run()
}
}
fn merge_descriptors(values: &[Tagged<Value>]) -> Vec<String> {
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_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))
}

View file

@ -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<OutputStream, ShellError> {
args.process(registry, vtable)?.run()
}
}
pub fn vtable(_args: VTableArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
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))
}

View file

@ -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>;

View file

@ -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<Vec<String>>,
}
impl VTableView {
pub fn from_list(values: &[Tagged<Value>]) -> Option<VTableView> {
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(())
}
}