forked from extern/nushell
split format/table::from_list into multiple functions
This commit is contained in:
parent
37cb7fec77
commit
04b214bef6
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user