From 7bf09559a648a2468dc3ea4def1dd72fc2374485 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Thu, 14 Jul 2022 23:24:32 +0300 Subject: [PATCH] Refactoring nu_table (#6049) * nu-table: Remove unused dependencies Signed-off-by: Maxim Zhiburt * nu-table: Small refactoring Signed-off-by: Maxim Zhiburt * nu-table: Refactoring Signed-off-by: Maxim Zhiburt * nu-table: Refactoring alignments Signed-off-by: Maxim Zhiburt * nu-table: Add width check Signed-off-by: Maxim Zhiburt * nu-table/ Use commit instead of branch of tabled To be safe * Update Cargo.lock Signed-off-by: Maxim Zhiburt * nu-table: Bump tabled Signed-off-by: Maxim Zhiburt --- Cargo.lock | 27 +--- crates/nu-command/src/viewers/table.rs | 33 ++-- crates/nu-table/Cargo.toml | 5 +- crates/nu-table/src/lib.rs | 2 +- crates/nu-table/src/main.rs | 7 +- crates/nu-table/src/table.rs | 210 +++++++++++++++---------- crates/nu-table/src/width_control.rs | 17 +- 7 files changed, 160 insertions(+), 141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c65773b86d..e57634d80c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,22 +94,6 @@ dependencies = [ "ansi-parser", ] -[[package]] -name = "ansi-str" -version = "0.2.0" -source = "git+https://github.com/zhiburt/ansi-str?rev=655cd8125a032286082794690c2cc6dc835345b4#655cd8125a032286082794690c2cc6dc835345b4" -dependencies = [ - "ansi_rs", -] - -[[package]] -name = "ansi_rs" -version = "0.1.0" -source = "git+https://gitlab.com/zhiburt/ansi_rs#16173ad4a2812a2b0dc9faf1552d5985db4ee310" -dependencies = [ - "nom 7.1.1", -] - [[package]] name = "ansi_term" version = "0.12.1" @@ -2856,14 +2840,11 @@ dependencies = [ name = "nu-table" version = "0.65.1" dependencies = [ - "ansi-str 0.2.0 (git+https://github.com/zhiburt/ansi-str?rev=655cd8125a032286082794690c2cc6dc835345b4)", "atty", "nu-ansi-term", "nu-protocol", - "regex", "strip-ansi-escapes", "tabled", - "unicode-width", ] [[package]] @@ -3187,7 +3168,7 @@ checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" [[package]] name = "papergrid" version = "0.4.0" -source = "git+https://github.com/zhiburt/tabled?branch=master#a17e76783422de7db213ef07e81ed1ef78ca599e" +source = "git+https://github.com/zhiburt/tabled?rev=cca285b1fc0eac48b8a386c8884092d894d0e7ae#cca285b1fc0eac48b8a386c8884092d894d0e7ae" dependencies = [ "ansi-str 0.1.1", "bytecount", @@ -4805,9 +4786,9 @@ dependencies = [ [[package]] name = "tabled" version = "0.7.0" -source = "git+https://github.com/zhiburt/tabled?branch=master#a17e76783422de7db213ef07e81ed1ef78ca599e" +source = "git+https://github.com/zhiburt/tabled?rev=cca285b1fc0eac48b8a386c8884092d894d0e7ae#cca285b1fc0eac48b8a386c8884092d894d0e7ae" dependencies = [ - "ansi-str 0.2.0 (git+https://github.com/zhiburt/ansi-str?branch=master)", + "ansi-str 0.2.0", "papergrid", "tabled_derive", "unicode-width", @@ -4816,7 +4797,7 @@ dependencies = [ [[package]] name = "tabled_derive" version = "0.3.0" -source = "git+https://github.com/zhiburt/tabled?branch=master#a17e76783422de7db213ef07e81ed1ef78ca599e" +source = "git+https://github.com/zhiburt/tabled?rev=cca285b1fc0eac48b8a386c8884092d894d0e7ae#cca285b1fc0eac48b8a386c8884092d894d0e7ae" dependencies = [ "heck 0.4.0", "proc-macro-error", diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 3ed62ee976..45c0317236 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -7,7 +7,7 @@ use nu_protocol::{ format_error, Category, Config, DataSource, Example, IntoPipelineData, ListStream, PipelineData, PipelineMetadata, RawStream, ShellError, Signature, Span, SyntaxShape, Value, }; -use nu_table::{StyledString, TableTheme, TextStyle}; +use nu_table::{Alignments, StyledString, TableTheme, TextStyle}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Instant; @@ -167,9 +167,11 @@ impl Command for Table { ]) } - let table = nu_table::Table::new(None, output, load_theme_from_config(config)); + let table = + nu_table::Table::new(Vec::new(), output, load_theme_from_config(config)); - let result = nu_table::draw_table(&table, term_width, &color_hm, config) + let result = table + .draw_table(config, &color_hm, Alignments::default(), term_width) .unwrap_or_else(|| format!("Couldn't fit table into {} columns!", term_width)); Ok(Value::String { @@ -426,18 +428,16 @@ fn convert_to_table( } Ok(Some(nu_table::Table::new( - Some( - headers - .into_iter() - .map(|x| StyledString { - contents: x, - style: TextStyle { - alignment: nu_table::Alignment::Center, - color_style: Some(color_hm["header"]), - }, - }) - .collect(), - ), + headers + .into_iter() + .map(|x| StyledString { + contents: x, + style: TextStyle { + alignment: nu_table::Alignment::Center, + color_style: Some(color_hm["header"]), + }, + }) + .collect(), data.into_iter() .map(|x| { x.into_iter() @@ -554,7 +554,8 @@ impl Iterator for PagingTableCreator { match table { Ok(Some(table)) => { - let result = nu_table::draw_table(&table, term_width, &color_hm, &self.config) + let result = table + .draw_table(&self.config, &color_hm, Alignments::default(), term_width) .unwrap_or_else(|| format!("Couldn't fit table into {} columns!", term_width)); Some(Ok(result.as_bytes().to_vec())) diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index 4ece8b999a..79147ae8ea 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -14,9 +14,6 @@ path = "src/main.rs" [dependencies] nu-ansi-term = "0.46.0" nu-protocol = { path = "../nu-protocol", version = "0.65.1" } -regex = "1.4" -unicode-width = "0.1.8" strip-ansi-escapes = "0.1.1" -ansi-str = { git = "https://github.com/zhiburt/ansi-str", rev = "655cd8125a032286082794690c2cc6dc835345b4" } atty = "0.2.14" -tabled = { git = "https://github.com/zhiburt/tabled", branch = "master", features = ["color"] } +tabled = { git = "https://github.com/zhiburt/tabled", rev = "9c831d5bc5bcd5a7b7a349ce63f746a64bf1c278", features = ["color"] } diff --git a/crates/nu-table/src/lib.rs b/crates/nu-table/src/lib.rs index b647e84ddd..f476bb26a4 100644 --- a/crates/nu-table/src/lib.rs +++ b/crates/nu-table/src/lib.rs @@ -3,6 +3,6 @@ mod table_theme; mod textstyle; mod width_control; -pub use table::{draw_table, Table}; +pub use table::{Alignments, Table}; pub use table_theme::TableTheme; pub use textstyle::{Alignment, StyledString, TextStyle}; diff --git a/crates/nu-table/src/main.rs b/crates/nu-table/src/main.rs index 79736e75a6..9ecc2a65c8 100644 --- a/crates/nu-table/src/main.rs +++ b/crates/nu-table/src/main.rs @@ -1,5 +1,5 @@ use nu_protocol::Config; -use nu_table::{draw_table, StyledString, Table, TableTheme, TextStyle}; +use nu_table::{Alignments, StyledString, Table, TableTheme, TextStyle}; use std::collections::HashMap; fn main() { @@ -23,13 +23,14 @@ fn main() { // The table rows let rows = vec_of_str_to_vec_of_styledstr(&row_data, false); // The table itself - let table = Table::new(Some(headers), vec![rows; 3], TableTheme::rounded()); + let table = Table::new(headers, vec![rows; 3], TableTheme::rounded()); // FIXME: Config isn't available from here so just put these here to compile let color_hm: HashMap = HashMap::new(); // get the default config let config = Config::default(); // Capture the table as a string - let output_table = draw_table(&table, width, &color_hm, &config) + let output_table = table + .draw_table(&config, &color_hm, Alignments::default(), width) .unwrap_or_else(|| format!("Couldn't fit table into {} columns!", width)); // Draw the table println!("{}", output_table) diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index cf249b2cbe..8d47dc3861 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -5,14 +5,15 @@ use nu_protocol::{Config, FooterMode, TrimStrategy}; use tabled::{ builder::Builder, formatting_settings::AlignmentStrategy, - object::{Cell, Columns, Rows}, + object::{Cell, Columns, Rows, Segment}, papergrid, style::BorderColor, - Alignment, Modify, TableOption, Width, + Alignment, AlignmentHorizontal, Modify, ModifyObject, TableOption, Width, }; use crate::{table_theme::TableTheme, width_control::maybe_truncate_columns, StyledString}; +/// Table represent a table view. #[derive(Debug)] pub struct Table { headers: Option>, @@ -20,15 +21,36 @@ pub struct Table { theme: TableTheme, } +#[derive(Debug)] +pub struct Alignments { + data: AlignmentHorizontal, + index: AlignmentHorizontal, + header: AlignmentHorizontal, +} + +impl Default for Alignments { + fn default() -> Self { + Self { + data: AlignmentHorizontal::Center, + index: AlignmentHorizontal::Right, + header: AlignmentHorizontal::Center, + } + } +} + impl Table { + /// Creates a [Table] instance. + /// + /// If `headers.is_empty` then no headers will be rendered. pub fn new( - headers: Option>, + headers: Vec, data: Vec>, theme: TableTheme, ) -> Table { - let headers = match headers { - Some(headers) if headers.is_empty() => None, - headers => headers, + let headers = if headers.is_empty() { + None + } else { + Some(headers) }; Table { @@ -37,34 +59,59 @@ impl Table { theme, } } + + /// Draws a trable on a String. + /// + /// It returns None in case where table cannot be fit to a terminal width. + pub fn draw_table( + &self, + config: &Config, + color_hm: &HashMap, + alignments: Alignments, + termwidth: usize, + ) -> Option { + draw_table(self, config, color_hm, alignments, termwidth) + } } -pub fn draw_table( +fn draw_table( table: &Table, - termwidth: usize, - color_hm: &HashMap, config: &Config, + color_hm: &HashMap, + alignments: Alignments, + termwidth: usize, ) -> Option { - let (mut headers, mut data, count_columns) = - table_fix_lengths(table.headers.as_ref(), &table.data); + let mut headers = colorize_headers(table.headers.as_deref()); + let mut data = colorize_data(&table.data, table.headers.as_ref().map_or(0, |h| h.len())); + + let count_columns = table_fix_lengths(headers.as_mut(), &mut data); maybe_truncate_columns(&mut headers, &mut data, count_columns, termwidth); - let alignments = build_alignment_map(&table.data); - - let headers = table_header_to_strings(headers); - let data = table_data_to_strings(data, count_columns); - + let table_data = &table.data; let theme = &table.theme; let with_header = headers.is_some(); let with_footer = with_header && need_footer(config, data.len() as u64); + let with_index = !config.disable_table_indexes; - let table = build_table(data, headers, Some(alignments), config, with_footer); + let table = build_table(data, headers, with_footer); let table = load_theme(table, color_hm, theme, with_footer, with_header); - + let table = align_table( + table, + alignments, + with_index, + with_header, + with_footer, + table_data, + ); let table = table_trim_columns(table, termwidth, &config.trim_strategy); - Some(print_table(table, config)) + let table = print_table(table, config); + if table_width(&table) > termwidth { + None + } else { + Some(table) + } } fn print_table(table: tabled::Table, config: &Config) -> String { @@ -83,19 +130,20 @@ fn print_table(table: tabled::Table, config: &Config) -> String { } } -fn table_data_to_strings( - table_data: Vec>, - count_headers: usize, -) -> Vec> { - let mut data = vec![Vec::with_capacity(count_headers); table_data.len()]; - for (row, row_data) in table_data.into_iter().enumerate() { +fn table_width(table: &str) -> usize { + table.lines().next().map_or(0, papergrid::string_width) +} + +fn colorize_data(table_data: &[Vec], count_columns: usize) -> Vec> { + let mut data = vec![Vec::with_capacity(count_columns); table_data.len()]; + for (row, row_data) in table_data.iter().enumerate() { for cell in row_data { let colored_text = cell .style .color_style .as_ref() .map(|color| color.paint(&cell.contents).to_string()) - .unwrap_or(cell.contents); + .unwrap_or_else(|| cell.contents.clone()); data[row].push(colored_text) } @@ -104,8 +152,8 @@ fn table_data_to_strings( data } -fn table_header_to_strings(table_headers: Option>) -> Option> { - table_headers.map(|table_headers| { +fn colorize_headers(headers: Option<&[StyledString]>) -> Option> { + headers.map(|table_headers| { let mut headers = Vec::with_capacity(table_headers.len()); for cell in table_headers { let colored_text = cell @@ -113,7 +161,7 @@ fn table_header_to_strings(table_headers: Option>) -> Option>) -> Option]) -> Vec> { - let mut v = vec![Vec::new(); data.len()]; - for (i, row) in data.iter().enumerate() { - let mut row_alignments = Vec::with_capacity(row.len()); - for col in row { - row_alignments.push(Alignment::Horizontal(col.style.alignment)); - } - - v[i] = row_alignments; - } - - v -} - fn build_table( data: Vec>, headers: Option>, - alignment_map: Option>>, - config: &Config, need_footer: bool, ) -> tabled::Table { - let header_present = headers.is_some(); let mut builder = Builder::from(data); if let Some(headers) = headers { @@ -154,38 +185,65 @@ fn build_table( } } - let mut table = builder.build(); + builder.build() +} +fn align_table( + mut table: tabled::Table, + alignments: Alignments, + with_index: bool, + with_header: bool, + with_footer: bool, + data: &[Vec], +) -> tabled::Table { table = table.with( - Modify::new(Rows::new(1..)) - .with(Alignment::left()) + Modify::new(Segment::all()) + .with(Alignment::Horizontal(alignments.data)) .with(AlignmentStrategy::PerLine), ); - if !config.disable_table_indexes { - table = table.with(Modify::new(Columns::first()).with(Alignment::right())); + if with_index { + table = + table.with(Modify::new(Columns::first()).with(Alignment::Horizontal(alignments.index))); } - if header_present { - table = table.with(Modify::new(Rows::first()).with(Alignment::center())); + if with_header { + let alignment = Alignment::Horizontal(alignments.header); + table = table.with(Modify::new(Rows::first()).with(alignment.clone())); + + if with_footer { + table = table.with(Modify::new(Rows::last()).with(alignment)); + } } - if let Some(alignments) = alignment_map { - table = apply_alignments(table, alignments, header_present); - } + table = override_alignments(table, data, with_header, with_index, alignments); table } -fn apply_alignments( +fn override_alignments( mut table: tabled::Table, - alignment: Vec>, + data: &[Vec], header_present: bool, + index_present: bool, + alignments: Alignments, ) -> tabled::Table { let offset = if header_present { 1 } else { 0 }; - for (row, alignments) in alignment.into_iter().enumerate() { - for (col, alignment) in alignments.into_iter().enumerate() { - table = table.with(Modify::new(Cell(row + offset, col)).with(alignment)); + for (row, rows) in data.iter().enumerate() { + for (col, s) in rows.iter().enumerate() { + if index_present && col == 0 && s.style.alignment == alignments.index { + continue; + } + + if s.style.alignment == alignments.data { + continue; + } + + table = table.with( + Cell(row + offset, col) + .modify() + .with(Alignment::Horizontal(s.style.alignment)), + ); } } @@ -307,31 +365,21 @@ impl tabled::TableOption for &TrimStrategyModifier<'_> { } } -fn table_fix_lengths( - headers: Option<&Vec>, - data: &[Vec], -) -> (Option>, Vec>, usize) { - let length = table_find_max_length(headers, data); +fn table_fix_lengths(headers: Option<&mut Vec>, data: &mut [Vec]) -> usize { + let length = table_find_max_length(headers.as_deref(), data); - let headers_fixed = headers.map(|h| { - let mut headers_fixed = Vec::with_capacity(length); - headers_fixed.extend(h.iter().cloned()); - headers_fixed.extend(std::iter::repeat(StyledString::default()).take(length - h.len())); - headers_fixed - }); - - let mut data_fixed = Vec::with_capacity(data.len()); - for row in data { - let mut row_fixed = Vec::with_capacity(length); - row_fixed.extend(row.iter().cloned()); - row_fixed.extend(std::iter::repeat(StyledString::default()).take(length - row.len())); - data_fixed.push(row_fixed); + if let Some(headers) = headers { + headers.extend(std::iter::repeat(String::default()).take(length - headers.len())); } - (headers_fixed, data_fixed, length) + for row in data { + row.extend(std::iter::repeat(String::default()).take(length - row.len())); + } + + length } -fn table_find_max_length(headers: Option<&Vec>, data: &[Vec]) -> usize { +fn table_find_max_length(headers: Option<&Vec>, data: &[Vec]) -> usize { let mut length = headers.map_or(0, |h| h.len()); for row in data { length = std::cmp::max(length, row.len()); diff --git a/crates/nu-table/src/width_control.rs b/crates/nu-table/src/width_control.rs index af5a2b6384..4793484b27 100644 --- a/crates/nu-table/src/width_control.rs +++ b/crates/nu-table/src/width_control.rs @@ -1,9 +1,6 @@ -use crate::textstyle::TextStyle; -use crate::StyledString; - pub(crate) fn maybe_truncate_columns( - headers: &mut Option>, - data: &mut [Vec], + headers: &mut Option>, + data: &mut [Vec], length: usize, termwidth: usize, ) { @@ -14,20 +11,14 @@ pub(crate) fn maybe_truncate_columns( if let Some(headers) = headers { if max_num_of_columns < length { headers.truncate(max_num_of_columns); - headers.push(StyledString::new( - String::from("..."), - TextStyle::basic_center(), - )); + headers.push(String::from("...")); } } if max_num_of_columns < length { for entry in data.iter_mut() { entry.truncate(max_num_of_columns); - entry.push(StyledString::new( - String::from("..."), - TextStyle::basic_center(), - )); + entry.push(String::from("...")); } } }