//! This module contains a main table representation [`Table`]. use core::ops::DerefMut; use std::{borrow::Cow, fmt, iter::FromIterator}; use crate::{ builder::Builder, grid::{ colors::NoColors, config::{ AlignmentHorizontal, ColorMap, ColoredConfig, CompactConfig, Entity, Formatting, Indent, Sides, SpannedConfig, }, dimension::{CompleteDimensionVecRecords, Dimension, Estimate, PeekableDimension}, records::{ vec_records::{CellInfo, VecRecords}, ExactRecords, Records, }, PeekableGrid, }, settings::{Style, TableOption}, Tabled, }; /// The structure provides an interface for building a table for types that implements [`Tabled`]. /// /// To build a string representation of a table you must use a [`std::fmt::Display`]. /// Or simply call `.to_string()` method. /// /// The default table [`Style`] is [`Style::ascii`], /// with a 1 left and right [`Padding`]. /// /// ## Example /// /// ### Basic usage /// /// ```rust,no_run /// use tabled::Table; /// /// let table = Table::new(&["Year", "2021"]); /// ``` /// /// ### With settings /// /// ```rust,no_run /// use tabled::{Table, settings::{Style, Alignment}}; /// /// let data = vec!["Hello", "2021"]; /// let mut table = Table::new(&data); /// table.with(Style::psql()).with(Alignment::left()); /// /// println!("{}", table); /// ``` /// /// [`Padding`]: crate::settings::Padding /// [`Style`]: crate::settings::Style /// [`Style::ascii`]: crate::settings::Style::ascii #[derive(Debug, Clone, PartialEq, Eq)] pub struct Table { records: VecRecords>, config: ColoredConfig, dimension: CompleteDimensionVecRecords<'static>, } impl Table { /// New creates a Table instance. /// /// If you use a reference iterator you'd better use [`FromIterator`] instead. /// As it has a different lifetime constraints and make less copies therefore. pub fn new(iter: I) -> Self where I: IntoIterator, T: Tabled, { let mut header = Vec::with_capacity(T::LENGTH); for text in T::headers() { let text = text.into_owned(); let cell = CellInfo::new(text); header.push(cell); } let mut records = vec![header]; for row in iter.into_iter() { let mut list = Vec::with_capacity(T::LENGTH); for text in row.fields().into_iter() { let text = text.into_owned(); let cell = CellInfo::new(text); list.push(cell); } records.push(list); } let records = VecRecords::new(records); Self { records, config: ColoredConfig::new(configure_grid()), dimension: CompleteDimensionVecRecords::default(), } } /// Creates a builder from a data set given. /// /// # Example /// /// #[cfg_attr(feature = "derive", doc = "```")] #[cfg_attr(not(feature = "derive"), doc = "```ignore")] /// use tabled::{ /// Table, Tabled, /// settings::{object::Segment, Modify, Alignment} /// }; /// /// #[derive(Tabled)] /// struct User { /// name: &'static str, /// #[tabled(inline("device::"))] /// device: Device, /// } /// /// #[derive(Tabled)] /// enum Device { /// PC, /// Mobile /// } /// /// let data = vec![ /// User { name: "Vlad", device: Device::Mobile }, /// User { name: "Dimitry", device: Device::PC }, /// User { name: "John", device: Device::PC }, /// ]; /// /// let mut table = Table::builder(data) /// .index() /// .column(0) /// .transpose() /// .build() /// .with(Modify::new(Segment::new(1.., 1..)).with(Alignment::center())) /// .to_string(); /// /// assert_eq!( /// table, /// "+----------------+------+---------+------+\n\ /// | name | Vlad | Dimitry | John |\n\ /// +----------------+------+---------+------+\n\ /// | device::PC | | + | + |\n\ /// +----------------+------+---------+------+\n\ /// | device::Mobile | + | | |\n\ /// +----------------+------+---------+------+" /// ) /// ``` pub fn builder(iter: I) -> Builder where T: Tabled, I: IntoIterator, { let mut records = Vec::new(); for row in iter { let mut list = Vec::with_capacity(T::LENGTH); for text in row.fields().into_iter() { list.push(text.into_owned()); } records.push(list); } let mut b = Builder::from(records); let _ = b.set_header(T::headers()).hint_column_size(T::LENGTH); b } /// With is a generic function which applies options to the [`Table`]. /// /// It applies settings immediately. pub fn with(&mut self, option: O) -> &mut Self where O: TableOption< VecRecords>, CompleteDimensionVecRecords<'static>, ColoredConfig, >, { self.dimension.clear_width(); self.dimension.clear_height(); option.change(&mut self.records, &mut self.config, &mut self.dimension); self } /// Returns a table shape (count rows, count columns). pub fn shape(&self) -> (usize, usize) { (self.count_rows(), self.count_columns()) } /// Returns an amount of rows in the table. pub fn count_rows(&self) -> usize { self.records.count_rows() } /// Returns an amount of columns in the table. pub fn count_columns(&self) -> usize { self.records.count_columns() } /// Returns a table shape (count rows, count columns). pub fn is_empty(&self) -> bool { let (count_rows, count_cols) = self.shape(); count_rows == 0 || count_cols == 0 } /// Returns total widths of a table, including margin and horizontal lines. pub fn total_height(&self) -> usize { let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension); dims.estimate(&self.records, self.config.as_ref()); let total = (0..self.count_rows()) .map(|row| dims.get_height(row)) .sum::(); let counth = self.config.count_horizontal(self.count_rows()); let margin = self.config.get_margin(); total + counth + margin.top.size + margin.bottom.size } /// Returns total widths of a table, including margin and vertical lines. pub fn total_width(&self) -> usize { let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension); dims.estimate(&self.records, self.config.as_ref()); let total = (0..self.count_columns()) .map(|col| dims.get_width(col)) .sum::(); let countv = self.config.count_vertical(self.count_columns()); let margin = self.config.get_margin(); total + countv + margin.left.size + margin.right.size } /// Returns a table config. pub fn get_config(&self) -> &ColoredConfig { &self.config } /// Returns a table config. pub fn get_config_mut(&mut self) -> &mut ColoredConfig { &mut self.config } /// Returns a used records. pub fn get_records(&self) -> &VecRecords> { &self.records } /// Returns a used records. pub fn get_records_mut(&mut self) -> &mut VecRecords> { &mut self.records } } impl Default for Table { fn default() -> Self { Self { records: VecRecords::default(), config: ColoredConfig::new(configure_grid()), dimension: CompleteDimensionVecRecords::default(), } } } impl fmt::Display for Table { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_empty() { return Ok(()); } let config = use_format_configuration(f, self); let colors = self.config.get_colors(); if !self.dimension.is_empty() { let mut dims = self.dimension.clone(); dims.estimate(&self.records, config.as_ref()); print_grid(f, &self.records, &config, &dims, colors) } else { let mut dims = PeekableDimension::default(); dims.estimate(&self.records, &config); print_grid(f, &self.records, &config, &dims, colors) } } } impl FromIterator for Table where T: IntoIterator, V: Into, { fn from_iter>(iter: I) -> Self { Builder::from_iter(iter.into_iter().map(|i| i.into_iter().map(|s| s.into()))).build() } } impl From for Table { fn from(builder: Builder) -> Self { let data: Vec>> = builder.into(); let records = VecRecords::new(data); Self { records, config: ColoredConfig::new(configure_grid()), dimension: CompleteDimensionVecRecords::default(), } } } impl From for Builder { fn from(val: Table) -> Self { let count_columns = val.count_columns(); let data: Vec>> = val.records.into(); let mut builder = Builder::from(data); let _ = builder.hint_column_size(count_columns); builder } } impl TableOption for CompactConfig { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { *cfg.deref_mut() = self.into(); } } impl TableOption for ColoredConfig { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { *cfg = self; } } impl TableOption for SpannedConfig { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { *cfg.deref_mut() = self; } } impl TableOption for &SpannedConfig { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { *cfg.deref_mut() = self.clone(); } } fn convert_fmt_alignment(alignment: fmt::Alignment) -> AlignmentHorizontal { match alignment { fmt::Alignment::Left => AlignmentHorizontal::Left, fmt::Alignment::Right => AlignmentHorizontal::Right, fmt::Alignment::Center => AlignmentHorizontal::Center, } } fn table_padding(alignment: fmt::Alignment, available: usize) -> (usize, usize) { match alignment { fmt::Alignment::Left => (available, 0), fmt::Alignment::Right => (0, available), fmt::Alignment::Center => { let left = available / 2; let right = available - left; (left, right) } } } fn configure_grid() -> SpannedConfig { let mut cfg = SpannedConfig::default(); cfg.set_padding( Entity::Global, Sides::new( Indent::spaced(1), Indent::spaced(1), Indent::default(), Indent::default(), ), ); cfg.set_alignment_horizontal(Entity::Global, AlignmentHorizontal::Left); cfg.set_formatting(Entity::Global, Formatting::new(false, false, false)); cfg.set_borders(*Style::ascii().get_borders()); cfg } fn use_format_configuration<'a>( f: &mut fmt::Formatter<'_>, table: &'a Table, ) -> Cow<'a, SpannedConfig> { if f.align().is_some() || f.width().is_some() { let mut cfg = table.config.as_ref().clone(); set_align_table(f, &mut cfg); set_width_table(f, &mut cfg, table); Cow::Owned(cfg) } else { Cow::Borrowed(table.config.as_ref()) } } fn set_align_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig) { if let Some(alignment) = f.align() { let alignment = convert_fmt_alignment(alignment); cfg.set_alignment_horizontal(Entity::Global, alignment); } } fn set_width_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig, table: &Table) { if let Some(width) = f.width() { let total_width = table.total_width(); if total_width >= width { return; } let mut fill = f.fill(); if fill == char::default() { fill = ' '; } let available = width - total_width; let alignment = f.align().unwrap_or(fmt::Alignment::Left); let (left, right) = table_padding(alignment, available); let mut margin = cfg.get_margin(); margin.left.size += left; margin.right.size += right; if (margin.left.size > 0 && margin.left.fill == char::default()) || fill != char::default() { margin.left.fill = fill; } if (margin.right.size > 0 && margin.right.fill == char::default()) || fill != char::default() { margin.right.fill = fill; } cfg.set_margin(margin); } } fn print_grid( f: &mut F, records: &VecRecords>, cfg: &SpannedConfig, dims: D, colors: &ColorMap, ) -> fmt::Result { if !colors.is_empty() { PeekableGrid::new(records, cfg, &dims, colors).build(f) } else { PeekableGrid::new(records, cfg, &dims, NoColors).build(f) } }