Revert "Add an option to move header on borders" (#9908)

Reverts nushell/nushell#9796

This is just draft since we're seeing some issues with the latest fixes
to table drawing that just landed with #9796. We're hoping to get these
fixed, but if we're not able to fix them before the next release, we'll
need to revert (hence this PR, just in case we need it).
This commit is contained in:
JT 2023-08-04 07:52:12 +12:00 committed by GitHub
parent 572698bf3e
commit a98b3124c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 747 additions and 762 deletions

23
Cargo.lock generated
View File

@ -2737,7 +2737,7 @@ dependencies = [
"sha2",
"sqlparser",
"sysinfo",
"tabled 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tabled",
"terminal_size 0.2.6",
"titlecase",
"toml",
@ -2904,7 +2904,7 @@ dependencies = [
"nu-engine",
"nu-protocol",
"nu-utils",
"tabled 0.13.0 (git+https://github.com/zhiburt/tabled/?rev=6c51e3eaa362914a71b868ccb78e7addbebd3657)",
"tabled",
]
[[package]]
@ -3289,9 +3289,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "papergrid"
version = "0.10.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ccbe15f2b6db62f9a9871642746427e297b0ceb85f9a7f1ee5ff47d184d0c8"
checksum = "ae7891b22598926e4398790c8fe6447930c72a67d36d983a49d6ce682ce83290"
dependencies = [
"ansi-str",
"ansitok",
@ -4947,20 +4947,9 @@ dependencies = [
[[package]]
name = "tabled"
version = "0.13.0"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d38d39c754ae037a9bc3ca1580a985db7371cd14f1229172d1db9093feb6739"
dependencies = [
"ansi-str",
"ansitok",
"papergrid",
"unicode-width",
]
[[package]]
name = "tabled"
version = "0.13.0"
source = "git+https://github.com/zhiburt/tabled/?rev=6c51e3eaa362914a71b868ccb78e7addbebd3657#6c51e3eaa362914a71b868ccb78e7addbebd3657"
checksum = "0ce69a5028cd9576063ec1f48edb2c75339fd835e6094ef3e05b3a079bf594a6"
dependencies = [
"ansi-str",
"ansitok",

View File

@ -84,7 +84,7 @@ serde_yaml = "0.9"
sha2 = "0.10"
sqlparser = { version = "0.34", features = ["serde"], optional = true }
sysinfo = "0.29"
tabled = { version = "0.13.0", features = ["color"], default-features = false }
tabled = { version = "0.12.2", features = ["color"], default-features = false }
terminal_size = "0.2"
titlecase = "2.0"
toml = "0.7"

View File

@ -6,13 +6,12 @@ use nu_engine::{env::get_config, env_to_string, CallExt};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Config, DataSource, Example, IntoPipelineData, ListStream, PipelineData,
Category, Config, DataSource, Example, FooterMode, IntoPipelineData, ListStream, PipelineData,
PipelineMetadata, RawStream, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use nu_table::common::create_nu_table_config;
use nu_table::{
CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, StringResult, TableOpts,
TableOutput,
BuildConfig, Cell, CollapsedTable, ExpandedTable, JustTable, NuTable, StringResult,
TableConfig, TableOutput, TableTheme,
};
use nu_utils::get_ls_colors;
use std::sync::Arc;
@ -362,8 +361,8 @@ fn handle_record(
let result = if cols.is_empty() {
create_empty_placeholder("record", term_width, engine_state, stack)
} else {
let opts = TableOpts::new(config, style_computer, ctrlc, span, 0, term_width);
let result = build_table_kv(cols, vals, table_view, opts, span)?;
let opts = BuildConfig::new(ctrlc, config, style_computer, span, term_width);
let result = build_table_kv(cols, vals, table_view, opts)?;
match result {
Some(output) => maybe_strip_color(output, config),
None => report_unsuccessful_output(ctrlc1, term_width),
@ -392,8 +391,7 @@ fn build_table_kv(
cols: Vec<String>,
vals: Vec<Value>,
table_view: TableView,
opts: TableOpts<'_>,
span: Span,
opts: BuildConfig<'_>,
) -> StringResult {
match table_view {
TableView::General => JustTable::kv_table(&cols, &vals, opts),
@ -406,6 +404,7 @@ fn build_table_kv(
ExpandedTable::new(limit, flatten, sep).build_map(&cols, &vals, opts)
}
TableView::Collapsed => {
let span = opts.span();
let value = Value::Record { cols, vals, span };
CollapsedTable::build(value, opts)
}
@ -415,20 +414,21 @@ fn build_table_kv(
fn build_table_batch(
vals: Vec<Value>,
table_view: TableView,
opts: TableOpts<'_>,
span: Span,
row_offset: usize,
opts: BuildConfig<'_>,
) -> StringResult {
match table_view {
TableView::General => JustTable::table(&vals, opts),
TableView::General => JustTable::table(&vals, row_offset, opts),
TableView::Expanded {
limit,
flatten,
flatten_separator,
} => {
let sep = flatten_separator.unwrap_or_else(|| String::from(' '));
ExpandedTable::new(limit, flatten, sep).build_list(&vals, opts)
ExpandedTable::new(limit, flatten, sep).build_list(&vals, opts, row_offset)
}
TableView::Collapsed => {
let span = opts.span();
let value = Value::List { vals, span };
CollapsedTable::build(value, opts)
}
@ -647,16 +647,20 @@ impl PagingTableCreator {
return Ok(None);
}
let cfg = get_config(&self.engine_state, &self.stack);
let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack);
let opts = self.create_table_opts(&cfg, &style_comp);
let config = get_config(&self.engine_state, &self.stack);
let style_computer = StyleComputer::from_config(&self.engine_state, &self.stack);
let term_width = get_width_param(self.width_param);
let ctrlc = self.ctrlc.clone();
let span = self.head;
let opts = BuildConfig::new(ctrlc, &config, &style_computer, span, term_width);
let view = TableView::Expanded {
limit,
flatten,
flatten_separator,
};
build_table_batch(batch, view, opts, self.head)
build_table_batch(batch, view, self.row_offset, opts)
}
fn build_collapsed(&mut self, batch: Vec<Value>) -> StringResult {
@ -664,34 +668,26 @@ impl PagingTableCreator {
return Ok(None);
}
let cfg = get_config(&self.engine_state, &self.stack);
let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack);
let opts = self.create_table_opts(&cfg, &style_comp);
let config = get_config(&self.engine_state, &self.stack);
let style_computer = StyleComputer::from_config(&self.engine_state, &self.stack);
let term_width = get_width_param(self.width_param);
let ctrlc = self.ctrlc.clone();
let span = self.head;
let opts = BuildConfig::new(ctrlc, &config, &style_computer, span, term_width);
build_table_batch(batch, TableView::Collapsed, opts, self.head)
build_table_batch(batch, TableView::Collapsed, self.row_offset, opts)
}
fn build_general(&mut self, batch: Vec<Value>) -> StringResult {
let cfg = get_config(&self.engine_state, &self.stack);
let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack);
let opts = self.create_table_opts(&cfg, &style_comp);
let term_width = get_width_param(self.width_param);
let config = get_config(&self.engine_state, &self.stack);
let style_computer = StyleComputer::from_config(&self.engine_state, &self.stack);
let ctrlc = self.ctrlc.clone();
let span = self.head;
let row_offset = self.row_offset;
let opts = BuildConfig::new(ctrlc, &config, &style_computer, span, term_width);
build_table_batch(batch, TableView::General, opts, self.head)
}
fn create_table_opts<'a>(
&self,
cfg: &'a Config,
style_comp: &'a StyleComputer<'a>,
) -> TableOpts<'a> {
TableOpts::new(
cfg,
style_comp,
self.ctrlc.clone(),
self.head,
self.row_offset,
get_width_param(self.width_param),
)
build_table_batch(batch, TableView::General, row_offset, opts)
}
}
@ -784,6 +780,22 @@ impl Iterator for PagingTableCreator {
}
}
fn load_theme_from_config(config: &Config) -> TableTheme {
match config.table_mode.as_str() {
"basic" => TableTheme::basic(),
"thin" => TableTheme::thin(),
"light" => TableTheme::light(),
"compact" => TableTheme::compact(),
"with_love" => TableTheme::with_love(),
"compact_double" => TableTheme::compact_double(),
"rounded" => TableTheme::rounded(),
"reinforced" => TableTheme::reinforced(),
"heavy" => TableTheme::heavy(),
"none" => TableTheme::none(),
_ => TableTheme::rounded(),
}
}
fn render_path_name(
path: &str,
config: &Config,
@ -847,6 +859,34 @@ fn maybe_strip_color(output: String, config: &Config) -> String {
}
}
fn create_table_config(config: &Config, comp: &StyleComputer, out: &TableOutput) -> TableConfig {
let theme = load_theme_from_config(config);
let footer = with_footer(config, out.with_header, out.table.count_rows());
let line_style = lookup_separator_color(comp);
let trim = config.trim_strategy.clone();
TableConfig::new()
.theme(theme)
.with_footer(footer)
.with_header(out.with_header)
.with_index(out.with_index)
.line_style(line_style)
.trim(trim)
}
fn lookup_separator_color(style_computer: &StyleComputer) -> nu_ansi_term::Style {
style_computer.compute("separator", &Value::nothing(Span::unknown()))
}
fn with_footer(config: &Config, with_header: bool, count_records: usize) -> bool {
with_header && need_footer(config, count_records as u64)
}
fn need_footer(config: &Config, count_records: u64) -> bool {
matches!(config.footer_mode, FooterMode::RowCount(limit) if count_records > limit)
|| matches!(config.footer_mode, FooterMode::Always)
}
fn create_empty_placeholder(
value_type_name: &str,
termwidth: usize,
@ -858,14 +898,14 @@ fn create_empty_placeholder(
return String::new();
}
let cell = NuTableCell::new(format!("empty {}", value_type_name));
let cell = Cell::new(format!("empty {}", value_type_name));
let data = vec![vec![cell]];
let mut table = NuTable::from(data);
table.set_data_style(TextStyle::default().dimmed());
table.set_cell_style((0, 0), TextStyle::default().dimmed());
let out = TableOutput::new(table, false, false);
let style_computer = &StyleComputer::from_config(engine_state, stack);
let config = create_nu_table_config(&config, style_computer, &out, false);
let config = create_table_config(&config, style_computer, &out);
out.table
.draw(config, termwidth)

View File

@ -1,9 +1,6 @@
use nu_color_config::StyleComputer;
use nu_protocol::{Span, Value};
use nu_table::{
common::{nu_value_to_string, nu_value_to_string_clean},
ExpandedTable, TableOpts,
};
use nu_table::{value_to_clean_styled_string, value_to_styled_string, BuildConfig, ExpandedTable};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
@ -21,9 +18,9 @@ pub fn try_build_table(
try_build_map(cols, vals, span, style_computer, ctrlc, config)
}
val if matches!(val, Value::String { .. }) => {
nu_value_to_string_clean(&val, config, style_computer).0
value_to_clean_styled_string(&val, config, style_computer).0
}
val => nu_value_to_string(&val, config, style_computer).0,
val => value_to_styled_string(&val, config, style_computer).0,
}
}
@ -35,19 +32,12 @@ fn try_build_map(
ctrlc: Option<Arc<AtomicBool>>,
config: &NuConfig,
) -> String {
let opts = TableOpts::new(
config,
style_computer,
ctrlc,
Span::unknown(),
0,
usize::MAX,
);
let opts = BuildConfig::new(ctrlc, config, style_computer, Span::unknown(), usize::MAX);
let result = ExpandedTable::new(None, false, String::new()).build_map(&cols, &vals, opts);
match result {
Ok(Some(result)) => result,
Ok(None) | Err(_) => {
nu_value_to_string(&Value::Record { cols, vals, span }, config, style_computer).0
value_to_styled_string(&Value::Record { cols, vals, span }, config, style_computer).0
}
}
}
@ -59,20 +49,13 @@ fn try_build_list(
span: Span,
style_computer: &StyleComputer,
) -> String {
let opts = TableOpts::new(
config,
style_computer,
ctrlc,
Span::unknown(),
0,
usize::MAX,
);
let result = ExpandedTable::new(None, false, String::new()).build_list(&vals, opts);
let opts = BuildConfig::new(ctrlc, config, style_computer, Span::unknown(), usize::MAX);
let result = ExpandedTable::new(None, false, String::new()).build_list(&vals, opts, 0);
match result {
Ok(Some(out)) => out,
Ok(None) | Err(_) => {
// it means that the list is empty
nu_value_to_string(&Value::List { vals, span }, config, style_computer).0
value_to_styled_string(&Value::List { vals, span }, config, style_computer).0
}
}
}

View File

@ -70,7 +70,6 @@ pub struct Config {
pub external_completer: Option<usize>,
pub filesize_metric: bool,
pub table_mode: String,
pub table_move_header: bool,
pub table_show_empty: bool,
pub use_ls_colors: bool,
pub color_config: HashMap<String, Value>,
@ -127,7 +126,6 @@ impl Default for Config {
table_index_mode: TableIndexMode::Always,
table_show_empty: true,
trim_strategy: TRIM_STRATEGY_DEFAULT,
table_move_header: false,
datetime_normal_format: None,
datetime_table_format: None,
@ -928,9 +926,6 @@ impl Value {
Value::string(config.table_mode.clone(), span);
}
}
"header_on_separator" => {
try_bool!(cols, vals, index, span, table_move_header)
}
"index_mode" => {
if let Ok(b) = value.as_string() {
let val_str = b.to_lowercase();

View File

@ -16,7 +16,7 @@ nu-utils = { path = "../nu-utils", version = "0.83.2" }
nu-engine = { path = "../nu-engine", version = "0.83.2" }
nu-color-config = { path = "../nu-color-config", version = "0.83.2" }
nu-ansi-term = "0.49.0"
tabled = { git = "https://github.com/zhiburt/tabled/", rev = "6c51e3eaa362914a71b868ccb78e7addbebd3657", features = ["color"], default-features = false }
tabled = { version = "0.12.2", features = ["color"], default-features = false }
[dev-dependencies]
# nu-test-support = { path="../nu-test-support", version = "0.83.2" }

View File

@ -1,6 +1,6 @@
use nu_ansi_term::{Color, Style};
use nu_color_config::TextStyle;
use nu_table::{NuTable, NuTableConfig, TableTheme};
use nu_table::{NuTable, TableConfig, TableTheme};
use tabled::grid::records::vec_records::CellInfo;
fn main() {
@ -29,11 +29,8 @@ fn main() {
table.set_data_style(TextStyle::basic_left());
table.set_header_style(TextStyle::basic_center().style(Style::new().on(Color::Blue)));
let table_cfg = NuTableConfig {
theme: TableTheme::rounded(),
with_header: true,
..Default::default()
};
let theme = TableTheme::rounded();
let table_cfg = TableConfig::new().theme(theme).with_header(true);
let output_table = table
.draw(table_cfg, width)

View File

@ -1,175 +0,0 @@
use nu_color_config::{Alignment, StyleComputer, TextStyle};
use nu_protocol::TrimStrategy;
use nu_protocol::{Config, FooterMode, ShellError, Span, Value};
use crate::{clean_charset, string_wrap, NuTableConfig, TableOutput, TableTheme};
pub type NuText = (String, TextStyle);
pub type TableResult = Result<Option<TableOutput>, ShellError>;
pub type StringResult = Result<Option<String>, ShellError>;
pub const INDEX_COLUMN_NAME: &str = "index";
pub fn create_nu_table_config(
config: &Config,
comp: &StyleComputer,
out: &TableOutput,
expand: bool,
) -> NuTableConfig {
NuTableConfig {
theme: load_theme_from_config(config),
with_footer: with_footer(config, out.with_header, out.table.count_rows()),
with_index: out.with_index,
with_header: out.with_header,
split_color: Some(lookup_separator_color(comp)),
trim: config.trim_strategy.clone(),
header_on_border: config.table_move_header,
expand,
}
}
pub fn nu_value_to_string(val: &Value, cfg: &Config, style: &StyleComputer) -> NuText {
let float_precision = cfg.float_precision as usize;
let text = val.into_abbreviated_string(cfg);
make_styled_string(style, text, Some(val), float_precision)
}
pub fn nu_value_to_string_clean(val: &Value, cfg: &Config, style: &StyleComputer) -> NuText {
let (text, style) = nu_value_to_string(val, cfg, style);
let text = clean_charset(&text);
(text, style)
}
pub fn error_sign(style_computer: &StyleComputer) -> (String, TextStyle) {
make_styled_string(style_computer, String::from(""), None, 0)
}
pub fn wrap_text(text: &str, width: usize, config: &Config) -> String {
string_wrap(text, width, is_cfg_trim_keep_words(config))
}
pub fn get_header_style(style_computer: &StyleComputer) -> TextStyle {
TextStyle::with_style(
Alignment::Center,
style_computer.compute("header", &Value::string("", Span::unknown())),
)
}
pub fn get_index_style(style_computer: &StyleComputer) -> TextStyle {
TextStyle::with_style(
Alignment::Right,
style_computer.compute("row_index", &Value::string("", Span::unknown())),
)
}
pub fn get_value_style(value: &Value, config: &Config, style_computer: &StyleComputer) -> NuText {
match value {
// Float precision is required here.
Value::Float { val, .. } => (
format!("{:.prec$}", val, prec = config.float_precision as usize),
style_computer.style_primitive(value),
),
_ => (
value.into_abbreviated_string(config),
style_computer.style_primitive(value),
),
}
}
pub fn get_empty_style(style_computer: &StyleComputer) -> NuText {
(
String::from(""),
TextStyle::with_style(
Alignment::Right,
style_computer.compute("empty", &Value::nothing(Span::unknown())),
),
)
}
fn make_styled_string(
style_computer: &StyleComputer,
text: String,
value: Option<&Value>, // None represents table holes.
float_precision: usize,
) -> NuText {
match value {
Some(value) => {
match value {
Value::Float { .. } => {
// set dynamic precision from config
let precise_number = match convert_with_precision(&text, float_precision) {
Ok(num) => num,
Err(e) => e.to_string(),
};
(precise_number, style_computer.style_primitive(value))
}
_ => (text, style_computer.style_primitive(value)),
}
}
None => {
// Though holes are not the same as null, the closure for "empty" is passed a null anyway.
(
text,
TextStyle::with_style(
Alignment::Center,
style_computer.compute("empty", &Value::nothing(Span::unknown())),
),
)
}
}
}
fn convert_with_precision(val: &str, precision: usize) -> Result<String, ShellError> {
// vall will always be a f64 so convert it with precision formatting
let val_float = match val.trim().parse::<f64>() {
Ok(f) => f,
Err(e) => {
return Err(ShellError::GenericError(
format!("error converting string [{}] to f64", &val),
"".to_string(),
None,
Some(e.to_string()),
Vec::new(),
));
}
};
Ok(format!("{val_float:.precision$}"))
}
fn is_cfg_trim_keep_words(config: &Config) -> bool {
matches!(
config.trim_strategy,
TrimStrategy::Wrap {
try_to_keep_words: true
}
)
}
pub fn load_theme_from_config(config: &Config) -> TableTheme {
match config.table_mode.as_str() {
"basic" => TableTheme::basic(),
"thin" => TableTheme::thin(),
"light" => TableTheme::light(),
"compact" => TableTheme::compact(),
"with_love" => TableTheme::with_love(),
"compact_double" => TableTheme::compact_double(),
"rounded" => TableTheme::rounded(),
"reinforced" => TableTheme::reinforced(),
"heavy" => TableTheme::heavy(),
"none" => TableTheme::none(),
_ => TableTheme::rounded(),
}
}
fn lookup_separator_color(style_computer: &StyleComputer) -> nu_ansi_term::Style {
style_computer.compute("separator", &Value::nothing(Span::unknown()))
}
fn with_footer(config: &Config, with_header: bool, count_records: usize) -> bool {
with_header && need_footer(config, count_records as u64)
}
fn need_footer(config: &Config, count_records: u64) -> bool {
matches!(config.footer_mode, FooterMode::RowCount(limit) if count_records > limit)
|| matches!(config.footer_mode, FooterMode::Always)
}

View File

@ -4,12 +4,12 @@ mod types;
mod unstructured_table;
mod util;
pub mod common;
pub use common::{StringResult, TableResult};
pub use nu_color_config::TextStyle;
pub use table::{NuTable, NuTableCell, NuTableConfig};
pub use table::{Alignments, Cell, NuTable, TableConfig};
pub use table_theme::TableTheme;
pub use types::{CollapsedTable, ExpandedTable, JustTable, TableOpts, TableOutput};
pub use types::{
clean_charset, value_to_clean_styled_string, value_to_styled_string, BuildConfig,
CollapsedTable, ExpandedTable, JustTable, NuText, StringResult, TableOutput, TableResult,
};
pub use unstructured_table::UnstructuredTable;
pub use util::*;

View File

@ -12,27 +12,25 @@ use tabled::{
dimension::CompleteDimensionVecRecords,
records::{
vec_records::{CellInfo, VecRecords},
ExactRecords, PeekableRecords, Records, Resizable,
ExactRecords, Records,
},
},
settings::{
formatting::AlignmentStrategy, object::Segment, peaker::Peaker, themes::ColumnNames, Color,
Modify, Settings, TableOption, Width,
formatting::AlignmentStrategy, object::Segment, peaker::Peaker, Color, Modify, Settings,
TableOption, Width,
},
Table,
};
/// NuTable is a table rendering implementation.
/// Table represent a table view.
#[derive(Debug, Clone)]
pub struct NuTable {
data: NuTableData,
data: Data,
styles: Styles,
alignments: Alignments,
size: (usize, usize),
}
type NuTableData = VecRecords<NuTableCell>;
pub type NuTableCell = CellInfo<String>;
#[derive(Debug, Default, Clone)]
struct Styles {
index: AnsiColor<'static>,
@ -41,39 +39,27 @@ struct Styles {
data_is_set: bool,
}
#[derive(Debug, Clone)]
struct Alignments {
data: AlignmentHorizontal,
index: AlignmentHorizontal,
header: AlignmentHorizontal,
columns: HashMap<usize, AlignmentHorizontal>,
cells: HashMap<Position, AlignmentHorizontal>,
}
type Data = VecRecords<Cell>;
pub type Cell = CellInfo<String>;
impl NuTable {
/// Creates an empty [Table] instance.
pub fn new(count_rows: usize, count_columns: usize) -> Self {
let data = VecRecords::new(vec![vec![CellInfo::default(); count_columns]; count_rows]);
Self {
data: VecRecords::new(vec![vec![CellInfo::default(); count_columns]; count_rows]),
data,
size: (count_rows, count_columns),
styles: Styles::default(),
alignments: Alignments {
data: AlignmentHorizontal::Left,
index: AlignmentHorizontal::Right,
header: AlignmentHorizontal::Center,
columns: HashMap::default(),
cells: HashMap::default(),
},
alignments: Alignments::default(),
}
}
/// Return amount of rows.
pub fn count_rows(&self) -> usize {
self.data.count_rows()
self.size.0
}
/// Return amount of columns.
pub fn count_columns(&self) -> usize {
self.data.count_columns()
self.size.1
}
pub fn insert(&mut self, pos: Position, text: String) {
@ -93,7 +79,7 @@ impl NuTable {
}
}
pub fn insert_style(&mut self, pos: Position, style: TextStyle) {
pub fn set_cell_style(&mut self, pos: Position, style: TextStyle) {
if let Some(style) = style.color_style {
let style = AnsiColor::from(convert_style(style));
self.styles.data.insert(Entity::Cell(pos.0, pos.1), style);
@ -137,12 +123,12 @@ impl NuTable {
/// Converts a table to a String.
///
/// It returns None in case where table cannot be fit to a terminal width.
pub fn draw(self, config: NuTableConfig, termwidth: usize) -> Option<String> {
pub fn draw(self, config: TableConfig, termwidth: usize) -> Option<String> {
build_table(self.data, config, self.alignments, self.styles, termwidth)
}
/// Return a total table width.
pub fn total_width(&self, config: &NuTableConfig) -> usize {
pub fn total_width(&self, config: &TableConfig) -> usize {
let config = get_config(&config.theme, false, None);
let widths = build_width(&self.data);
get_total_width2(&widths, &config)
@ -151,43 +137,107 @@ impl NuTable {
impl From<Vec<Vec<CellInfo<String>>>> for NuTable {
fn from(value: Vec<Vec<CellInfo<String>>>) -> Self {
let mut nutable = Self::new(0, 0);
nutable.data = VecRecords::new(value);
nutable
let data = VecRecords::new(value);
let size = (data.count_rows(), data.count_columns());
Self {
data,
size,
alignments: Alignments::default(),
styles: Styles::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct NuTableConfig {
pub theme: TableTheme,
pub trim: TrimStrategy,
pub split_color: Option<Style>,
pub expand: bool,
pub with_index: bool,
pub with_header: bool,
pub with_footer: bool,
pub header_on_border: bool,
pub struct TableConfig {
theme: TableTheme,
trim: TrimStrategy,
split_color: Option<Style>,
expand: bool,
with_index: bool,
with_header: bool,
with_footer: bool,
}
impl Default for NuTableConfig {
fn default() -> Self {
impl TableConfig {
pub fn new() -> Self {
Self {
theme: TableTheme::basic(),
trim: TrimStrategy::truncate(None),
with_header: false,
with_index: false,
with_footer: false,
expand: false,
trim: TrimStrategy::truncate(None),
split_color: None,
header_on_border: false,
}
}
pub fn expand(mut self, on: bool) -> Self {
self.expand = on;
self
}
pub fn trim(mut self, strategy: TrimStrategy) -> Self {
self.trim = strategy;
self
}
pub fn line_style(mut self, color: Style) -> Self {
self.split_color = Some(color);
self
}
pub fn with_header(mut self, on: bool) -> Self {
self.with_header = on;
self
}
pub fn with_footer(mut self, on: bool) -> Self {
self.with_footer = on;
self
}
pub fn with_index(mut self, on: bool) -> Self {
self.with_index = on;
self
}
pub fn theme(mut self, theme: TableTheme) -> Self {
self.theme = theme;
self
}
}
impl Default for TableConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Alignments {
data: AlignmentHorizontal,
index: AlignmentHorizontal,
header: AlignmentHorizontal,
columns: HashMap<usize, AlignmentHorizontal>,
cells: HashMap<Position, AlignmentHorizontal>,
}
impl Default for Alignments {
fn default() -> Self {
Self {
data: AlignmentHorizontal::Left,
index: AlignmentHorizontal::Right,
header: AlignmentHorizontal::Center,
columns: HashMap::default(),
cells: HashMap::default(),
}
}
}
fn build_table(
mut data: NuTableData,
cfg: NuTableConfig,
mut data: Data,
cfg: TableConfig,
alignments: Alignments,
styles: Styles,
termwidth: usize,
@ -209,24 +259,22 @@ fn build_table(
}
fn draw_table(
data: NuTableData,
mut alignments: Alignments,
mut styles: Styles,
data: Data,
alignments: Alignments,
styles: Styles,
widths: Vec<usize>,
cfg: NuTableConfig,
cfg: TableConfig,
termwidth: usize,
) -> Option<String> {
let data: Vec<Vec<_>> = data.into();
let mut table = Builder::from(data).build();
let need_header_move = cfg.header_on_border && has_horizontals_for_header(&cfg);
let with_footer = cfg.with_footer;
let with_index = cfg.with_index;
let with_header = cfg.with_header && table.count_rows() > 1 && !need_header_move;
let with_footer = cfg.with_footer && !need_header_move;
let with_header = cfg.with_header && table.count_rows() > 1;
let sep_color = cfg.split_color;
load_theme(&mut table, &cfg.theme, with_footer, with_header, sep_color);
move_header_on_border(&mut table, &mut alignments, &mut styles, &cfg);
align_table(&mut table, alignments, with_index, with_header, with_footer);
colorize_table(&mut table, styles, with_index, with_header, with_footer);
@ -253,55 +301,6 @@ fn draw_table(
}
}
fn move_header_on_border(
table: &mut Table,
alignments: &mut Alignments,
styles: &mut Styles,
cfg: &NuTableConfig,
) {
if !cfg.header_on_border || table.count_rows() <= 1 {
return;
}
let color = Color::from(styles.header.clone());
let has_bottom_line = table
.get_config()
.has_horizontal(table.count_rows(), table.count_rows());
let has_top_line = table.get_config().has_horizontal(0, table.count_rows());
if cfg.with_header && has_top_line {
move_row_on_border(table, 0, color.clone(), alignments.header)
}
if cfg.with_footer && has_bottom_line {
let last_row = table.count_rows() - 1;
move_row_on_border(table, last_row, color, alignments.header)
}
// because we remove rows we will invalidate the data alignments and colors
// so we need to restore it back
if cfg.with_header && has_top_line {
if !alignments.cells.is_empty() {
for row in 1..table.count_rows() {
for col in 0..table.count_rows() {
let val = alignments.cells.get(&(row, col));
if let Some(val) = val {
alignments.cells.insert((row - 1, col), *val);
}
}
}
}
for row in 1..table.count_rows() {
for col in 0..table.count_rows() {
let val = styles.data.get(Entity::Cell(row, col)).clone();
styles.data.insert(Entity::Cell(row - 1, col), val);
}
}
}
}
fn align_table(
table: &mut Table,
alignments: Alignments,
@ -430,11 +429,7 @@ fn table_trim_columns(
}
}
fn maybe_truncate_columns(
data: &mut NuTableData,
theme: &TableTheme,
termwidth: usize,
) -> Vec<usize> {
fn maybe_truncate_columns(data: &mut Data, theme: &TableTheme, termwidth: usize) -> Vec<usize> {
const TERMWIDTH_THRESHOLD: usize = 120;
let truncate = if termwidth > TERMWIDTH_THRESHOLD {
@ -448,7 +443,7 @@ fn maybe_truncate_columns(
// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE.
fn truncate_columns_by_content(
data: &mut NuTableData,
data: &mut Data,
theme: &TableTheme,
termwidth: usize,
) -> Vec<usize> {
@ -527,7 +522,7 @@ fn truncate_columns_by_content(
// VERSION where we are showing AS MANY COLUMNS AS POSSIBLE but as a side affect they MIGHT CONTAIN AS LITTLE CONTENT AS POSSIBLE
fn truncate_columns_by_columns(
data: &mut NuTableData,
data: &mut Data,
theme: &TableTheme,
termwidth: usize,
) -> Vec<usize> {
@ -625,7 +620,7 @@ fn get_config(theme: &TableTheme, with_header: bool, color: Option<Style>) -> Co
table.get_config().clone()
}
fn push_empty_column(data: &mut NuTableData) {
fn push_empty_column(data: &mut Data) {
let records = std::mem::take(data);
let mut inner: Vec<Vec<_>> = records.into();
@ -637,7 +632,7 @@ fn push_empty_column(data: &mut NuTableData) {
*data = VecRecords::new(inner);
}
fn duplicate_row(data: &mut NuTableData, row: usize) {
fn duplicate_row(data: &mut Data, row: usize) {
let records = std::mem::take(data);
let mut inner: Vec<Vec<_>> = records.into();
@ -647,7 +642,7 @@ fn duplicate_row(data: &mut NuTableData, row: usize) {
*data = VecRecords::new(inner);
}
fn truncate_columns(data: &mut NuTableData, count: usize) {
fn truncate_columns(data: &mut Data, count: usize) {
let records = std::mem::take(data);
let mut inner: Vec<Vec<_>> = records.into();
@ -702,39 +697,3 @@ fn build_width(records: &VecRecords<CellInfo<String>>) -> Vec<usize> {
widths
}
fn move_row_on_border(table: &mut Table, row: usize, color: Color, alignment: AlignmentHorizontal) {
if table.is_empty() {
return;
}
let columns = (0..table.get_records().count_columns())
.map(|column| table.get_records().get_text((row, column)).to_owned())
.collect::<Vec<_>>();
table.get_records_mut().remove_row(row);
if table.is_empty() {
table.get_records_mut().push_row();
}
let mut line = 0;
if row != 0 {
line = row;
}
let colors = std::iter::repeat(color)
.take(table.count_columns())
.collect::<Vec<_>>();
let names = ColumnNames::new(columns)
.set_line(line)
.set_colors(colors)
.set_alignment(alignment);
table.with(names);
}
fn has_horizontals_for_header(cfg: &NuTableConfig) -> bool {
cfg.theme.get_theme().get_horizontal(0).is_some()
|| cfg.theme.get_theme().get_borders().has_horizontal()
}

View File

@ -191,10 +191,6 @@ impl TableTheme {
self.has_inner
}
pub fn has_horizontals(&self) -> bool {
self.full_theme.get_borders().has_horizontal()
}
pub fn get_theme_full(&self) -> RawStyle {
self.full_theme.clone()
}

View File

@ -3,17 +3,16 @@ use nu_protocol::{Config, Span, Value};
use crate::UnstructuredTable;
use crate::common::nu_value_to_string_clean;
use crate::{
common::{get_index_style, load_theme_from_config},
StringResult, TableOpts,
use super::{
clean_charset, general::BuildConfig, get_index_style, load_theme_from_config,
value_to_styled_string, StringResult,
};
pub struct CollapsedTable;
impl CollapsedTable {
pub fn build(value: Value, opts: TableOpts<'_>) -> StringResult {
collapsed_table(value, opts.config, opts.width, opts.style_computer)
pub fn build(value: Value, opts: BuildConfig<'_>) -> StringResult {
collapsed_table(value, opts.config, opts.term_width, opts.style_computer)
}
}
@ -57,7 +56,20 @@ fn colorize_value(value: &mut Value, config: &Config, style_computer: &StyleComp
}
}
value => {
let (text, style) = nu_value_to_string_clean(value, config, style_computer);
let (text, style) = value_to_styled_string(value, config, style_computer);
let is_string = matches!(value, Value::String { .. });
if is_string {
let mut text = clean_charset(&text);
if let Some(color) = style.color_style {
text = color.paint(text).to_string();
}
let span = value.span().unwrap_or(Span::unknown());
*value = Value::string(text, span);
return;
}
if let Some(color) = style.color_style {
let text = color.paint(text).to_string();
let span = value.span().unwrap_or(Span::unknown());

View File

@ -1,18 +1,17 @@
use std::cmp::max;
use std::collections::HashMap;
use nu_color_config::{Alignment, StyleComputer, TextStyle};
use nu_engine::column::get_columns;
use nu_protocol::{ast::PathMember, Config, ShellError, Span, TableIndexMode, Value};
use tabled::grid::config::Position;
use nu_protocol::{ast::PathMember, Config, Span, TableIndexMode, Value};
use std::collections::HashMap;
use std::sync::Arc;
use std::{cmp::max, sync::atomic::AtomicBool};
use crate::{
common::{
create_nu_table_config, error_sign, get_header_style, get_index_style,
load_theme_from_config, nu_value_to_string, nu_value_to_string_clean, wrap_text, NuText,
StringResult, TableResult, INDEX_COLUMN_NAME,
},
string_width, NuTable, NuTableCell, TableOpts, TableOutput,
use crate::{string_width, Cell, NuTable};
use super::{clean_charset, value_to_clean_styled_string};
use super::{
create_table_config, error_sign, general::BuildConfig, get_header_style, get_index_style,
load_theme_from_config, set_data_styles, value_to_styled_string, wrap_text, NuText,
StringResult, TableOutput, TableResult, INDEX_COLUMN_NAME,
};
#[derive(Debug, Clone)]
@ -31,35 +30,69 @@ impl ExpandedTable {
}
}
pub fn build_value(self, item: &Value, opts: TableOpts<'_>) -> NuText {
expanded_table_entry2(item, Cfg { opts, format: self })
}
pub fn build_map(self, cols: &[String], vals: &[Value], opts: TableOpts<'_>) -> StringResult {
expanded_table_kv(cols, vals, Cfg { opts, format: self })
}
pub fn build_list(self, vals: &[Value], opts: TableOpts<'_>) -> StringResult {
let cfg = Cfg {
opts: opts.clone(),
format: self,
pub fn build_value(&self, item: &Value, opts: BuildConfig<'_>) -> NuText {
let opts = Options {
ctrlc: opts.ctrlc,
config: opts.config,
style_computer: opts.style_computer,
available_width: opts.term_width,
span: opts.span,
format: self.clone(),
};
let out = match expanded_table_list(vals, cfg)? {
expanded_table_entry2(item, opts)
}
pub fn build_map(
&self,
cols: &[String],
vals: &[Value],
opts: BuildConfig<'_>,
) -> StringResult {
let opts = Options {
ctrlc: opts.ctrlc,
config: opts.config,
style_computer: opts.style_computer,
available_width: opts.term_width,
span: opts.span,
format: self.clone(),
};
expanded_table_kv(cols, vals, opts)
}
pub fn build_list(
&self,
vals: &[Value],
opts: BuildConfig<'_>,
row_offset: usize,
) -> StringResult {
let opts1 = Options {
ctrlc: opts.ctrlc,
config: opts.config,
style_computer: opts.style_computer,
available_width: opts.term_width,
span: opts.span,
format: self.clone(),
};
let out = match expanded_table_list(vals, row_offset, opts1)? {
Some(out) => out,
None => return Ok(None),
};
maybe_expand_table(out, opts.width, &opts)
maybe_expand_table(out, opts.term_width, opts.config, opts.style_computer)
}
}
#[derive(Debug, Clone)]
struct Cfg<'a> {
opts: TableOpts<'a>,
struct Options<'a> {
ctrlc: Option<Arc<AtomicBool>>,
config: &'a Config,
style_computer: &'a StyleComputer<'a>,
available_width: usize,
format: ExpandedTable,
span: Span,
}
fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> TableResult {
const PADDING_SPACE: usize = 2;
const SPLIT_LINE_SPACE: usize = 1;
const ADDITIONAL_CELL_SPACE: usize = PADDING_SPACE + SPLIT_LINE_SPACE;
@ -72,9 +105,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
}
// 2 - split lines
let mut available_width = cfg
.opts
.width
let mut available_width = opts
.available_width
.saturating_sub(SPLIT_LINE_SPACE + SPLIT_LINE_SPACE);
if available_width < MIN_CELL_CONTENT_WIDTH {
return Ok(None);
@ -82,7 +114,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
let headers = get_columns(input);
let with_index = match cfg.opts.config.table_index_mode {
let with_index = match opts.config.table_index_mode {
TableIndexMode::Always => true,
TableIndexMode::Never => false,
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
@ -102,11 +134,11 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
if with_index {
if with_header {
data[0].push(NuTableCell::exact(String::from("#"), 1, vec![]));
data[0].push(Cell::exact(String::from("#"), 1, vec![]));
}
for (row, item) in input.iter().enumerate() {
if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) {
if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) {
return Ok(None);
}
@ -114,15 +146,14 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
return Err(*error.clone());
}
let index = row + cfg.opts.row_offset;
let index = row + row_offset;
let text = matches!(item, Value::Record { .. })
.then(|| {
lookup_index_value(item, cfg.opts.config).unwrap_or_else(|| index.to_string())
})
.then(|| lookup_index_value(item, opts.config).unwrap_or_else(|| index.to_string()))
.unwrap_or_else(|| index.to_string());
let value = Cell::new(text);
let row = row + with_header as usize;
let value = NuTableCell::new(text);
data[row].push(value);
}
@ -146,7 +177,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
}
for (row, item) in input.iter().enumerate() {
if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) {
if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) {
return Ok(None);
}
@ -154,9 +185,9 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
return Err(*error.clone());
}
let mut inner_cfg = cfg.clone();
inner_cfg.opts.width = available_width;
let (mut text, style) = expanded_table_entry2(item, inner_cfg);
let mut oopts = opts.clone();
oopts.available_width = available_width;
let (mut text, style) = expanded_table_entry2(item, oopts.clone());
let value_width = string_width(&text);
if value_width > available_width {
@ -165,16 +196,16 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
//
// todo: Maybe convert_to_table2_entry could do for strings to not mess caller code?
text = wrap_text(&text, available_width, cfg.opts.config);
text = wrap_text(&text, available_width, opts.config);
}
let value = NuTableCell::new(text);
let value = Cell::new(text);
data[row].push(value);
data_styles.insert((row, with_index as usize), style);
}
let mut table = NuTable::from(data);
table.set_index_style(get_index_style(cfg.opts.style_computer));
table.set_index_style(get_index_style(opts.style_computer));
set_data_styles(&mut table, data_styles);
return Ok(Some(TableOutput::new(table, false, with_index)));
@ -233,7 +264,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
}
for (row, item) in input.iter().enumerate() {
if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) {
if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) {
return Ok(None);
}
@ -241,27 +272,27 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
return Err(*error.clone());
}
let mut inner_cfg = cfg.clone();
inner_cfg.opts.width = available;
let (mut text, style) = expanded_table_entry(item, header.as_str(), inner_cfg);
let mut oopts = opts.clone();
oopts.available_width = available;
let (mut text, style) = expanded_table_entry(item, header.as_str(), oopts);
let mut value_width = string_width(&text);
if value_width > available {
// it must only happen when a string is produced, so we can safely wrap it.
// (it might be string table representation as well)
text = wrap_text(&text, available, cfg.opts.config);
text = wrap_text(&text, available, opts.config);
value_width = available;
}
column_width = max(column_width, value_width);
let value = NuTableCell::new(text);
let value = Cell::new(text);
data[row + 1].push(value);
data_styles.insert((row + 1, col + with_index as usize), style);
}
let head_cell = NuTableCell::new(header);
let head_cell = Cell::new(header);
data[0].push(head_cell);
if column_width > available {
@ -321,7 +352,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
let is_last_column = widths.len() == count_columns;
if !is_last_column {
let shift = NuTableCell::exact(String::from("..."), 3, vec![]);
let shift = Cell::exact(String::from("..."), 3, vec![]);
for row in &mut data {
row.push(shift.clone());
}
@ -331,34 +362,92 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
}
let mut table = NuTable::from(data);
table.set_index_style(get_index_style(cfg.opts.style_computer));
table.set_header_style(get_header_style(cfg.opts.style_computer));
table.set_index_style(get_index_style(opts.style_computer));
table.set_header_style(get_header_style(opts.style_computer));
set_data_styles(&mut table, data_styles);
Ok(Some(TableOutput::new(table, true, with_index)))
}
fn expanded_table_kv(cols: &[String], vals: &[Value], cfg: Cfg<'_>) -> StringResult {
let theme = load_theme_from_config(cfg.opts.config);
fn expanded_table_kv(cols: &[String], vals: &[Value], opts: Options<'_>) -> StringResult {
let theme = load_theme_from_config(opts.config);
let key_width = cols.iter().map(|col| string_width(col)).max().unwrap_or(0);
let count_borders =
theme.has_inner() as usize + theme.has_right() as usize + theme.has_left() as usize;
let padding = 2;
if key_width + count_borders + padding + padding > cfg.opts.width {
if key_width + count_borders + padding + padding > opts.available_width {
return Ok(None);
}
let value_width = cfg.opts.width - key_width - count_borders - padding - padding;
let value_width = opts.available_width - key_width - count_borders - padding - padding;
let mut data = Vec::with_capacity(cols.len());
for (key, value) in cols.iter().zip(vals) {
if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) {
if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) {
return Ok(None);
}
let (value, is_expanded) = match expand_table_value(value, value_width, &cfg)? {
Some(val) => val,
None => return Ok(None),
let is_limited = matches!(opts.format.expand_limit, Some(0));
let mut is_expanded = false;
let value = if is_limited {
let (text, _) = value_to_styled_string(value, opts.config, opts.style_computer);
clean_charset(&text)
} else {
match value {
Value::List { vals, span } => {
let mut oopts = dive_options(&opts, *span);
oopts.available_width = value_width;
let table = expanded_table_list(vals, 0, oopts)?;
match table {
Some(out) => {
is_expanded = true;
let table_config =
create_table_config(opts.config, opts.style_computer, &out);
let value = out.table.draw(table_config, value_width);
match value {
Some(result) => result,
None => return Ok(None),
}
}
None => {
// it means that the list is empty
let text =
value_to_styled_string(value, opts.config, opts.style_computer).0;
wrap_text(&text, value_width, opts.config)
}
}
}
Value::Record { cols, vals, span } => {
if cols.is_empty() {
// Like list case return styled string instead of empty value
let text =
value_to_styled_string(value, opts.config, opts.style_computer).0;
wrap_text(&text, value_width, opts.config)
} else {
let mut oopts = dive_options(&opts, *span);
oopts.available_width = value_width;
let result = expanded_table_kv(cols, vals, oopts)?;
match result {
Some(result) => {
is_expanded = true;
result
}
None => {
let failed_value =
value_to_styled_string(value, opts.config, opts.style_computer);
wrap_text(&failed_value.0, value_width, opts.config)
}
}
}
}
val => {
let text =
value_to_clean_styled_string(val, opts.config, opts.style_computer).0;
wrap_text(&text, value_width, opts.config)
}
}
};
// we want to have a key being aligned to 2nd line,
@ -369,154 +458,90 @@ fn expanded_table_kv(cols: &[String], vals: &[Value], cfg: Cfg<'_>) -> StringRes
key.insert(0, '\n');
}
let key = NuTableCell::new(key);
let val = NuTableCell::new(value);
let row = vec![key, val];
let key = Cell::new(key);
let val = Cell::new(value);
let row = vec![key, val];
data.push(row);
}
let mut table = NuTable::from(data);
table.set_index_style(get_key_style(&cfg));
let keys_style = get_header_style(opts.style_computer).alignment(Alignment::Left);
table.set_index_style(keys_style);
let out = TableOutput::new(table, false, true);
maybe_expand_table(out, cfg.opts.width, &cfg.opts)
maybe_expand_table(out, opts.available_width, opts.config, opts.style_computer)
}
// the flag is used as an optimization to not do `value.lines().count()` search.
fn expand_table_value(
value: &Value,
value_width: usize,
cfg: &Cfg<'_>,
) -> Result<Option<(String, bool)>, ShellError> {
let is_limited = matches!(cfg.format.expand_limit, Some(0));
if is_limited {
return Ok(Some((value_to_string_clean(value, cfg), false)));
}
match value {
Value::List { vals, span } => {
let mut inner_cfg = dive_options(cfg, *span);
inner_cfg.opts.width = value_width;
let table = expanded_table_list(vals, inner_cfg)?;
match table {
Some(out) => {
let cfg = create_table_cfg(cfg, &out);
let value = out.table.draw(cfg, value_width);
match value {
Some(result) => Ok(Some((result, true))),
None => Ok(None),
}
}
None => {
// it means that the list is empty
Ok(Some((
value_to_wrapped_string(value, cfg, value_width),
false,
)))
}
}
}
Value::Record { cols, vals, span } => {
if cols.is_empty() {
// Like list case return styled string instead of empty value
return Ok(Some((
value_to_wrapped_string(value, cfg, value_width),
false,
)));
}
let mut inner_cfg = dive_options(cfg, *span);
inner_cfg.opts.width = value_width;
let result = expanded_table_kv(cols, vals, inner_cfg)?;
match result {
Some(result) => Ok(Some((result, true))),
None => Ok(Some((
value_to_wrapped_string(value, cfg, value_width),
false,
))),
}
}
_ => Ok(Some((
value_to_wrapped_string_clean(value, cfg, value_width),
false,
))),
}
}
fn get_key_style(cfg: &Cfg<'_>) -> TextStyle {
get_header_style(cfg.opts.style_computer).alignment(Alignment::Left)
}
fn expanded_table_entry(item: &Value, header: &str, cfg: Cfg<'_>) -> NuText {
fn expanded_table_entry(item: &Value, header: &str, opts: Options<'_>) -> NuText {
match item {
Value::Record { .. } => {
let val = header.to_owned();
let path = PathMember::String {
val,
span: cfg.opts.span,
span: opts.span,
optional: false,
};
let val = item.clone().follow_cell_path(&[path], false);
match val {
Ok(val) => expanded_table_entry2(&val, cfg),
Err(_) => error_sign(cfg.opts.style_computer),
Ok(val) => expanded_table_entry2(&val, opts),
Err(_) => error_sign(opts.style_computer),
}
}
_ => expanded_table_entry2(item, cfg),
_ => expanded_table_entry2(item, opts),
}
}
fn expanded_table_entry2(item: &Value, cfg: Cfg<'_>) -> NuText {
let is_limit_reached = matches!(cfg.format.expand_limit, Some(0));
fn expanded_table_entry2(item: &Value, opts: Options<'_>) -> NuText {
let is_limit_reached = matches!(opts.format.expand_limit, Some(0));
if is_limit_reached {
return nu_value_to_string_clean(item, cfg.opts.config, cfg.opts.style_computer);
return value_to_clean_styled_string(item, opts.config, opts.style_computer);
}
match &item {
Value::Record { cols, vals, span } => {
if cols.is_empty() && vals.is_empty() {
return nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer);
return value_to_styled_string(item, opts.config, opts.style_computer);
}
// we verify what is the structure of a Record cause it might represent
let inner_cfg = dive_options(&cfg, *span);
let table = expanded_table_kv(cols, vals, inner_cfg);
let oopts = dive_options(&opts, *span);
let table = expanded_table_kv(cols, vals, oopts);
match table {
Ok(Some(table)) => (table, TextStyle::default()),
_ => nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer),
_ => value_to_styled_string(item, opts.config, opts.style_computer),
}
}
Value::List { vals, span } => {
if cfg.format.flatten && is_simple_list(vals) {
if opts.format.flatten && is_simple_list(vals) {
return value_list_to_string(
vals,
cfg.opts.config,
cfg.opts.style_computer,
&cfg.format.flatten_sep,
opts.config,
opts.style_computer,
&opts.format.flatten_sep,
);
}
let inner_cfg = dive_options(&cfg, *span);
let table = expanded_table_list(vals, inner_cfg);
let oopts = dive_options(&opts, *span);
let table = expanded_table_list(vals, 0, oopts);
let out = match table {
Ok(Some(out)) => out,
_ => return nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer),
_ => return value_to_styled_string(item, opts.config, opts.style_computer),
};
let table_config = create_table_cfg(&cfg, &out);
let table_config = create_table_config(opts.config, opts.style_computer, &out);
let table = out.table.draw(table_config, usize::MAX);
match table {
Some(table) => (table, TextStyle::default()),
None => nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer),
None => value_to_styled_string(item, opts.config, opts.style_computer),
}
}
_ => nu_value_to_string_clean(item, cfg.opts.config, cfg.opts.style_computer),
_ => value_to_clean_styled_string(item, opts.config, opts.style_computer),
}
}
@ -537,21 +562,21 @@ fn value_list_to_string(
buf.push_str(flatten_sep);
}
let text = nu_value_to_string_clean(value, config, style_computer).0;
let (text, _) = value_to_clean_styled_string(value, config, style_computer);
buf.push_str(&text);
}
(buf, TextStyle::default())
}
fn dive_options<'b>(cfg: &Cfg<'b>, span: Span) -> Cfg<'b> {
let mut cfg = cfg.clone();
cfg.opts.span = span;
if let Some(deep) = cfg.format.expand_limit.as_mut() {
fn dive_options<'b>(opts: &Options<'b>, span: Span) -> Options<'b> {
let mut opts = opts.clone();
opts.span = span;
if let Some(deep) = opts.format.expand_limit.as_mut() {
*deep -= 1
}
cfg
opts
}
fn lookup_index_value(item: &Value, config: &Config) -> Option<String> {
@ -559,47 +584,23 @@ fn lookup_index_value(item: &Value, config: &Config) -> Option<String> {
.map(|value| value.into_string("", config))
}
fn maybe_expand_table(out: TableOutput, term_width: usize, opts: &TableOpts<'_>) -> StringResult {
let mut table_config = create_nu_table_config(opts.config, opts.style_computer, &out, false);
fn maybe_expand_table(
out: TableOutput,
term_width: usize,
config: &Config,
style_computer: &StyleComputer,
) -> StringResult {
let mut table_config = create_table_config(config, style_computer, &out);
let total_width = out.table.total_width(&table_config);
if total_width < term_width {
const EXPAND_THRESHOLD: f32 = 0.80;
let used_percent = total_width as f32 / term_width as f32;
let need_expansion = total_width < term_width && used_percent > EXPAND_THRESHOLD;
if need_expansion {
table_config.expand = true;
table_config = table_config.expand(true);
}
}
Ok(out.table.draw(table_config, term_width))
}
fn set_data_styles(table: &mut NuTable, styles: HashMap<Position, TextStyle>) {
for (pos, style) in styles {
table.insert_style(pos, style);
}
}
fn create_table_cfg(cfg: &Cfg<'_>, out: &TableOutput) -> crate::NuTableConfig {
create_nu_table_config(cfg.opts.config, cfg.opts.style_computer, out, false)
}
fn value_to_string(value: &Value, cfg: &Cfg<'_>) -> String {
nu_value_to_string(value, cfg.opts.config, cfg.opts.style_computer).0
}
fn value_to_string_clean(value: &Value, cfg: &Cfg<'_>) -> String {
nu_value_to_string_clean(value, cfg.opts.config, cfg.opts.style_computer).0
}
fn value_to_wrapped_string(value: &Value, cfg: &Cfg<'_>, value_width: usize) -> String {
wrap_text(&value_to_string(value, cfg), value_width, cfg.opts.config)
}
fn value_to_wrapped_string_clean(value: &Value, cfg: &Cfg<'_>, value_width: usize) -> String {
wrap_text(
&value_to_string_clean(value, cfg),
value_width,
cfg.opts.config,
)
let output = out.table.draw(table_config, term_width);
Ok(output)
}

View File

@ -1,40 +1,84 @@
use nu_color_config::TextStyle;
use nu_color_config::{StyleComputer, TextStyle};
use nu_engine::column::get_columns;
use nu_protocol::{ast::PathMember, Config, ShellError, Span, TableIndexMode, Value};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use crate::{
clean_charset,
common::{
create_nu_table_config, get_empty_style, get_header_style, get_index_style,
get_value_style, NuText, INDEX_COLUMN_NAME,
},
NuTable, NuTableCell, StringResult, TableOpts, TableOutput, TableResult,
use crate::{Cell, NuTable, NuText};
use super::{
clean_charset, create_table_config, get_empty_style, get_header_style, get_index_style,
get_value_style, StringResult, TableOutput, TableResult, INDEX_COLUMN_NAME,
};
pub struct JustTable;
impl JustTable {
pub fn table(input: &[Value], opts: TableOpts<'_>) -> StringResult {
create_table(input, opts)
pub fn table(input: &[Value], row_offset: usize, opts: BuildConfig<'_>) -> StringResult {
let out = match table(input, row_offset, opts.clone())? {
Some(out) => out,
None => return Ok(None),
};
let table_config = create_table_config(opts.config, opts.style_computer, &out);
let table = out.table.draw(table_config, opts.term_width);
Ok(table)
}
pub fn kv_table(cols: &[String], vals: &[Value], opts: TableOpts<'_>) -> StringResult {
pub fn kv_table(cols: &[String], vals: &[Value], opts: BuildConfig<'_>) -> StringResult {
kv_table(cols, vals, opts)
}
}
fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
match table(input, opts.row_offset, opts.clone())? {
Some(out) => {
let table_config =
create_nu_table_config(opts.config, opts.style_computer, &out, false);
Ok(out.table.draw(table_config, opts.width))
#[derive(Debug, Clone)]
pub struct BuildConfig<'a> {
pub(crate) ctrlc: Option<Arc<AtomicBool>>,
pub(crate) config: &'a Config,
pub(crate) style_computer: &'a StyleComputer<'a>,
pub(crate) span: Span,
pub(crate) term_width: usize,
}
impl<'a> BuildConfig<'a> {
pub fn new(
ctrlc: Option<Arc<AtomicBool>>,
config: &'a Config,
style_computer: &'a StyleComputer<'a>,
span: Span,
term_width: usize,
) -> Self {
Self {
ctrlc,
config,
style_computer,
span,
term_width,
}
None => Ok(None),
}
pub fn span(&self) -> Span {
self.span
}
pub fn term_width(&self) -> usize {
self.term_width
}
pub fn config(&self) -> &Config {
self.config
}
pub fn style_computer(&self) -> &StyleComputer {
self.style_computer
}
pub fn ctrlc(&self) -> Option<&Arc<AtomicBool>> {
self.ctrlc.as_ref()
}
}
fn kv_table(cols: &[String], vals: &[Value], opts: TableOpts<'_>) -> StringResult {
fn kv_table(cols: &[String], vals: &[Value], opts: BuildConfig<'_>) -> StringResult {
let mut data = vec![Vec::with_capacity(2); cols.len()];
for ((column, value), row) in cols.iter().zip(vals.iter()).zip(data.iter_mut()) {
if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) {
@ -47,8 +91,8 @@ fn kv_table(cols: &[String], vals: &[Value], opts: TableOpts<'_>) -> StringResul
value = clean_charset(&value);
}
let key = NuTableCell::new(column.to_string());
let value = NuTableCell::new(value);
let key = Cell::new(column.to_string());
let value = Cell::new(value);
row.push(key);
row.push(value);
}
@ -57,13 +101,13 @@ fn kv_table(cols: &[String], vals: &[Value], opts: TableOpts<'_>) -> StringResul
table.set_index_style(TextStyle::default_field());
let out = TableOutput::new(table, false, true);
let table_config = create_nu_table_config(opts.config, opts.style_computer, &out, false);
let table = out.table.draw(table_config, opts.width);
let table_config = create_table_config(opts.config, opts.style_computer, &out);
let table = out.table.draw(table_config, opts.term_width);
Ok(table)
}
fn table(input: &[Value], row_offset: usize, opts: TableOpts<'_>) -> TableResult {
fn table(input: &[Value], row_offset: usize, opts: BuildConfig<'_>) -> TableResult {
if input.is_empty() {
return Ok(None);
}
@ -104,7 +148,7 @@ fn to_table_with_header(
headers: Vec<String>,
with_index: bool,
row_offset: usize,
opts: TableOpts<'_>,
opts: BuildConfig<'_>,
) -> Result<Option<NuTable>, ShellError> {
let count_rows = input.len() + 1;
let count_columns = headers.len();
@ -135,7 +179,7 @@ fn to_table_with_header(
let (text, style) = get_string_value_with_header(item, header, &opts);
table.insert((row + 1, col), text);
table.insert_style((row + 1, col), style);
table.set_cell_style((row + 1, col), style);
}
}
@ -146,7 +190,7 @@ fn to_table_with_no_header(
input: &[Value],
with_index: bool,
row_offset: usize,
opts: TableOpts<'_>,
opts: BuildConfig<'_>,
) -> Result<Option<NuTable>, ShellError> {
let mut table = NuTable::new(input.len(), with_index as usize + 1);
table.set_index_style(get_index_style(opts.style_computer));
@ -169,13 +213,13 @@ fn to_table_with_no_header(
let pos = (row, with_index as usize);
table.insert(pos, text);
table.insert_style(pos, style);
table.set_cell_style(pos, style);
}
Ok(Some(table))
}
fn get_string_value_with_header(item: &Value, header: &str, opts: &TableOpts) -> NuText {
fn get_string_value_with_header(item: &Value, header: &str, opts: &BuildConfig) -> NuText {
match item {
Value::Record { .. } => {
let path = PathMember::String {
@ -194,7 +238,7 @@ fn get_string_value_with_header(item: &Value, header: &str, opts: &TableOpts) ->
}
}
fn get_string_value(item: &Value, opts: &TableOpts) -> NuText {
fn get_string_value(item: &Value, opts: &BuildConfig) -> NuText {
let (mut text, style) = get_value_style(item, opts.config, opts.style_computer);
let is_string_value = matches!(item, Value::String { .. });
if is_string_value {

View File

@ -2,15 +2,20 @@ mod collapse;
mod expanded;
mod general;
use std::sync::{atomic::AtomicBool, Arc};
use nu_color_config::{Alignment, StyleComputer, TextStyle};
use nu_protocol::TrimStrategy;
use nu_protocol::{Config, FooterMode, ShellError, Span, Value};
use std::collections::HashMap;
use crate::{string_wrap, NuTable, TableConfig, TableTheme};
pub use collapse::CollapsedTable;
pub use expanded::ExpandedTable;
pub use general::JustTable;
use nu_color_config::StyleComputer;
use nu_protocol::{Config, Span};
pub use general::{BuildConfig, JustTable};
use crate::NuTable;
pub type NuText = (String, TextStyle);
pub type TableResult = Result<Option<TableOutput>, ShellError>;
pub type StringResult = Result<Option<String>, ShellError>;
pub struct TableOutput {
pub table: NuTable,
@ -28,32 +33,176 @@ impl TableOutput {
}
}
#[derive(Debug, Clone)]
pub struct TableOpts<'a> {
ctrlc: Option<Arc<AtomicBool>>,
config: &'a Config,
style_computer: &'a StyleComputer<'a>,
span: Span,
row_offset: usize,
width: usize,
pub fn value_to_styled_string(val: &Value, cfg: &Config, style: &StyleComputer) -> NuText {
let float_precision = cfg.float_precision as usize;
let text = val.into_abbreviated_string(cfg);
make_styled_string(style, text, Some(val), float_precision)
}
impl<'a> TableOpts<'a> {
pub fn new(
config: &'a Config,
style_computer: &'a StyleComputer<'a>,
ctrlc: Option<Arc<AtomicBool>>,
span: Span,
row_offset: usize,
available_width: usize,
) -> Self {
Self {
ctrlc,
config,
style_computer,
span,
row_offset,
width: available_width,
pub fn value_to_clean_styled_string(val: &Value, cfg: &Config, style: &StyleComputer) -> NuText {
let (text, style) = value_to_styled_string(val, cfg, style);
let text = clean_charset(&text);
(text, style)
}
pub fn clean_charset(text: &str) -> String {
// todo: optimize, I bet it can be done in 1 path
text.replace('\t', " ").replace('\r', "")
}
const INDEX_COLUMN_NAME: &str = "index";
fn error_sign(style_computer: &StyleComputer) -> (String, TextStyle) {
make_styled_string(style_computer, String::from(""), None, 0)
}
fn wrap_text(text: &str, width: usize, config: &Config) -> String {
string_wrap(text, width, is_cfg_trim_keep_words(config))
}
fn make_styled_string(
style_computer: &StyleComputer,
text: String,
value: Option<&Value>, // None represents table holes.
float_precision: usize,
) -> NuText {
match value {
Some(value) => {
match value {
Value::Float { .. } => {
// set dynamic precision from config
let precise_number = match convert_with_precision(&text, float_precision) {
Ok(num) => num,
Err(e) => e.to_string(),
};
(precise_number, style_computer.style_primitive(value))
}
_ => (text, style_computer.style_primitive(value)),
}
}
None => {
// Though holes are not the same as null, the closure for "empty" is passed a null anyway.
(
text,
TextStyle::with_style(
Alignment::Center,
style_computer.compute("empty", &Value::nothing(Span::unknown())),
),
)
}
}
}
fn convert_with_precision(val: &str, precision: usize) -> Result<String, ShellError> {
// vall will always be a f64 so convert it with precision formatting
let val_float = match val.trim().parse::<f64>() {
Ok(f) => f,
Err(e) => {
return Err(ShellError::GenericError(
format!("error converting string [{}] to f64", &val),
"".to_string(),
None,
Some(e.to_string()),
Vec::new(),
));
}
};
Ok(format!("{val_float:.precision$}"))
}
fn is_cfg_trim_keep_words(config: &Config) -> bool {
matches!(
config.trim_strategy,
TrimStrategy::Wrap {
try_to_keep_words: true
}
)
}
fn load_theme_from_config(config: &Config) -> TableTheme {
match config.table_mode.as_str() {
"basic" => TableTheme::basic(),
"thin" => TableTheme::thin(),
"light" => TableTheme::light(),
"compact" => TableTheme::compact(),
"with_love" => TableTheme::with_love(),
"compact_double" => TableTheme::compact_double(),
"rounded" => TableTheme::rounded(),
"reinforced" => TableTheme::reinforced(),
"heavy" => TableTheme::heavy(),
"none" => TableTheme::none(),
_ => TableTheme::rounded(),
}
}
fn create_table_config(config: &Config, comp: &StyleComputer, out: &TableOutput) -> TableConfig {
let theme = load_theme_from_config(config);
let footer = with_footer(config, out.with_header, out.table.count_rows());
let line_style = lookup_separator_color(comp);
let trim = config.trim_strategy.clone();
TableConfig::new()
.theme(theme)
.with_footer(footer)
.with_header(out.with_header)
.with_index(out.with_index)
.line_style(line_style)
.trim(trim)
}
fn lookup_separator_color(style_computer: &StyleComputer) -> nu_ansi_term::Style {
style_computer.compute("separator", &Value::nothing(Span::unknown()))
}
fn with_footer(config: &Config, with_header: bool, count_records: usize) -> bool {
with_header && need_footer(config, count_records as u64)
}
fn need_footer(config: &Config, count_records: u64) -> bool {
matches!(config.footer_mode, FooterMode::RowCount(limit) if count_records > limit)
|| matches!(config.footer_mode, FooterMode::Always)
}
fn set_data_styles(table: &mut NuTable, styles: HashMap<(usize, usize), TextStyle>) {
for (pos, style) in styles {
table.set_cell_style(pos, style);
}
}
fn get_header_style(style_computer: &StyleComputer) -> TextStyle {
TextStyle::with_style(
Alignment::Center,
style_computer.compute("header", &Value::string("", Span::unknown())),
)
}
fn get_index_style(style_computer: &StyleComputer) -> TextStyle {
TextStyle::with_style(
Alignment::Right,
style_computer.compute("row_index", &Value::string("", Span::unknown())),
)
}
fn get_value_style(value: &Value, config: &Config, style_computer: &StyleComputer) -> NuText {
match value {
// Float precision is required here.
Value::Float { val, .. } => (
format!("{:.prec$}", val, prec = config.float_precision as usize),
style_computer.style_primitive(value),
),
_ => (
value.into_abbreviated_string(config),
style_computer.style_primitive(value),
),
}
}
fn get_empty_style(style_computer: &StyleComputer) -> NuText {
(
String::from(""),
TextStyle::with_style(
Alignment::Right,
style_computer.compute("empty", &Value::nothing(Span::unknown())),
),
)
}

View File

@ -42,8 +42,3 @@ pub fn string_truncate(text: &str, width: usize) -> String {
Truncate::truncate_text(line, width).into_owned()
}
pub fn clean_charset(text: &str) -> String {
// todo: optimize, I bet it can be done in 1 path
text.replace('\t', " ").replace('\r', "")
}

View File

@ -1,16 +1,16 @@
#![allow(dead_code)]
use nu_table::{string_width, NuTable, NuTableConfig};
use nu_table::{string_width, NuTable, TableConfig};
use tabled::grid::records::vec_records::CellInfo;
pub struct TestCase {
cfg: NuTableConfig,
cfg: TableConfig,
termwidth: usize,
expected: Option<String>,
}
impl TestCase {
pub fn new(cfg: NuTableConfig, termwidth: usize, expected: Option<String>) -> Self {
pub fn new(cfg: TableConfig, termwidth: usize, expected: Option<String>) -> Self {
Self {
cfg,
termwidth,
@ -37,7 +37,7 @@ pub fn test_table<I: IntoIterator<Item = TestCase>>(data: Data, tests: I) {
}
}
pub fn create_table(data: Data, config: NuTableConfig, termwidth: usize) -> Option<String> {
pub fn create_table(data: Data, config: TableConfig, termwidth: usize) -> Option<String> {
let table = NuTable::from(data);
table.draw(config, termwidth)
}

View File

@ -1,7 +1,7 @@
mod common;
use nu_protocol::TrimStrategy;
use nu_table::{NuTable, NuTableConfig, TableTheme as theme};
use nu_table::{NuTable, TableConfig, TableTheme as theme};
use common::{create_row, test_table, TestCase};
use tabled::grid::records::vec_records::CellInfo;
@ -9,13 +9,11 @@ use tabled::grid::records::vec_records::CellInfo;
#[test]
fn data_and_header_has_different_size_doesnt_work() {
let table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
let cfg = NuTableConfig {
theme: theme::heavy(),
with_header: true,
..Default::default()
};
let table = table.draw(cfg.clone(), usize::MAX);
let table = table.draw(
TableConfig::new().theme(theme::heavy()).with_header(true),
usize::MAX,
);
assert_eq!(
table.as_deref(),
@ -31,7 +29,10 @@ fn data_and_header_has_different_size_doesnt_work() {
let table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
let table = table.draw(cfg, usize::MAX);
let table = table.draw(
TableConfig::new().theme(theme::heavy()).with_header(true),
usize::MAX,
);
assert_eq!(
table.as_deref(),
@ -48,7 +49,7 @@ fn data_and_header_has_different_size_doesnt_work() {
#[test]
fn termwidth_too_small() {
let test_loop = |config: NuTableConfig| {
let test_loop = |config: TableConfig| {
for i in 0..10 {
let table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
let table = table.draw(config.clone(), i);
@ -57,22 +58,29 @@ fn termwidth_too_small() {
}
};
let mut cfg = NuTableConfig {
theme: theme::heavy(),
with_header: true,
..Default::default()
};
let base_config = TableConfig::new().theme(theme::heavy()).with_header(true);
for case in [
TrimStrategy::truncate(None),
TrimStrategy::truncate(Some(String::from("**"))),
TrimStrategy::truncate(Some(String::from(""))),
TrimStrategy::wrap(false),
TrimStrategy::wrap(true),
] {
cfg.trim = case;
test_loop(cfg.clone());
}
let config = base_config.clone();
test_loop(config);
let config = base_config.clone().trim(TrimStrategy::truncate(None));
test_loop(config);
let config = base_config
.clone()
.trim(TrimStrategy::truncate(Some(String::from("**"))));
test_loop(config);
let config = base_config
.clone()
.trim(TrimStrategy::truncate(Some(String::from(""))));
test_loop(config);
let config = base_config.clone().trim(TrimStrategy::wrap(false));
test_loop(config);
let config = base_config.trim(TrimStrategy::wrap(true));
test_loop(config);
}
#[test]
@ -196,12 +204,11 @@ fn width_control_test_0() {
}
fn test_width(data: Vec<Vec<CellInfo<String>>>, tests: &[(usize, &str)]) {
let config = NuTableConfig {
theme: theme::heavy(),
trim: TrimStrategy::truncate(Some(String::from("..."))),
with_header: true,
..Default::default()
};
let trim = TrimStrategy::truncate(Some(String::from("...")));
let config = TableConfig::new()
.theme(theme::heavy())
.with_header(true)
.trim(trim);
let tests = tests.iter().map(|&(termwidth, expected)| {
TestCase::new(config.clone(), termwidth, Some(expected.to_owned()))
@ -211,13 +218,10 @@ fn test_width(data: Vec<Vec<CellInfo<String>>>, tests: &[(usize, &str)]) {
}
fn test_trim(tests: &[(usize, Option<&str>)], trim: TrimStrategy) {
let config = NuTableConfig {
theme: theme::heavy(),
with_header: true,
trim,
..Default::default()
};
let config = TableConfig::new()
.theme(theme::heavy())
.with_header(true)
.trim(trim);
let tests = tests.iter().map(|&(termwidth, expected)| {
TestCase::new(config.clone(), termwidth, expected.map(|s| s.to_string()))
});

View File

@ -2,18 +2,16 @@ mod common;
use common::{create_row, create_table};
use nu_table::{NuTableConfig, TableTheme as theme};
use nu_table::{TableConfig, TableTheme as theme};
#[test]
fn test_expand() {
let table = create_table(
vec![create_row(4); 3],
NuTableConfig {
theme: theme::rounded(),
with_header: true,
expand: true,
..Default::default()
},
TableConfig::new()
.theme(theme::rounded())
.with_header(true)
.expand(true),
50,
);

View File

@ -1,7 +1,7 @@
mod common;
use common::create_row as row;
use nu_table::{NuTable, NuTableConfig, TableTheme as theme};
use nu_table::{NuTable, TableConfig, TableTheme as theme};
use tabled::grid::records::vec_records::CellInfo;
#[test]
@ -476,11 +476,10 @@ fn test_with_love() {
}
fn create_table(data: Vec<Vec<CellInfo<String>>>, with_header: bool, theme: theme) -> String {
let config = NuTableConfig {
theme,
with_header,
..Default::default()
};
let mut config = TableConfig::new().theme(theme);
if with_header {
config = config.with_header(true);
}
let out = common::create_table(data, config, usize::MAX);
@ -492,11 +491,10 @@ fn create_table_with_size(
with_header: bool,
theme: theme,
) -> String {
let config = NuTableConfig {
theme,
with_header,
..Default::default()
};
let mut config = TableConfig::new().theme(theme);
if with_header {
config = config.with_header(true);
}
let table = NuTable::from(data);