proxmox/src/api/cli/text_table.rs: implement sort by multiple columns

Also implement sort order flag and column header property.
This commit is contained in:
Dietmar Maurer 2020-02-27 10:01:30 +01:00
parent f62fe221f0
commit 43fd868a6d

View File

@ -141,6 +141,7 @@ impl TableBorders {
/// This structure can be used to set additional rendering information for a table column. /// This structure can be used to set additional rendering information for a table column.
pub struct ColumnConfig { pub struct ColumnConfig {
pub name: String, pub name: String,
pub header: Option<String>,
pub right_align: Option<bool>, pub right_align: Option<bool>,
pub renderer: Option<RenderFunction>, pub renderer: Option<RenderFunction>,
} }
@ -150,6 +151,7 @@ impl ColumnConfig {
pub fn new(name: &str) -> Self { pub fn new(name: &str) -> Self {
Self { Self {
name: name.to_string(), name: name.to_string(),
header: None,
right_align: None, right_align: None,
renderer: None, renderer: None,
} }
@ -164,15 +166,21 @@ impl ColumnConfig {
self.renderer = Some(renderer); self.renderer = Some(renderer);
self self
} }
pub fn header<S: Into<String>>(mut self, header: S) -> Self {
self.header = Some(header.into());
self
}
} }
/// Table formatter configuration /// Table formatter configuration
#[derive(Default)] #[derive(Default)]
pub struct TableFormatOptions { pub struct TableFormatOptions {
/// Can be used to sort after a specific column, if it isn't set we sort /// Can be used to sort after a specific columns, if it isn't set
/// after the leftmost column (with no undef value in $data) this can be /// we sort after the leftmost column (with no undef value in
/// turned off by passing "" as sort_key. /// $data) this can be turned off by passing and empty array. The
pub sortkey: Option<String>, /// boolean argument specifies the sort order (false => ASC, true => DESC)
pub sortkeys: Option<Vec<(String, bool)>>,
/// Print without asciiart border. /// Print without asciiart border.
pub noborder: bool, pub noborder: bool,
/// Print without table header. /// Print without table header.
@ -218,10 +226,26 @@ impl TableFormatOptions {
me me
} }
pub fn sortkey(mut self, sortkey: Option<String>) -> Self { pub fn disable_sort(mut self) -> Self {
self.sortkey = sortkey; self.sortkeys = Some(Vec::new());
self self
} }
pub fn sortby<S: Into<String>>(mut self, key: S, sort_desc: bool) -> Self {
let key = key.into();
match self.sortkeys {
None => {
let mut list = Vec::new();
list.push((key.to_string(), sort_desc));
self.sortkeys = Some(list);
}
Some(ref mut list) => {
list.push((key.to_string(), sort_desc));
}
}
self
}
pub fn noborder(mut self, noborder: bool) -> Self { pub fn noborder(mut self, noborder: bool) -> Self {
self.noborder = noborder; self.noborder = noborder;
self self
@ -280,50 +304,77 @@ fn format_table<W: Write>(
let column_count = properties_to_print.len(); let column_count = properties_to_print.len();
if column_count == 0 { return Ok(()); }; if column_count == 0 { return Ok(()); };
let sortkey = if let Some(ref sortkey) = options.sortkey { let sortkeys = if let Some(ref sortkeys) = options.sortkeys {
sortkey.clone() sortkeys.clone()
} else { } else {
properties_to_print[0].clone() // leftmost let mut keys = Vec::new();
keys.push((properties_to_print[0].clone(), false)); // leftmost, ASC
keys
}; };
let (_optional, sort_prop_schema) = match schema.lookup(&sortkey) { let mut sortinfo = Vec::new();
Some(tup) => tup,
None => bail!("property {} does not exist in schema.", sortkey),
};
let numeric_sort = match sort_prop_schema { for (sortkey, sort_order) in sortkeys {
Schema::Integer(_) => true, let (_optional, sort_prop_schema) = match schema.lookup(&sortkey) {
Schema::Number(_) => true, Some(tup) => tup,
_ => false, None => bail!("property {} does not exist in schema.", sortkey),
}; };
let numeric_sort = match sort_prop_schema {
if numeric_sort { Schema::Integer(_) => true,
use std::cmp::Ordering; Schema::Number(_) => true,
list.sort_unstable_by(move |a, b| { _ => false,
let d1 = a[&sortkey].as_f64(); };
let d2 = b[&sortkey].as_f64(); sortinfo.push((sortkey, sort_order, numeric_sort));
match (d1,d2) {
(None, None) => return Ordering::Greater,
(Some(_), None) => return Ordering::Greater,
(None, Some(_)) => return Ordering::Less,
(Some(a), Some(b)) => {
if a.is_nan() { return Ordering::Greater; }
if b.is_nan() { return Ordering::Less; }
if a < b {
return Ordering::Less;
} else if a > b {
return Ordering::Greater;
}
return Ordering::Equal;
}
};
})
} else {
list.sort_unstable_by_key(move |d| d[&sortkey].to_string());
} }
use std::cmp::Ordering;
list.sort_unstable_by(move |a, b| {
for pos in 0..sortinfo.len() {
let (ref sortkey, sort_desc, numeric) = sortinfo[pos];
let res = if numeric {
let (v1, v2) = if sort_desc {
(b[&sortkey].as_f64(), a[&sortkey].as_f64())
} else {
(a[&sortkey].as_f64(), b[&sortkey].as_f64())
};
match (v1,v2) {
(None, None) => Ordering::Greater,
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(Some(a), Some(b)) => {
if a.is_nan() {
Ordering::Greater
} else if b.is_nan() {
Ordering::Less
} else {
if a < b {
Ordering::Less
} else if a > b {
Ordering::Greater
} else {
Ordering::Equal
}
}
}
}
} else {
let (v1, v2) = if sort_desc {
(b[sortkey].as_str(), a[sortkey].as_str())
} else {
(a[sortkey].as_str(), b[sortkey].as_str())
};
v1.cmp(&v2)
};
if res != Ordering::Equal { return res; }
}
return Ordering::Equal;
});
let mut tabledata: Vec<TableColumn> = Vec::new(); let mut tabledata: Vec<TableColumn> = Vec::new();
let mut column_names = Vec::new();
for name in properties_to_print.iter() { for name in properties_to_print.iter() {
let (_optional, prop_schema) = match schema.lookup(name) { let (_optional, prop_schema) = match schema.lookup(name) {
Some(tup) => tup, Some(tup) => tup,
@ -339,13 +390,23 @@ fn format_table<W: Write>(
let mut renderer = None; let mut renderer = None;
let header;
if let Some(column_config) = options.column_config.iter().find(|v| v.name == *name) { if let Some(column_config) = options.column_config.iter().find(|v| v.name == *name) {
renderer = column_config.renderer; renderer = column_config.renderer;
right_align = column_config.right_align.unwrap_or(right_align); right_align = column_config.right_align.unwrap_or(right_align);
if let Some(ref h) = column_config.header {
header = h.to_owned();
} else {
header = name.to_string();
}
} else {
header = name.to_string();
} }
let mut max_width = name.chars().count(); let mut max_width = header.chars().count();
column_names.push(header);
let mut cells = Vec::new(); let mut cells = Vec::new();
for entry in list.iter() { for entry in list.iter() {
@ -372,7 +433,7 @@ fn format_table<W: Write>(
tabledata.push(TableColumn { cells, width: max_width, right_align}); tabledata.push(TableColumn { cells, width: max_width, right_align});
} }
render_table(output, &tabledata, &properties_to_print, options) render_table(output, &tabledata, &column_names, options)
} }
fn render_table<W: Write>( fn render_table<W: Write>(