split format/table::from_list into multiple functions

This commit is contained in:
Thibaut Brandscheid 2019-12-07 11:40:47 +01:00
parent 37cb7fec77
commit 04b214bef6

View File

@ -9,13 +9,15 @@ use textwrap::fill;
use prettytable::format::{FormatBuilder, LinePosition, LineSeparator}; use prettytable::format::{FormatBuilder, LinePosition, LineSeparator};
use prettytable::{color, Attr, Cell, Row, Table}; use prettytable::{color, Attr, Cell, Row, Table};
type Entries = Vec<Vec<(String, &'static str)>>;
#[derive(Debug, new)] #[derive(Debug, new)]
pub struct TableView { pub struct TableView {
// List of header cell values: // List of header cell values:
headers: Vec<String>, headers: Vec<String>,
// List of rows of cells, each containing value and prettytable style-string: // List of rows of cells, each containing value and prettytable style-string:
entries: Vec<Vec<(String, &'static str)>>, entries: Entries,
} }
enum TableMode { enum TableMode {
@ -50,18 +52,60 @@ impl TableView {
return None; return None;
} }
// Different platforms want different amounts of buffer, not sure why
let termwidth = std::cmp::max(textwrap::termwidth(), 20);
let mut headers = TableView::merge_descriptors(values); let mut headers = TableView::merge_descriptors(values);
let mut entries = values_to_entries(values, &mut headers, starting_idx);
let max_per_column = max_per_column(&headers, &entries, values.len());
maybe_truncate_columns(&mut headers, &mut entries, termwidth);
let headers_len = headers.len();
// Measure how big our columns need to be (accounting for separators also)
let max_naive_column_width = (termwidth - 3 * (headers_len - 1)) / headers_len;
let column_space =
ColumnSpace::measure(&max_per_column, max_naive_column_width, headers_len);
// This gives us the max column width
let max_column_width = column_space.max_width(termwidth);
// This width isn't quite right, as we're rounding off some of our space
let column_space = column_space.fix_almost_column_width(
&max_per_column,
max_naive_column_width,
max_column_width,
headers_len,
);
// This should give us the final max column width
let max_column_width = column_space.max_width(termwidth);
// Wrap cells as needed
let table_view = wrap_cells(
headers,
entries,
max_per_column,
max_naive_column_width,
max_column_width,
);
Some(table_view)
}
}
fn values_to_entries(values: &[Value], headers: &mut Vec<String>, starting_idx: usize) -> Entries {
let mut entries = vec![];
let values_len = values.len();
if headers.is_empty() { if headers.is_empty() {
headers.push("<value>".to_string()); headers.push("<value>".to_string());
} }
let mut entries = vec![];
for (idx, value) in values.iter().enumerate() { for (idx, value) in values.iter().enumerate() {
let mut row: Vec<(String, &'static str)> = headers let mut row: Vec<(String, &'static str)> = headers
.iter() .iter()
.map(|d| { .map(|d: &String| {
if d == "<value>" { if d == "<value>" {
match value { match value {
Value { Value {
@ -94,7 +138,7 @@ impl TableView {
}) })
.collect(); .collect();
if values.len() > 1 { if values_len > 1 {
// Indices are black, bold, right-aligned: // Indices are black, bold, right-aligned:
row.insert(0, ((starting_idx + idx).to_string(), "Fdbr")); row.insert(0, ((starting_idx + idx).to_string(), "Fdbr"));
} }
@ -102,15 +146,19 @@ impl TableView {
entries.push(row); entries.push(row);
} }
let mut max_per_column = vec![]; if values_len > 1 {
if values.len() > 1 {
headers.insert(0, "#".to_owned()); headers.insert(0, "#".to_owned());
} }
entries
}
fn max_per_column(headers: &Vec<String>, entries: &Entries, values_len: usize) -> Vec<usize> {
let mut max_per_column = vec![];
for i in 0..headers.len() { for i in 0..headers.len() {
let mut current_col_max = 0; let mut current_col_max = 0;
let iter = entries.iter().take(values.len()); let iter = entries.iter().take(values_len);
for entry in iter { for entry in iter {
let value_length = entry[i].0.chars().count(); let value_length = entry[i].0.chars().count();
@ -122,9 +170,10 @@ impl TableView {
max_per_column.push(std::cmp::max(current_col_max, headers[i].chars().count())); max_per_column.push(std::cmp::max(current_col_max, headers[i].chars().count()));
} }
// Different platforms want different amounts of buffer, not sure why max_per_column
let termwidth = std::cmp::max(textwrap::termwidth(), 20); }
fn maybe_truncate_columns(headers: &mut Vec<String>, entries: &mut Entries, termwidth: usize) {
// Make sure we have enough space for the columns we have // Make sure we have enough space for the columns we have
let max_num_of_columns = termwidth / 10; let max_num_of_columns = termwidth / 10;
@ -132,28 +181,40 @@ impl TableView {
if max_num_of_columns < headers.len() { if max_num_of_columns < headers.len() {
headers.truncate(max_num_of_columns); headers.truncate(max_num_of_columns);
for entry in &mut entries { for entry in &mut entries.into_iter() {
entry.truncate(max_num_of_columns); entry.truncate(max_num_of_columns);
} }
headers.push("...".to_owned()); headers.push("...".to_owned());
for entry in &mut entries {
for entry in &mut entries.into_iter() {
entry.push(("...".to_owned(), "c")); // ellipsis is centred entry.push(("...".to_owned(), "c")); // ellipsis is centred
} }
} }
}
// Measure how big our columns need to be (accounting for separators also) struct ColumnSpace {
let max_naive_column_width = (termwidth - 3 * (headers.len() - 1)) / headers.len(); num_overages: usize,
underage_sum: usize,
overage_separator_sum: usize,
}
// Measure how much space we have once we subtract off the columns who are small enough impl ColumnSpace {
/// Measure how much space we have once we subtract off the columns who are small enough
fn measure(
max_per_column: &[usize],
max_naive_column_width: usize,
headers_len: usize,
) -> ColumnSpace {
let mut num_overages = 0; let mut num_overages = 0;
let mut underage_sum = 0; let mut underage_sum = 0;
let mut overage_separator_sum = 0; let mut overage_separator_sum = 0;
let iter = max_per_column.iter().enumerate().take(headers.len()); let iter = max_per_column.iter().enumerate().take(headers_len);
for (i, &column_max) in iter { for (i, &column_max) in iter {
if column_max > max_naive_column_width { if column_max > max_naive_column_width {
num_overages += 1; num_overages += 1;
if i != (headers.len() - 1) { if i != (headers_len - 1) {
overage_separator_sum += 3; overage_separator_sum += 3;
} }
if i == 0 { if i == 0 {
@ -162,7 +223,7 @@ impl TableView {
} else { } else {
underage_sum += column_max; underage_sum += column_max;
// if column isn't last, add 3 for its separator // if column isn't last, add 3 for its separator
if i != (headers.len() - 1) { if i != (headers_len - 1) {
underage_sum += 3; underage_sum += 3;
} }
if i == 0 { if i == 0 {
@ -171,23 +232,31 @@ impl TableView {
} }
} }
// This gives us the max column width ColumnSpace {
let max_column_width = if num_overages > 0 { num_overages,
(termwidth - 1 - underage_sum - overage_separator_sum) / num_overages underage_sum,
} else { overage_separator_sum,
99999 }
}; }
fn fix_almost_column_width(
self,
max_per_column: &[usize],
max_naive_column_width: usize,
max_column_width: usize,
headers_len: usize,
) -> ColumnSpace {
let mut num_overages = 0;
let mut overage_separator_sum = 0;
let mut underage_sum = self.underage_sum;
let iter = max_per_column.iter().enumerate().take(headers_len);
// This width isn't quite right, as we're rounding off some of our space
num_overages = 0;
overage_separator_sum = 0;
let iter = max_per_column.iter().enumerate().take(headers.len());
for (i, &column_max) in iter { for (i, &column_max) in iter {
if column_max > max_naive_column_width { if column_max > max_naive_column_width {
if column_max <= max_column_width { if column_max <= max_column_width {
underage_sum += column_max; underage_sum += column_max;
// if column isn't last, add 3 for its separator // if column isn't last, add 3 for its separator
if i != (headers.len() - 1) { if i != (headers_len - 1) {
underage_sum += 3; underage_sum += 3;
} }
if i == 0 { if i == 0 {
@ -196,7 +265,7 @@ impl TableView {
} else { } else {
// Column is still too large, so let's count it // Column is still too large, so let's count it
num_overages += 1; num_overages += 1;
if i != (headers.len() - 1) { if i != (headers_len - 1) {
overage_separator_sum += 3; overage_separator_sum += 3;
} }
if i == 0 { if i == 0 {
@ -205,25 +274,53 @@ impl TableView {
} }
} }
} }
// This should give us the final max column width
let max_column_width = if num_overages > 0 { ColumnSpace {
(termwidth - 1 - underage_sum - overage_separator_sum) / num_overages num_overages,
underage_sum,
overage_separator_sum,
}
}
fn max_width(&self, termwidth: usize) -> usize {
let ColumnSpace {
num_overages,
underage_sum,
overage_separator_sum,
} = self;
if *num_overages > 0 {
(termwidth - 1 - *underage_sum - *overage_separator_sum) / *num_overages
} else { } else {
99999 99999
}; }
}
}
fn wrap_cells(
mut headers: Vec<String>,
mut entries: Entries,
max_per_column: Vec<usize>,
max_naive_column_width: usize,
max_column_width: usize,
) -> TableView {
{
let entries = &mut entries;
// Wrap cells as needed
for head in 0..headers.len() { for head in 0..headers.len() {
if max_per_column[head] > max_naive_column_width { if max_per_column[head] > max_naive_column_width {
headers[head] = fill(&headers[head], max_column_width); headers[head] = fill(&headers[head], max_column_width);
for entry in &mut entries { for entry in &mut entries.into_iter() {
entry[head].0 = fill(&entry[head].0, max_column_width); entry[head].0 = fill(&entry[head].0, max_column_width);
} }
} }
} }
}
Some(TableView { headers, entries }) TableView {
headers: headers,
entries: entries,
} }
} }