summaryrefslogtreecommitdiffstats
path: root/vendor/tabled/src/tables
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-07 05:48:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-07 05:48:48 +0000
commitef24de24a82fe681581cc130f342363c47c0969a (patch)
tree0d494f7e1a38b95c92426f58fe6eaa877303a86c /vendor/tabled/src/tables
parentReleasing progress-linux version 1.74.1+dfsg1-1~progress7.99u1. (diff)
downloadrustc-ef24de24a82fe681581cc130f342363c47c0969a.tar.xz
rustc-ef24de24a82fe681581cc130f342363c47c0969a.zip
Merging upstream version 1.75.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/tabled/src/tables')
-rw-r--r--vendor/tabled/src/tables/compact.rs309
-rw-r--r--vendor/tabled/src/tables/extended.rs338
-rw-r--r--vendor/tabled/src/tables/iter.rs344
-rw-r--r--vendor/tabled/src/tables/mod.rs48
-rw-r--r--vendor/tabled/src/tables/table.rs464
-rw-r--r--vendor/tabled/src/tables/table_pool.rs1607
-rw-r--r--vendor/tabled/src/tables/util/mod.rs2
-rw-r--r--vendor/tabled/src/tables/util/utf8_writer.rs29
8 files changed, 3141 insertions, 0 deletions
diff --git a/vendor/tabled/src/tables/compact.rs b/vendor/tabled/src/tables/compact.rs
new file mode 100644
index 000000000..14c1e2b5b
--- /dev/null
+++ b/vendor/tabled/src/tables/compact.rs
@@ -0,0 +1,309 @@
+//! This module contains a [`CompactTable`] table.
+//!
+//! In contrast to [`Table`] [`CompactTable`] does no allocations but it consumes an iterator.
+//! It's useful when you don't want to re/allocate a buffer for your data.
+//!
+//! # Example
+//!
+//! It works smoothly with arrays.
+//!
+#![cfg_attr(feature = "std", doc = "```")]
+#![cfg_attr(not(feature = "std"), doc = "```ignore")]
+//!use tabled::{settings::Style, tables::CompactTable};
+//!
+//! let data = [
+//! ["FreeBSD", "1993", "William and Lynne Jolitz", "?"],
+//! ["OpenBSD", "1995", "Theo de Raadt", ""],
+//! ["HardenedBSD", "2014", "Oliver Pinter and Shawn Webb", ""],
+//! ];
+//!
+//! let table = CompactTable::from(data)
+//! .with(Style::psql())
+//! .to_string();
+//!
+//! assert_eq!(
+//! table,
+//! concat!(
+//! " FreeBSD | 1993 | William and Lynne Jolitz | ? \n",
+//! "-------------+------+------------------------------+---\n",
+//! " OpenBSD | 1995 | Theo de Raadt | \n",
+//! " HardenedBSD | 2014 | Oliver Pinter and Shawn Webb | ",
+//! )
+//! );
+//! ```
+//!
+//! But it's default creation requires to be given an estimated cell width, and the amount of columns.
+//!
+#![cfg_attr(feature = "std", doc = "```")]
+#![cfg_attr(not(feature = "std"), doc = "```ignore")]
+//!use tabled::{settings::Style, tables::CompactTable};
+//!
+//! let data = [
+//! ["FreeBSD", "1993", "William and Lynne Jolitz", "?"],
+//! ["OpenBSD", "1995", "Theo de Raadt", ""],
+//! ["HardenedBSD", "2014", "Oliver Pinter and Shawn Webb", ""],
+//! ];
+//!
+//! // See what will happen if the given width is too narrow
+//!
+//! let table = CompactTable::new(&data)
+//! .columns(4)
+//! .width(5)
+//! .with(Style::ascii())
+//! .to_string();
+//!
+//! assert_eq!(
+//! table,
+//! "+-----+-----+-----+-----+\n\
+//! | FreeBSD | 1993 | William and Lynne Jolitz | ? |\n\
+//! |-----+-----+-----+-----|\n\
+//! | OpenBSD | 1995 | Theo de Raadt | |\n\
+//! |-----+-----+-----+-----|\n\
+//! | HardenedBSD | 2014 | Oliver Pinter and Shawn Webb | |\n\
+//! +-----+-----+-----+-----+"
+//! );
+//! ```
+//!
+//! [`Table`]: crate::Table
+
+use core::cmp::max;
+use core::fmt;
+
+use crate::{
+ grid::{
+ config::{AlignmentHorizontal, CompactConfig, Indent, Sides},
+ dimension::{ConstDimension, ConstSize, Dimension},
+ records::{
+ into_records::{LimitColumns, LimitRows},
+ IntoRecords, IterRecords,
+ },
+ util::string::string_width,
+ CompactGrid,
+ },
+ settings::{Style, TableOption},
+};
+
+/// A table which consumes an [`IntoRecords`] iterator.
+/// It assumes that the content has only single line.
+#[derive(Debug, Clone)]
+pub struct CompactTable<I, D> {
+ records: I,
+ cfg: CompactConfig,
+ dims: D,
+ count_columns: usize,
+ count_rows: Option<usize>,
+}
+
+impl<I> CompactTable<I, ConstDimension<0, 0>> {
+ /// Creates a new [`CompactTable`] structure with a width dimension for all columns.
+ pub const fn new(iter: I) -> Self
+ where
+ I: IntoRecords,
+ {
+ Self {
+ records: iter,
+ cfg: create_config(),
+ count_columns: 0,
+ count_rows: None,
+ dims: ConstDimension::new(ConstSize::Value(2), ConstSize::Value(1)),
+ }
+ }
+}
+
+impl<I, const ROWS: usize, const COLS: usize> CompactTable<I, ConstDimension<COLS, ROWS>> {
+ /// Set a height for each row.
+ pub fn height<S: Into<ConstSize<COUNT_ROWS>>, const COUNT_ROWS: usize>(
+ self,
+ size: S,
+ ) -> CompactTable<I, ConstDimension<COLS, COUNT_ROWS>> {
+ let (width, _) = self.dims.into();
+ CompactTable {
+ dims: ConstDimension::new(width, size.into()),
+ records: self.records,
+ cfg: self.cfg,
+ count_columns: self.count_columns,
+ count_rows: self.count_rows,
+ }
+ }
+
+ /// Set a width for each column.
+ pub fn width<S: Into<ConstSize<COUNT_COLUMNS>>, const COUNT_COLUMNS: usize>(
+ self,
+ size: S,
+ ) -> CompactTable<I, ConstDimension<COUNT_COLUMNS, ROWS>> {
+ let (_, height) = self.dims.into();
+ CompactTable {
+ dims: ConstDimension::new(size.into(), height),
+ records: self.records,
+ cfg: self.cfg,
+ count_columns: self.count_columns,
+ count_rows: self.count_rows,
+ }
+ }
+}
+
+impl<I, D> CompactTable<I, D> {
+ /// Creates a new [`CompactTable`] structure with a known dimension.
+ ///
+ /// Notice that the function wont call [`Estimate`].
+ ///
+ /// [`Estimate`]: crate::grid::dimension::Estimate
+ pub fn with_dimension(iter: I, dimension: D) -> Self
+ where
+ I: IntoRecords,
+ {
+ Self {
+ records: iter,
+ dims: dimension,
+ cfg: create_config(),
+ count_columns: 0,
+ count_rows: None,
+ }
+ }
+
+ /// With is a generic function which applies options to the [`CompactTable`].
+ pub fn with<O>(mut self, option: O) -> Self
+ where
+ for<'a> O: TableOption<IterRecords<&'a I>, D, CompactConfig>,
+ {
+ let mut records = IterRecords::new(&self.records, self.count_columns, self.count_rows);
+ option.change(&mut records, &mut self.cfg, &mut self.dims);
+
+ self
+ }
+
+ /// Limit a number of rows.
+ pub const fn rows(mut self, count_rows: usize) -> Self {
+ self.count_rows = Some(count_rows);
+ self
+ }
+
+ /// Limit a number of columns.
+ pub const fn columns(mut self, count: usize) -> Self {
+ self.count_columns = count;
+ self
+ }
+
+ /// Returns a table config.
+ pub fn get_config(&self) -> &CompactConfig {
+ &self.cfg
+ }
+
+ /// Returns a table config.
+ pub fn get_config_mut(&mut self) -> &mut CompactConfig {
+ &mut self.cfg
+ }
+
+ /// Format table into [fmt::Write]er.
+ pub fn fmt<W>(self, writer: W) -> fmt::Result
+ where
+ I: IntoRecords,
+ D: Dimension,
+ W: fmt::Write,
+ {
+ build_grid(
+ writer,
+ self.records,
+ self.dims,
+ self.cfg,
+ self.count_columns,
+ self.count_rows,
+ )
+ }
+
+ /// Format table into a writer.
+ #[cfg(feature = "std")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+ pub fn build<W>(self, writer: W) -> std::io::Result<()>
+ where
+ I: IntoRecords,
+ D: Dimension,
+ W: std::io::Write,
+ {
+ let writer = super::util::utf8_writer::UTF8Writer::new(writer);
+ self.fmt(writer)
+ .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))
+ }
+
+ /// Build a string.
+ ///
+ /// We can't implement [`std::string::ToString`] cause it does takes `&self` reference.
+ #[allow(clippy::inherent_to_string)]
+ #[cfg(feature = "std")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+ pub fn to_string(self) -> String
+ where
+ I: IntoRecords,
+ D: Dimension,
+ {
+ let mut buf = String::new();
+ self.fmt(&mut buf).unwrap();
+ buf
+ }
+}
+
+impl<T, const ROWS: usize, const COLS: usize> From<[[T; COLS]; ROWS]>
+ for CompactTable<[[T; COLS]; ROWS], ConstDimension<COLS, ROWS>>
+where
+ T: AsRef<str>,
+{
+ fn from(mat: [[T; COLS]; ROWS]) -> Self {
+ let mut width = [0; COLS];
+ for row in mat.iter() {
+ for (col, text) in row.iter().enumerate() {
+ let text = text.as_ref();
+ let text_width = string_width(text);
+ width[col] = max(width[col], text_width);
+ }
+ }
+
+ // add padding
+ for w in &mut width {
+ *w += 2;
+ }
+
+ let dims = ConstDimension::new(ConstSize::List(width), ConstSize::Value(1));
+ Self::with_dimension(mat, dims).columns(COLS).rows(ROWS)
+ }
+}
+
+fn build_grid<W: fmt::Write, I: IntoRecords, D: Dimension>(
+ writer: W,
+ records: I,
+ dims: D,
+ config: CompactConfig,
+ cols: usize,
+ rows: Option<usize>,
+) -> Result<(), fmt::Error> {
+ match rows {
+ Some(limit) => {
+ let records = LimitRows::new(records, limit);
+ let records = LimitColumns::new(records, cols);
+ let records = IterRecords::new(records, cols, rows);
+ CompactGrid::new(records, dims, config).build(writer)
+ }
+ None => {
+ let records = LimitColumns::new(records, cols);
+ let records = IterRecords::new(records, cols, rows);
+ CompactGrid::new(records, dims, config).build(writer)
+ }
+ }
+}
+
+const fn create_config() -> CompactConfig {
+ CompactConfig::empty()
+ .set_padding(Sides::new(
+ Indent::spaced(1),
+ Indent::spaced(1),
+ Indent::zero(),
+ Indent::zero(),
+ ))
+ .set_alignment_horizontal(AlignmentHorizontal::Left)
+ .set_borders(*Style::ascii().get_borders())
+}
+
+impl<R, D> TableOption<R, D, CompactConfig> for CompactConfig {
+ fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) {
+ *cfg = self;
+ }
+}
diff --git a/vendor/tabled/src/tables/extended.rs b/vendor/tabled/src/tables/extended.rs
new file mode 100644
index 000000000..478505c4b
--- /dev/null
+++ b/vendor/tabled/src/tables/extended.rs
@@ -0,0 +1,338 @@
+//! This module contains an [`ExtendedTable`] structure which is useful in cases where
+//! a structure has a lot of fields.
+//!
+#![cfg_attr(feature = "derive", doc = "```")]
+#![cfg_attr(not(feature = "derive"), doc = "```ignore")]
+//! use tabled::{Tabled, tables::ExtendedTable};
+//!
+//! #[derive(Tabled)]
+//! struct Language {
+//! name: &'static str,
+//! designed_by: &'static str,
+//! invented_year: usize,
+//! }
+//!
+//! let languages = vec![
+//! Language{
+//! name: "C",
+//! designed_by: "Dennis Ritchie",
+//! invented_year: 1972
+//! },
+//! Language{
+//! name: "Rust",
+//! designed_by: "Graydon Hoare",
+//! invented_year: 2010
+//! },
+//! Language{
+//! name: "Go",
+//! designed_by: "Rob Pike",
+//! invented_year: 2009
+//! },
+//! ];
+//!
+//! let table = ExtendedTable::new(languages).to_string();
+//!
+//! let expected = "-[ RECORD 0 ]-+---------------\n\
+//! name | C\n\
+//! designed_by | Dennis Ritchie\n\
+//! invented_year | 1972\n\
+//! -[ RECORD 1 ]-+---------------\n\
+//! name | Rust\n\
+//! designed_by | Graydon Hoare\n\
+//! invented_year | 2010\n\
+//! -[ RECORD 2 ]-+---------------\n\
+//! name | Go\n\
+//! designed_by | Rob Pike\n\
+//! invented_year | 2009";
+//!
+//! assert_eq!(table, expected);
+//! ```
+
+use std::borrow::Cow;
+use std::fmt::{self, Display};
+
+use crate::grid::util::string::string_width;
+use crate::Tabled;
+
+/// `ExtendedTable` display data in a 'expanded display mode' from postgresql.
+/// It may be useful for a large data sets with a lot of fields.
+///
+/// See 'Examples' in <https://www.postgresql.org/docs/current/app-psql.html>.
+///
+/// It escapes strings to resolve a multi-line ones.
+/// Because of that ANSI sequences will be not be rendered too so colores will not be showed.
+///
+/// ```
+/// use tabled::tables::ExtendedTable;
+///
+/// let data = vec!["Hello", "2021"];
+/// let table = ExtendedTable::new(&data).to_string();
+///
+/// assert_eq!(
+/// table,
+/// concat!(
+/// "-[ RECORD 0 ]-\n",
+/// "&str | Hello\n",
+/// "-[ RECORD 1 ]-\n",
+/// "&str | 2021",
+/// )
+/// );
+/// ```
+#[derive(Debug, Clone)]
+pub struct ExtendedTable {
+ fields: Vec<String>,
+ records: Vec<Vec<String>>,
+}
+
+impl ExtendedTable {
+ /// Creates a new instance of `ExtendedTable`
+ pub fn new<T>(iter: impl IntoIterator<Item = T>) -> Self
+ where
+ T: Tabled,
+ {
+ let data = iter
+ .into_iter()
+ .map(|i| {
+ i.fields()
+ .into_iter()
+ .map(|s| s.escape_debug().to_string())
+ .collect()
+ })
+ .collect();
+ let header = T::headers()
+ .into_iter()
+ .map(|s| s.escape_debug().to_string())
+ .collect();
+
+ Self {
+ records: data,
+ fields: header,
+ }
+ }
+
+ /// Truncates table to a set width value for a table.
+ /// It returns a success inticator, where `false` means it's not possible to set the table width,
+ /// because of the given arguments.
+ ///
+ /// It tries to not affect fields, but if there's no enough space all records will be deleted and fields will be cut.
+ ///
+ /// The minimum width is 14.
+ pub fn truncate(&mut self, max: usize, suffix: &str) -> bool {
+ // -[ RECORD 0 ]-
+ let teplate_width = self.records.len().to_string().len() + 13;
+ let min_width = teplate_width;
+ if max < min_width {
+ return false;
+ }
+
+ let suffix_width = string_width(suffix);
+ if max < suffix_width {
+ return false;
+ }
+
+ let max = max - suffix_width;
+
+ let fields_max_width = self
+ .fields
+ .iter()
+ .map(|s| string_width(s))
+ .max()
+ .unwrap_or_default();
+
+ // 3 is a space for ' | '
+ let fields_affected = max < fields_max_width + 3;
+ if fields_affected {
+ if max < 3 {
+ return false;
+ }
+
+ let max = max - 3;
+
+ if max < suffix_width {
+ return false;
+ }
+
+ let max = max - suffix_width;
+
+ truncate_fields(&mut self.fields, max, suffix);
+ truncate_records(&mut self.records, 0, suffix);
+ } else {
+ let max = max - fields_max_width - 3 - suffix_width;
+ truncate_records(&mut self.records, max, suffix);
+ }
+
+ true
+ }
+}
+
+impl From<Vec<Vec<String>>> for ExtendedTable {
+ fn from(mut data: Vec<Vec<String>>) -> Self {
+ if data.is_empty() {
+ return Self {
+ fields: vec![],
+ records: vec![],
+ };
+ }
+
+ let fields = data.remove(0);
+
+ Self {
+ fields,
+ records: data,
+ }
+ }
+}
+
+impl Display for ExtendedTable {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if self.records.is_empty() {
+ return Ok(());
+ }
+
+ // It's possible that field|header can be a multiline string so
+ // we escape it and trim \" chars.
+ let fields = self.fields.iter().collect::<Vec<_>>();
+
+ let max_field_width = fields
+ .iter()
+ .map(|s| string_width(s))
+ .max()
+ .unwrap_or_default();
+
+ let max_values_length = self
+ .records
+ .iter()
+ .map(|record| record.iter().map(|s| string_width(s)).max())
+ .max()
+ .unwrap_or_default()
+ .unwrap_or_default();
+
+ for (i, records) in self.records.iter().enumerate() {
+ write_header_template(f, i, max_field_width, max_values_length)?;
+
+ for (value, field) in records.iter().zip(fields.iter()) {
+ writeln!(f)?;
+ write_record(f, field, value, max_field_width)?;
+ }
+
+ let is_last_record = i + 1 == self.records.len();
+ if !is_last_record {
+ writeln!(f)?;
+ }
+ }
+
+ Ok(())
+ }
+}
+
+fn truncate_records(records: &mut Vec<Vec<String>>, max_width: usize, suffix: &str) {
+ for fields in records {
+ truncate_fields(fields, max_width, suffix);
+ }
+}
+
+fn truncate_fields(records: &mut Vec<String>, max_width: usize, suffix: &str) {
+ for text in records {
+ truncate(text, max_width, suffix);
+ }
+}
+
+fn write_header_template(
+ f: &mut fmt::Formatter<'_>,
+ index: usize,
+ max_field_width: usize,
+ max_values_length: usize,
+) -> fmt::Result {
+ let mut template = format!("-[ RECORD {index} ]-");
+ let default_template_length = template.len();
+
+ // 3 - is responsible for ' | ' formatting
+ let max_line_width = std::cmp::max(
+ max_field_width + 3 + max_values_length,
+ default_template_length,
+ );
+ let rest_to_print = max_line_width - default_template_length;
+ if rest_to_print > 0 {
+ // + 1 is a space after field name and we get a next pos so its +2
+ if max_field_width + 2 > default_template_length {
+ let part1 = (max_field_width + 1) - default_template_length;
+ let part2 = rest_to_print - part1 - 1;
+
+ template.extend(
+ std::iter::repeat('-')
+ .take(part1)
+ .chain(std::iter::once('+'))
+ .chain(std::iter::repeat('-').take(part2)),
+ );
+ } else {
+ template.extend(std::iter::repeat('-').take(rest_to_print));
+ }
+ }
+
+ write!(f, "{template}")?;
+
+ Ok(())
+}
+
+fn write_record(
+ f: &mut fmt::Formatter<'_>,
+ field: &str,
+ value: &str,
+ max_field_width: usize,
+) -> fmt::Result {
+ write!(f, "{field:max_field_width$} | {value}")
+}
+
+fn truncate(text: &mut String, max: usize, suffix: &str) {
+ let original_len = text.len();
+
+ if max == 0 || text.is_empty() {
+ *text = String::new();
+ } else {
+ *text = cut_str_basic(text, max).into_owned();
+ }
+
+ let cut_was_done = text.len() < original_len;
+ if !suffix.is_empty() && cut_was_done {
+ text.push_str(suffix);
+ }
+}
+
+fn cut_str_basic(s: &str, width: usize) -> Cow<'_, str> {
+ const REPLACEMENT: char = '\u{FFFD}';
+
+ let (length, count_unknowns, _) = split_at_pos(s, width);
+ let buf = &s[..length];
+ if count_unknowns == 0 {
+ return Cow::Borrowed(buf);
+ }
+
+ let mut buf = buf.to_owned();
+ buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns));
+
+ Cow::Owned(buf)
+}
+
+fn split_at_pos(s: &str, pos: usize) -> (usize, usize, usize) {
+ let mut length = 0;
+ let mut i = 0;
+ for c in s.chars() {
+ if i == pos {
+ break;
+ };
+
+ let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
+
+ // We cut the chars which takes more then 1 symbol to display,
+ // in order to archive the necessary width.
+ if i + c_width > pos {
+ let count = pos - i;
+ return (length, count, c.len_utf8());
+ }
+
+ i += c_width;
+ length += c.len_utf8();
+ }
+
+ (length, 0, 0)
+}
diff --git a/vendor/tabled/src/tables/iter.rs b/vendor/tabled/src/tables/iter.rs
new file mode 100644
index 000000000..af485ac08
--- /dev/null
+++ b/vendor/tabled/src/tables/iter.rs
@@ -0,0 +1,344 @@
+//! This module contains a [`IterTable`] table.
+//!
+//! In contrast to [`Table`] [`IterTable`] does no allocations but it consumes an iterator.
+//! It's useful when you don't want to re/allocate a buffer for your data.
+//!
+//! # Example
+//!
+//! ```
+//! use tabled::{grid::records::IterRecords, tables::IterTable};
+//!
+//! let iterator = vec![vec!["First", "row"], vec!["Second", "row"]];
+//! let records = IterRecords::new(iterator, 2, Some(2));
+//! let table = IterTable::new(records);
+//!
+//! let s = table.to_string();
+//!
+//! assert_eq!(
+//! s,
+//! "+--------+-----+\n\
+//! | First | row |\n\
+//! +--------+-----+\n\
+//! | Second | row |\n\
+//! +--------+-----+",
+//! );
+//! ```
+//!
+//! [`Table`]: crate::Table
+
+use std::{fmt, io};
+
+use crate::{
+ grid::{
+ colors::NoColors,
+ config::{AlignmentHorizontal, CompactConfig, Indent, Sides, SpannedConfig},
+ dimension::{CompactGridDimension, DimensionValue, StaticDimension},
+ records::{
+ into_records::{
+ truncate_records::ExactValue, BufColumns, BufRows, LimitColumns, LimitRows,
+ TruncateContent,
+ },
+ IntoRecords, IterRecords,
+ },
+ Grid,
+ },
+ settings::{Style, TableOption},
+};
+
+use super::util::utf8_writer::UTF8Writer;
+
+/// A table which consumes an [`IntoRecords`] iterator.
+///
+/// To be able to build table we need a dimensions.
+/// If no width and count_columns is set, [`IterTable`] will sniff the records, by
+/// keeping a number of rows buffered (You can set the number via [`IterTable::sniff`]).
+#[derive(Debug, Clone)]
+pub struct IterTable<I> {
+ records: I,
+ cfg: CompactConfig,
+ table: Settings,
+}
+
+#[derive(Debug, Clone)]
+struct Settings {
+ sniff: usize,
+ count_columns: Option<usize>,
+ count_rows: Option<usize>,
+ width: Option<usize>,
+ height: Option<usize>,
+}
+
+impl<I> IterTable<I> {
+ /// Creates a new [`IterTable`] structure.
+ pub fn new(iter: I) -> Self
+ where
+ I: IntoRecords,
+ {
+ Self {
+ records: iter,
+ cfg: create_config(),
+ table: Settings {
+ sniff: 1000,
+ count_columns: None,
+ count_rows: None,
+ height: None,
+ width: None,
+ },
+ }
+ }
+
+ /// With is a generic function which applies options to the [`IterTable`].
+ pub fn with<O>(mut self, option: O) -> Self
+ where
+ for<'a> O: TableOption<IterRecords<&'a I>, StaticDimension, CompactConfig>,
+ {
+ let count_columns = self.table.count_columns.unwrap_or(0);
+ let mut records = IterRecords::new(&self.records, count_columns, self.table.count_rows);
+ let mut dims = StaticDimension::new(DimensionValue::Exact(0), DimensionValue::Exact(1));
+ option.change(&mut records, &mut self.cfg, &mut dims);
+
+ self
+ }
+
+ /// Limit a number of columns.
+ pub fn columns(mut self, count_columns: usize) -> Self {
+ self.table.count_columns = Some(count_columns);
+ self
+ }
+
+ /// Limit a number of rows.
+ pub fn rows(mut self, count_rows: usize) -> Self {
+ self.table.count_rows = Some(count_rows);
+ self
+ }
+
+ /// Limit an amount of rows will be read for dimension estimations.
+ pub fn sniff(mut self, count: usize) -> Self {
+ self.table.sniff = count;
+ self
+ }
+
+ /// Set a height for each row.
+ pub fn height(mut self, size: usize) -> Self {
+ self.table.height = Some(size);
+ self
+ }
+
+ /// Set a width for each column.
+ pub fn width(mut self, size: usize) -> Self {
+ self.table.width = Some(size);
+ self
+ }
+
+ /// Build a string.
+ ///
+ /// We can't implement [`std::string::ToString`] cause it does takes `&self` reference.
+ #[allow(clippy::inherent_to_string)]
+ pub fn to_string(self) -> String
+ where
+ I: IntoRecords,
+ {
+ let mut buf = String::new();
+ self.fmt(&mut buf).expect("safe");
+
+ buf
+ }
+
+ /// Format table into [`io::Write`]r.
+ pub fn build<W>(self, writer: W) -> io::Result<()>
+ where
+ I: IntoRecords,
+ W: io::Write,
+ {
+ let writer = UTF8Writer::new(writer);
+ self.fmt(writer)
+ .map_err(|err| io::Error::new(io::ErrorKind::Other, err))
+ }
+
+ /// Format table into [fmt::Write]er.
+ pub fn fmt<W>(self, writer: W) -> fmt::Result
+ where
+ I: IntoRecords,
+ W: fmt::Write,
+ {
+ build_grid(writer, self.records, self.cfg, self.table)
+ }
+}
+
+fn build_grid<W: fmt::Write, I: IntoRecords>(
+ f: W,
+ iter: I,
+ cfg: CompactConfig,
+ opts: Settings,
+) -> fmt::Result {
+ let dont_sniff = opts.width.is_some() && opts.count_columns.is_some();
+ if dont_sniff {
+ build_table_with_static_dims(f, iter, cfg, opts)
+ } else if opts.width.is_none() {
+ build_table_sniffing_with_unknown_width(f, iter, cfg, opts)
+ } else {
+ build_table_sniffing_with_known_width(f, iter, cfg, opts)
+ }
+}
+
+fn build_table_with_static_dims<W, I>(
+ f: W,
+ iter: I,
+ cfg: CompactConfig,
+ opts: Settings,
+) -> fmt::Result
+where
+ W: fmt::Write,
+ I: IntoRecords,
+{
+ let count_columns = opts.count_columns.unwrap();
+ let width = opts.width.unwrap();
+ let height = opts.height.unwrap_or(1);
+ let contentw = ExactValue::Exact(width);
+ let pad = cfg.get_padding();
+ let w = DimensionValue::Exact(width + pad.left.size + pad.right.size);
+ let h = DimensionValue::Exact(height + pad.top.size + pad.bottom.size);
+ let dims = StaticDimension::new(w, h);
+ let cfg = SpannedConfig::from(cfg);
+
+ match opts.count_rows {
+ Some(limit) => {
+ let records = LimitRows::new(iter, limit);
+ let records = build_records(records, contentw, count_columns, Some(limit));
+ Grid::new(records, dims, cfg, NoColors).build(f)
+ }
+ None => {
+ let records = build_records(iter, contentw, count_columns, None);
+ Grid::new(records, dims, cfg, NoColors).build(f)
+ }
+ }
+}
+
+fn build_table_sniffing_with_unknown_width<W, I>(
+ f: W,
+ iter: I,
+ cfg: CompactConfig,
+ opts: Settings,
+) -> fmt::Result
+where
+ W: fmt::Write,
+ I: IntoRecords,
+{
+ let records = BufRows::new(iter, opts.sniff);
+ let records = BufColumns::from(records);
+
+ let count_columns = get_count_columns(&opts, records.as_slice());
+
+ let (mut width, height) = {
+ let records = LimitColumns::new(records.as_slice(), count_columns);
+ let records = IterRecords::new(records, count_columns, None);
+ CompactGridDimension::dimension(records, &cfg)
+ };
+
+ let padding = cfg.get_padding();
+ let pad = padding.left.size + padding.right.size;
+ let padv = padding.top.size + padding.bottom.size;
+
+ if opts.sniff == 0 {
+ width = std::iter::repeat(pad)
+ .take(count_columns)
+ .collect::<Vec<_>>();
+ }
+
+ let content_width = ExactValue::List(width.iter().map(|i| i.saturating_sub(pad)).collect());
+ let dims_width = DimensionValue::List(width);
+
+ let height_exact = opts.height.unwrap_or(1) + padv;
+ let mut dims_height = DimensionValue::Partial(height, height_exact);
+
+ if opts.height.is_some() {
+ dims_height = DimensionValue::Exact(height_exact);
+ }
+
+ let dims = StaticDimension::new(dims_width, dims_height);
+ let cfg = SpannedConfig::from(cfg);
+
+ match opts.count_rows {
+ Some(limit) => {
+ let records = LimitRows::new(records, limit);
+ let records = build_records(records, content_width, count_columns, Some(limit));
+ Grid::new(records, dims, cfg, NoColors).build(f)
+ }
+ None => {
+ let records = build_records(records, content_width, count_columns, None);
+ Grid::new(records, dims, cfg, NoColors).build(f)
+ }
+ }
+}
+
+fn build_table_sniffing_with_known_width<W, I>(
+ f: W,
+ iter: I,
+ cfg: CompactConfig,
+ opts: Settings,
+) -> fmt::Result
+where
+ W: fmt::Write,
+ I: IntoRecords,
+{
+ let records = BufRows::new(iter, opts.sniff);
+ let records = BufColumns::from(records);
+
+ let count_columns = get_count_columns(&opts, records.as_slice());
+
+ let width = opts.width.unwrap();
+ let contentw = ExactValue::Exact(width);
+
+ let padding = cfg.get_padding();
+ let pad = padding.left.size + padding.right.size;
+ let padv = padding.top.size + padding.bottom.size;
+
+ let height = opts.height.unwrap_or(1) + padv;
+ let dimsh = DimensionValue::Exact(height);
+ let dimsw = DimensionValue::Exact(width + pad);
+ let dims = StaticDimension::new(dimsw, dimsh);
+
+ let cfg = SpannedConfig::from(cfg);
+
+ match opts.count_rows {
+ Some(limit) => {
+ let records = LimitRows::new(records, limit);
+ let records = build_records(records, contentw, count_columns, Some(limit));
+ Grid::new(records, dims, cfg, NoColors).build(f)
+ }
+ None => {
+ let records = build_records(records, contentw, count_columns, None);
+ Grid::new(records, dims, cfg, NoColors).build(f)
+ }
+ }
+}
+
+fn get_count_columns(opts: &Settings, buf: &[Vec<String>]) -> usize {
+ match opts.count_columns {
+ Some(size) => size,
+ None => buf.iter().map(|row| row.len()).max().unwrap_or(0),
+ }
+}
+
+fn create_config() -> CompactConfig {
+ CompactConfig::default()
+ .set_padding(Sides::new(
+ Indent::spaced(1),
+ Indent::spaced(1),
+ Indent::default(),
+ Indent::default(),
+ ))
+ .set_alignment_horizontal(AlignmentHorizontal::Left)
+ .set_borders(*Style::ascii().get_borders())
+}
+
+fn build_records<I: IntoRecords>(
+ records: I,
+ width: ExactValue<'_>,
+ count_columns: usize,
+ count_rows: Option<usize>,
+) -> IterRecords<LimitColumns<TruncateContent<'_, I>>> {
+ let records = TruncateContent::new(records, width);
+ let records = LimitColumns::new(records, count_columns);
+ IterRecords::new(records, count_columns, count_rows)
+}
diff --git a/vendor/tabled/src/tables/mod.rs b/vendor/tabled/src/tables/mod.rs
new file mode 100644
index 000000000..e0c4bf794
--- /dev/null
+++ b/vendor/tabled/src/tables/mod.rs
@@ -0,0 +1,48 @@
+//! Module contains a list of table representatives.
+//!
+//! ## [`Table`]
+//!
+//! A default table implementation.
+//!
+//! ## [`IterTable`]
+//!
+//! Just like [`Table`] but it's API is a bit different to serve better in context
+//! where there is a memory limit.
+//!
+//! ## [`ExtendedTable`]
+//!
+//! It's a table which is useful for large amount of data.
+//!
+//! ## [`PoolTable`]
+//!
+//! A table with a greather controll of a layout.
+
+mod compact;
+mod util;
+
+#[cfg(feature = "std")]
+mod extended;
+#[cfg(feature = "std")]
+mod iter;
+#[cfg(feature = "std")]
+mod table;
+#[cfg(feature = "std")]
+mod table_pool;
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub use table::Table;
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub use iter::IterTable;
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub use extended::ExtendedTable;
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub use table_pool::{PoolTable, TableValue};
+
+pub use compact::CompactTable;
diff --git a/vendor/tabled/src/tables/table.rs b/vendor/tabled/src/tables/table.rs
new file mode 100644
index 000000000..d6ff37661
--- /dev/null
+++ b/vendor/tabled/src/tables/table.rs
@@ -0,0 +1,464 @@
+//! 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<CellInfo<String>>,
+ 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<I, T>(iter: I) -> Self
+ where
+ I: IntoIterator<Item = T>,
+ 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<I, T>(iter: I) -> Builder
+ where
+ T: Tabled,
+ I: IntoIterator<Item = T>,
+ {
+ 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<O>(&mut self, option: O) -> &mut Self
+ where
+ O: TableOption<
+ VecRecords<CellInfo<String>>,
+ 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::<usize>();
+ 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::<usize>();
+ 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<CellInfo<String>> {
+ &self.records
+ }
+
+ /// Returns a used records.
+ pub fn get_records_mut(&mut self) -> &mut VecRecords<CellInfo<String>> {
+ &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<T, V> FromIterator<T> for Table
+where
+ T: IntoIterator<Item = V>,
+ V: Into<String>,
+{
+ fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
+ Builder::from_iter(iter.into_iter().map(|i| i.into_iter().map(|s| s.into()))).build()
+ }
+}
+
+impl From<Builder> for Table {
+ fn from(builder: Builder) -> Self {
+ let data: Vec<Vec<CellInfo<String>>> = builder.into();
+ let records = VecRecords::new(data);
+
+ Self {
+ records,
+ config: ColoredConfig::new(configure_grid()),
+ dimension: CompleteDimensionVecRecords::default(),
+ }
+ }
+}
+
+impl From<Table> for Builder {
+ fn from(val: Table) -> Self {
+ let count_columns = val.count_columns();
+ let data: Vec<Vec<CellInfo<String>>> = val.records.into();
+ let mut builder = Builder::from(data);
+ let _ = builder.hint_column_size(count_columns);
+ builder
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for CompactConfig {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ *cfg.deref_mut() = self.into();
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for ColoredConfig {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ *cfg = self;
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for SpannedConfig {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ *cfg.deref_mut() = self;
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> 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: fmt::Write, D: Dimension>(
+ f: &mut F,
+ records: &VecRecords<CellInfo<String>>,
+ 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)
+ }
+}
diff --git a/vendor/tabled/src/tables/table_pool.rs b/vendor/tabled/src/tables/table_pool.rs
new file mode 100644
index 000000000..07d2a5437
--- /dev/null
+++ b/vendor/tabled/src/tables/table_pool.rs
@@ -0,0 +1,1607 @@
+use core::fmt::{self, Display, Formatter};
+
+use crate::{
+ grid::{
+ config::{AlignmentHorizontal, CompactMultilineConfig, Indent, Sides},
+ dimension::{DimensionPriority, PoolTableDimension},
+ records::EmptyRecords,
+ records::IntoRecords,
+ },
+ settings::{Style, TableOption},
+};
+
+/// [`PoolTable`] is a table which allows a greater set of possibilities for cell alignment.
+/// It's data is not aligned in any way by default.
+///
+/// It works similar to the main [`Table`] by default.
+///
+///
+/// ```
+/// use tabled::tables::PoolTable;
+///
+/// let data = vec![
+/// vec!["Hello", "World", "!"],
+/// vec!["Salve", "mondo", "!"],
+/// vec!["Hola", "mundo", "!"],
+/// ];
+///
+/// let table = PoolTable::new(data).to_string();
+///
+/// assert_eq!(
+/// table,
+/// "+-------+-------+---+\n\
+/// | Hello | World | ! |\n\
+/// +-------+-------+---+\n\
+/// | Salve | mondo | ! |\n\
+/// +-------+-------+---+\n\
+/// | Hola | mundo | ! |\n\
+/// +-------+-------+---+"
+/// )
+/// ```
+///
+/// But it allows you to have a different number of columns inside the rows.
+///
+/// ```
+/// use tabled::tables::PoolTable;
+///
+/// let data = vec![
+/// vec!["Hello", "World", "!"],
+/// vec!["Salve, mondo!"],
+/// vec!["Hola", "mundo", "", "", "!"],
+/// ];
+///
+/// let table = PoolTable::new(data).to_string();
+///
+/// assert_eq!(
+/// table,
+/// "+---------+---------+----+\n\
+/// | Hello | World | ! |\n\
+/// +---------+---------+----+\n\
+/// | Salve, mondo! |\n\
+/// +------+-------+--+--+---+\n\
+/// | Hola | mundo | | | ! |\n\
+/// +------+-------+--+--+---+"
+/// )
+/// ```
+///
+/// Notice that you also can build a custom table layout by using [`TableValue`].
+///
+/// ```
+/// use tabled::tables::{PoolTable, TableValue};
+///
+/// let message = "Hello\nWorld";
+///
+/// let data = TableValue::Column(vec![
+/// TableValue::Row(vec![
+/// TableValue::Column(vec![
+/// TableValue::Cell(String::from(message)),
+/// ]),
+/// TableValue::Column(vec![
+/// TableValue::Cell(String::from(message)),
+/// TableValue::Row(vec![
+/// TableValue::Cell(String::from(message)),
+/// TableValue::Cell(String::from(message)),
+/// TableValue::Cell(String::from(message)),
+/// ])
+/// ]),
+/// ]),
+/// TableValue::Cell(String::from(message)),
+/// ]);
+///
+/// let table = PoolTable::from(data).to_string();
+///
+/// assert_eq!(
+/// table,
+/// "+-------+-----------------------+\n\
+/// | Hello | Hello |\n\
+/// | World | World |\n\
+/// | +-------+-------+-------+\n\
+/// | | Hello | Hello | Hello |\n\
+/// | | World | World | World |\n\
+/// +-------+-------+-------+-------+\n\
+/// | Hello |\n\
+/// | World |\n\
+/// +-------------------------------+"
+/// )
+/// ```
+///
+/// [`Table`]: crate::Table
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct PoolTable {
+ config: CompactMultilineConfig,
+ dims: PoolTableDimension,
+ value: TableValue,
+}
+
+impl PoolTable {
+ /// Creates a [`PoolTable`] out from a record iterator.
+ pub fn new<I: IntoRecords>(iter: I) -> Self {
+ let value = TableValue::Column(
+ iter.iter_rows()
+ .into_iter()
+ .map(|row| {
+ TableValue::Row(
+ row.into_iter()
+ .map(|cell| cell.as_ref().to_string())
+ .map(TableValue::Cell)
+ .collect(),
+ )
+ })
+ .collect(),
+ );
+
+ Self {
+ config: configure_grid(),
+ dims: PoolTableDimension::new(DimensionPriority::List, DimensionPriority::List),
+ value,
+ }
+ }
+
+ /// A is a generic function which applies options to the [`PoolTable`] configuration.
+ ///
+ /// Notice that it has a limited support of options.
+ ///
+ /// ```
+ /// use tabled::tables::PoolTable;
+ /// use tabled::settings::{Style, Padding};
+ ///
+ /// let data = vec![
+ /// vec!["Hello", "World", "!"],
+ /// vec!["Salve", "mondo", "!"],
+ /// vec!["Hola", "mundo", "!"],
+ /// ];
+ ///
+ /// let table = PoolTable::new(data)
+ /// .with(Style::extended())
+ /// .with(Padding::zero())
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "╔═════╦═════╦═╗\n\
+ /// ║Hello║World║!║\n\
+ /// ╠═════╬═════╬═╣\n\
+ /// ║Salve║mondo║!║\n\
+ /// ╠═════╬═════╬═╣\n\
+ /// ║Hola ║mundo║!║\n\
+ /// ╚═════╩═════╩═╝"
+ /// )
+ /// ```
+ pub fn with<O>(&mut self, option: O) -> &mut Self
+ where
+ O: TableOption<EmptyRecords, PoolTableDimension, CompactMultilineConfig>,
+ {
+ let mut records = EmptyRecords::default();
+ option.change(&mut records, &mut self.config, &mut self.dims);
+
+ self
+ }
+}
+
+impl From<TableValue> for PoolTable {
+ fn from(value: TableValue) -> Self {
+ Self {
+ config: configure_grid(),
+ dims: PoolTableDimension::new(DimensionPriority::List, DimensionPriority::List),
+ value,
+ }
+ }
+}
+
+impl Display for PoolTable {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ print::build_table(&self.value, &self.config, self.dims).fmt(f)
+ }
+}
+
+/// [`TableValue`] a structure which is responsible for a [`PoolTable`] layout.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub enum TableValue {
+ /// A horizontal row.
+ Row(Vec<TableValue>),
+ /// A vertical column.
+ Column(Vec<TableValue>),
+ /// A single cell.
+ Cell(String),
+}
+
+fn configure_grid() -> CompactMultilineConfig {
+ let pad = Sides::new(
+ Indent::spaced(1),
+ Indent::spaced(1),
+ Indent::default(),
+ Indent::default(),
+ );
+
+ CompactMultilineConfig::default()
+ .set_padding(pad)
+ .set_alignment_horizontal(AlignmentHorizontal::Left)
+ .set_borders(*Style::ascii().get_borders())
+}
+
+impl<R, C> TableOption<R, PoolTableDimension, C> for PoolTableDimension {
+ fn change(self, _: &mut R, _: &mut C, dimension: &mut PoolTableDimension) {
+ *dimension = self;
+ }
+}
+
+impl<R, D> TableOption<R, D, CompactMultilineConfig> for CompactMultilineConfig {
+ fn change(self, _: &mut R, config: &mut CompactMultilineConfig, _: &mut D) {
+ *config = self;
+ }
+}
+
+mod print {
+ use std::{cmp::max, collections::HashMap, iter::repeat};
+
+ use papergrid::{
+ color::StaticColor,
+ config::{Border, Borders},
+ util::string::string_width_multiline,
+ };
+
+ use crate::{
+ builder::Builder,
+ grid::{
+ config::{
+ AlignmentHorizontal, AlignmentVertical, ColoredConfig, CompactMultilineConfig,
+ Indent, Offset, Sides,
+ },
+ dimension::{Dimension, DimensionPriority, Estimate, PoolTableDimension},
+ records::Records,
+ util::string::{count_lines, get_lines, string_dimension, string_width},
+ },
+ settings::{Padding, Style, TableOption},
+ };
+
+ use super::TableValue;
+
+ #[derive(Debug, Default)]
+ struct PrintContext {
+ pos: usize,
+ is_last_col: bool,
+ is_last_row: bool,
+ is_first_col: bool,
+ is_first_row: bool,
+ kv: bool,
+ kv_is_first: bool,
+ list: bool,
+ list_is_first: bool,
+ no_left: bool,
+ no_right: bool,
+ no_bottom: bool,
+ lean_top: bool,
+ top_intersection: bool,
+ top_left: bool,
+ intersections_horizontal: Vec<usize>,
+ intersections_vertical: Vec<usize>,
+ size: Dim,
+ }
+
+ struct CellData {
+ content: String,
+ intersections_horizontal: Vec<usize>,
+ intersections_vertical: Vec<usize>,
+ }
+
+ impl CellData {
+ fn new(content: String, i_horizontal: Vec<usize>, i_vertical: Vec<usize>) -> Self {
+ Self {
+ content,
+ intersections_horizontal: i_horizontal,
+ intersections_vertical: i_vertical,
+ }
+ }
+ }
+
+ pub(super) fn build_table(
+ val: &TableValue,
+ cfg: &CompactMultilineConfig,
+ dims_priority: PoolTableDimension,
+ ) -> String {
+ let dims = collect_table_dimensions(val, cfg);
+ let ctx = PrintContext {
+ is_last_col: true,
+ is_last_row: true,
+ is_first_col: true,
+ is_first_row: true,
+ size: *dims.all.get(&0).unwrap(),
+ ..Default::default()
+ };
+
+ let data = _build_table(val, cfg, &dims, dims_priority, ctx);
+ let mut table = data.content;
+
+ let margin = cfg.get_margin();
+ let has_margin = margin.top.size > 0
+ || margin.bottom.size > 0
+ || margin.left.size > 0
+ || margin.right.size > 0;
+ if has_margin {
+ let color = convert_border_colors(cfg.get_margin_color());
+ table = set_margin(&table, *margin, color);
+ }
+
+ table
+ }
+
+ fn _build_table(
+ val: &TableValue,
+ cfg: &CompactMultilineConfig,
+ dims: &Dimensions,
+ priority: PoolTableDimension,
+ ctx: PrintContext,
+ ) -> CellData {
+ match val {
+ TableValue::Cell(text) => generate_value_cell(text, cfg, ctx),
+ TableValue::Row(list) => {
+ if list.is_empty() {
+ return generate_value_cell("", cfg, ctx);
+ }
+
+ generate_table_row(list, cfg, dims, priority, ctx)
+ }
+ TableValue::Column(list) => {
+ if list.is_empty() {
+ return generate_value_cell("", cfg, ctx);
+ }
+
+ generate_table_column(list, cfg, dims, priority, ctx)
+ }
+ }
+ }
+
+ fn generate_table_column(
+ list: &Vec<TableValue>,
+ cfg: &CompactMultilineConfig,
+ dims: &Dimensions,
+ priority: PoolTableDimension,
+ ctx: PrintContext,
+ ) -> CellData {
+ let array_dims = dims.arrays.get(&ctx.pos).unwrap();
+
+ let height = dims.all.get(&ctx.pos).unwrap().height;
+ let additional_height = ctx.size.height - height;
+ let (chunk_height, mut rest_height) = split_value(additional_height, list.len());
+
+ let mut intersections_horizontal = ctx.intersections_horizontal;
+ let mut intersections_vertical = ctx.intersections_vertical;
+ let mut next_vsplit = false;
+ let mut next_intersections_vertical = vec![];
+
+ let mut builder = Builder::new();
+ for (i, val) in list.iter().enumerate() {
+ let val_pos = *array_dims.index.get(&i).unwrap();
+
+ let mut height = dims.all.get(&val_pos).unwrap().height;
+ match priority.height() {
+ DimensionPriority::First => {
+ if i == 0 {
+ height += additional_height;
+ }
+ }
+ DimensionPriority::Last => {
+ if i + 1 == list.len() {
+ height += additional_height;
+ }
+ }
+ DimensionPriority::List => {
+ height += chunk_height;
+
+ if rest_height > 0 {
+ height += 1;
+ rest_height -= 1; // must be safe
+ }
+ }
+ }
+
+ let size = Dim::new(ctx.size.width, height);
+
+ let (split, intersections_vertical) =
+ short_splits3(&mut intersections_vertical, size.height);
+ let old_split = next_vsplit;
+ next_vsplit = split;
+
+ let is_prev_list_not_first = ctx.list && !ctx.list_is_first;
+ let valctx = PrintContext {
+ pos: val_pos,
+ is_last_col: ctx.is_last_col,
+ is_last_row: ctx.is_last_row && i + 1 == list.len(),
+ is_first_col: ctx.is_first_col,
+ is_first_row: ctx.is_first_row && i == 0,
+ kv: ctx.kv,
+ kv_is_first: ctx.kv_is_first,
+ list: true,
+ list_is_first: i == 0 && !is_prev_list_not_first,
+ no_left: ctx.no_left,
+ no_right: ctx.no_right,
+ no_bottom: ctx.no_bottom && i + 1 == list.len(),
+ lean_top: ctx.lean_top && i == 0,
+ top_intersection: (ctx.top_intersection && i == 0) || old_split,
+ top_left: ctx.top_left || i > 0,
+ intersections_horizontal,
+ intersections_vertical,
+ size,
+ };
+
+ let data = _build_table(val, cfg, dims, priority, valctx);
+ intersections_horizontal = data.intersections_horizontal;
+ next_intersections_vertical.extend(data.intersections_vertical);
+
+ let _ = builder.push_record([data.content]);
+ }
+
+ let table = builder
+ .build()
+ .with(Style::empty())
+ .with(Padding::zero())
+ .to_string();
+
+ CellData::new(table, intersections_horizontal, next_intersections_vertical)
+ }
+
+ fn generate_table_row(
+ list: &Vec<TableValue>,
+ cfg: &CompactMultilineConfig,
+ dims: &Dimensions,
+ priority: PoolTableDimension,
+ ctx: PrintContext,
+ ) -> CellData {
+ let array_dims = dims.arrays.get(&ctx.pos).unwrap();
+
+ let list_width = dims.all.get(&ctx.pos).unwrap().width;
+ let additional_width = ctx.size.width - list_width;
+ let (chunk_width, mut rest_width) = split_value(additional_width, list.len());
+
+ let mut intersections_horizontal = ctx.intersections_horizontal;
+ let mut intersections_vertical = ctx.intersections_vertical;
+ let mut new_intersections_horizontal = vec![];
+ let mut split_next = false;
+
+ let mut buf = Vec::with_capacity(list.len());
+ for (i, val) in list.iter().enumerate() {
+ let val_pos = *array_dims.index.get(&i).unwrap();
+
+ let mut width = dims.all.get(&val_pos).unwrap().width;
+ match priority.width() {
+ DimensionPriority::First => {
+ if i == 0 {
+ width += additional_width;
+ }
+ }
+ DimensionPriority::Last => {
+ if i + 1 == list.len() {
+ width += additional_width;
+ }
+ }
+ DimensionPriority::List => {
+ width += chunk_width;
+
+ if rest_width > 0 {
+ width += 1;
+ rest_width -= 1; // must be safe
+ }
+ }
+ }
+
+ let size = Dim::new(width, ctx.size.height);
+
+ let (split, intersections_horizontal) =
+ short_splits3(&mut intersections_horizontal, width);
+ let old_split = split_next;
+ split_next = split;
+
+ let is_prev_list_not_first = ctx.list && !ctx.list_is_first;
+ let valctx = PrintContext {
+ pos: val_pos,
+ is_first_col: ctx.is_first_col && i == 0,
+ is_last_col: ctx.is_last_col && i + 1 == list.len(),
+ is_last_row: ctx.is_last_row,
+ is_first_row: ctx.is_first_row,
+ kv: false,
+ kv_is_first: false,
+ list: false,
+ list_is_first: !is_prev_list_not_first,
+ no_left: false,
+ no_right: !(ctx.is_last_col && i + 1 == list.len()),
+ no_bottom: false,
+ lean_top: !(ctx.is_first_col && i == 0),
+ top_intersection: (ctx.top_intersection && i == 0) || old_split,
+ top_left: ctx.top_left && i == 0,
+ intersections_horizontal,
+ intersections_vertical,
+ size,
+ };
+
+ let val = _build_table(val, cfg, dims, priority, valctx);
+ intersections_vertical = val.intersections_vertical;
+ new_intersections_horizontal.extend(val.intersections_horizontal.iter());
+ let value = val.content;
+
+ buf.push(value);
+ }
+
+ let mut b = Builder::with_capacity(1);
+ let _ = b.hint_column_size(buf.len()).push_record(buf);
+ let table = b
+ .build()
+ .with(Style::empty())
+ .with(Padding::zero())
+ .to_string();
+
+ CellData::new(table, new_intersections_horizontal, intersections_vertical)
+ }
+
+ fn generate_value_cell(
+ text: &str,
+ cfg: &CompactMultilineConfig,
+ ctx: PrintContext,
+ ) -> CellData {
+ let width = ctx.size.width;
+ let height = ctx.size.height;
+ let table = generate_value_table(text, cfg, ctx);
+ CellData::new(table, vec![width], vec![height])
+ }
+
+ fn generate_value_table(
+ text: &str,
+ cfg: &CompactMultilineConfig,
+ mut ctx: PrintContext,
+ ) -> String {
+ if ctx.size.width == 0 || ctx.size.height == 0 {
+ return String::new();
+ }
+
+ let halignment = cfg.get_alignment_horizontal();
+ let valignment = cfg.get_alignment_vertical();
+ let pad = cfg.get_padding();
+ let pad_color = cfg.get_padding_color();
+ let pad_color = convert_border_colors(pad_color);
+ let lines_alignemnt = cfg.get_formatting().allow_lines_alignment;
+
+ let mut borders = *cfg.get_borders();
+
+ let bottom_intesection = cfg.get_borders().bottom_intersection.unwrap_or(' ');
+ let mut horizontal_splits = short_splits(&mut ctx.intersections_horizontal, ctx.size.width);
+ squash_splits(&mut horizontal_splits);
+
+ let right_intersection = borders.right_intersection.unwrap_or(' ');
+ let mut vertical_splits = short_splits(&mut ctx.intersections_vertical, ctx.size.height);
+ squash_splits(&mut vertical_splits);
+
+ config_borders(&mut borders, &ctx);
+ let border = create_border(borders);
+
+ let borders_colors = *cfg.get_borders_color();
+ let border_color = create_border(borders_colors);
+
+ let mut height = ctx.size.height;
+ height -= pad.top.size + pad.bottom.size;
+
+ let mut width = ctx.size.width;
+ width -= pad.left.size + pad.right.size;
+
+ let count_lines = count_lines(text);
+ let (top, bottom) = indent_vertical(valignment, height, count_lines);
+
+ let mut buf = String::new();
+ print_top_line(
+ &mut buf,
+ border,
+ border_color,
+ &horizontal_splits,
+ bottom_intesection,
+ ctx.size.width,
+ );
+
+ let mut line_index = 0;
+ let mut vertical_splits = &vertical_splits[..];
+
+ for _ in 0..top {
+ let mut border = border;
+ if vertical_splits.first() == Some(&line_index) {
+ border.left = Some(right_intersection);
+ vertical_splits = &vertical_splits[1..];
+ }
+
+ print_line(&mut buf, border, border_color, None, ' ', ctx.size.width);
+ line_index += 1;
+ }
+
+ for _ in 0..pad.top.size {
+ let mut border = border;
+ if vertical_splits.first() == Some(&line_index) {
+ border.left = Some(right_intersection);
+ vertical_splits = &vertical_splits[1..];
+ }
+
+ print_line(
+ &mut buf,
+ border,
+ border_color,
+ pad_color.top,
+ pad.top.fill,
+ ctx.size.width,
+ );
+ line_index += 1;
+ }
+
+ if lines_alignemnt {
+ for line in get_lines(text) {
+ let line_width = string_width(&line);
+ let (left, right) = indent_horizontal(halignment, width, line_width);
+
+ if border.has_left() {
+ let mut c = border.left.unwrap_or(' ');
+ if vertical_splits.first() == Some(&line_index) {
+ c = right_intersection;
+ vertical_splits = &vertical_splits[1..];
+ }
+
+ print_char(&mut buf, c, border_color.left);
+ }
+
+ print_chars(&mut buf, pad.left.fill, pad_color.left, pad.left.size);
+ buf.extend(repeat(' ').take(left));
+ buf.push_str(&line);
+ buf.extend(repeat(' ').take(right));
+ print_chars(&mut buf, pad.right.fill, pad_color.right, pad.right.size);
+
+ if border.has_right() {
+ print_char(&mut buf, border.right.unwrap_or(' '), border_color.right);
+ }
+
+ buf.push('\n');
+
+ line_index += 1;
+ }
+ } else {
+ let text_width = string_width_multiline(text);
+ let (left, _) = indent_horizontal(halignment, width, text_width);
+
+ for line in get_lines(text) {
+ let line_width = string_width(&line);
+ let right = width - line_width - left;
+
+ if border.has_left() {
+ let mut c = border.left.unwrap_or(' ');
+ if vertical_splits.first() == Some(&line_index) {
+ c = right_intersection;
+ vertical_splits = &vertical_splits[1..];
+ }
+
+ print_char(&mut buf, c, border_color.left);
+ }
+
+ print_chars(&mut buf, pad.left.fill, pad_color.left, pad.left.size);
+ buf.extend(repeat(' ').take(left));
+ buf.push_str(&line);
+ buf.extend(repeat(' ').take(right));
+ print_chars(&mut buf, pad.right.fill, pad_color.right, pad.right.size);
+
+ if border.has_right() {
+ print_char(&mut buf, border.right.unwrap_or(' '), border_color.right);
+ }
+
+ buf.push('\n');
+
+ line_index += 1;
+ }
+ }
+
+ for _ in 0..pad.bottom.size {
+ let mut border = border;
+ if vertical_splits.first() == Some(&line_index) {
+ border.left = Some(right_intersection);
+ vertical_splits = &vertical_splits[1..];
+ }
+
+ print_line(
+ &mut buf,
+ border,
+ border_color,
+ pad_color.bottom,
+ pad.bottom.fill,
+ ctx.size.width,
+ );
+
+ line_index += 1;
+ }
+
+ for _ in 0..bottom {
+ let mut border = border;
+ if vertical_splits.first() == Some(&line_index) {
+ border.left = Some(right_intersection);
+ vertical_splits = &vertical_splits[1..];
+ }
+
+ print_line(&mut buf, border, border_color, None, ' ', ctx.size.width);
+ line_index += 1;
+ }
+
+ print_bottom_line(&mut buf, border, border_color, ctx.size.width);
+
+ let _ = buf.remove(buf.len() - 1);
+
+ buf
+ }
+
+ fn print_chars(buf: &mut String, c: char, color: Option<StaticColor>, width: usize) {
+ match color {
+ Some(color) => {
+ buf.push_str(color.get_prefix());
+ buf.extend(repeat(c).take(width));
+ buf.push_str(color.get_suffix());
+ }
+ None => buf.extend(repeat(c).take(width)),
+ }
+ }
+
+ fn print_char(buf: &mut String, c: char, color: Option<StaticColor>) {
+ match color {
+ Some(color) => {
+ buf.push_str(color.get_prefix());
+ buf.push(c);
+ buf.push_str(color.get_suffix());
+ }
+ None => buf.push(c),
+ }
+ }
+
+ fn print_line(
+ buf: &mut String,
+ border: Border<char>,
+ border_color: Border<StaticColor>,
+ color: Option<StaticColor>,
+ c: char,
+ width: usize,
+ ) {
+ if border.has_left() {
+ let c = border.left.unwrap_or(' ');
+ print_char(buf, c, border_color.left);
+ }
+
+ print_chars(buf, c, color, width);
+
+ if border.has_right() {
+ let c = border.right.unwrap_or(' ');
+ print_char(buf, c, border_color.right);
+ }
+
+ buf.push('\n');
+ }
+
+ fn print_top_line(
+ buf: &mut String,
+ border: Border<char>,
+ color: Border<StaticColor>,
+ splits: &[usize],
+ split_char: char,
+ width: usize,
+ ) {
+ if !border.has_top() {
+ return;
+ }
+
+ let mut used_color: Option<StaticColor> = None;
+
+ if border.has_left() {
+ if let Some(color) = color.left_top_corner {
+ used_color = Some(color);
+ buf.push_str(color.get_prefix());
+ }
+
+ let c = border.left_top_corner.unwrap_or(' ');
+ buf.push(c);
+ }
+
+ if let Some(color) = color.top {
+ match used_color {
+ Some(used) => {
+ if used != color {
+ buf.push_str(used.get_suffix());
+ buf.push_str(color.get_prefix());
+ }
+ }
+ None => {
+ buf.push_str(color.get_prefix());
+ used_color = Some(color);
+ }
+ }
+ }
+
+ let c = border.top.unwrap_or(' ');
+ if splits.is_empty() {
+ buf.extend(repeat(c).take(width));
+ } else {
+ let mut splits = splits;
+ for i in 0..width {
+ if splits.first() == Some(&i) {
+ buf.push(split_char);
+ splits = &splits[1..];
+ } else {
+ buf.push(c);
+ }
+ }
+ }
+
+ if border.has_right() {
+ if let Some(color) = color.right_top_corner {
+ match used_color {
+ Some(used) => {
+ if used != color {
+ buf.push_str(used.get_suffix());
+ buf.push_str(color.get_prefix());
+ }
+ }
+ None => {
+ buf.push_str(color.get_prefix());
+ used_color = Some(color);
+ }
+ }
+ }
+
+ let c = border.right_top_corner.unwrap_or(' ');
+ buf.push(c);
+ }
+
+ if let Some(used) = used_color {
+ buf.push_str(used.get_suffix());
+ }
+
+ buf.push('\n');
+ }
+
+ fn print_bottom_line(
+ buf: &mut String,
+ border: Border<char>,
+ color: Border<StaticColor>,
+ width: usize,
+ ) {
+ if !border.has_bottom() {
+ return;
+ }
+
+ let mut used_color: Option<StaticColor> = None;
+
+ if border.has_left() {
+ if let Some(color) = color.left_bottom_corner {
+ used_color = Some(color);
+ buf.push_str(color.get_prefix());
+ }
+
+ let c = border.left_bottom_corner.unwrap_or(' ');
+ buf.push(c);
+ }
+
+ if let Some(color) = color.bottom {
+ match used_color {
+ Some(used) => {
+ if used != color {
+ buf.push_str(used.get_suffix());
+ buf.push_str(color.get_prefix());
+ }
+ }
+ None => {
+ buf.push_str(color.get_prefix());
+ used_color = Some(color);
+ }
+ }
+ }
+
+ let c = border.bottom.unwrap_or(' ');
+ buf.extend(repeat(c).take(width));
+
+ if border.has_right() {
+ if let Some(color) = color.right_bottom_corner {
+ match used_color {
+ Some(used) => {
+ if used != color {
+ buf.push_str(used.get_suffix());
+ buf.push_str(color.get_prefix());
+ }
+ }
+ None => {
+ buf.push_str(color.get_prefix());
+ used_color = Some(color);
+ }
+ }
+ }
+
+ let c = border.right_bottom_corner.unwrap_or(' ');
+ buf.push(c);
+ }
+
+ if let Some(used) = used_color {
+ buf.push_str(used.get_suffix());
+ }
+
+ buf.push('\n');
+ }
+
+ fn create_border<T>(borders: Borders<T>) -> Border<T> {
+ Border {
+ top: borders.top,
+ bottom: borders.bottom,
+ left: borders.left,
+ right: borders.right,
+ left_top_corner: borders.top_left,
+ left_bottom_corner: borders.bottom_left,
+ right_top_corner: borders.top_right,
+ right_bottom_corner: borders.bottom_right,
+ }
+ }
+
+ fn config_borders(borders: &mut Borders<char>, ctx: &PrintContext) {
+ // set top_left
+ {
+ if ctx.kv && ctx.kv_is_first {
+ borders.top_left = borders.top_intersection;
+ }
+
+ if ctx.kv && !ctx.kv_is_first {
+ borders.top_left = borders.intersection;
+ }
+
+ if ctx.kv && ctx.list && !ctx.list_is_first {
+ borders.top_left = borders.left_intersection;
+ }
+
+ if ctx.is_first_col && !ctx.is_first_row {
+ borders.top_left = borders.left_intersection;
+ }
+
+ if ctx.lean_top {
+ borders.top_left = borders.top_intersection;
+ }
+
+ if ctx.top_left {
+ borders.top_left = borders.left_intersection;
+ }
+
+ if ctx.top_intersection {
+ borders.top_left = borders.intersection;
+ }
+ }
+
+ if ctx.is_last_col && !ctx.is_first_row {
+ borders.top_right = borders.right_intersection;
+ }
+
+ if !ctx.is_first_col && ctx.is_last_row {
+ borders.bottom_left = borders.bottom_intersection;
+ }
+
+ if !ctx.is_last_row || ctx.no_bottom {
+ cfg_no_bottom_borders(borders);
+ }
+
+ if ctx.no_right {
+ cfg_no_right_borders(borders);
+ }
+ }
+
+ struct ConfigCell(PrintContext);
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for ConfigCell {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ {
+ // we set a horizontal lines to borders to not complicate logic with cleaning it
+
+ let mut borders = *cfg.get_borders();
+ if let Some(line) = cfg.get_horizontal_line(0) {
+ borders.top = line.main;
+ borders.top_left = line.left;
+ borders.top_right = line.right;
+ }
+
+ if let Some(line) = cfg.get_horizontal_line(1) {
+ borders.bottom = line.main;
+ borders.bottom_left = line.left;
+ borders.bottom_right = line.right;
+ }
+
+ cfg.clear_theme();
+ cfg.set_borders(borders);
+ }
+
+ let mut ctx = self.0;
+
+ let has_vertical = cfg.get_borders().has_left();
+ if !ctx.intersections_horizontal.is_empty() && has_vertical {
+ let mut splits = short_splits(&mut ctx.intersections_horizontal, ctx.size.width);
+ squash_splits(&mut splits);
+
+ let c = cfg.get_borders().bottom_intersection.unwrap_or(' ');
+ cfg_set_top_chars(cfg, &splits, c)
+ }
+
+ let has_horizontal = cfg.get_borders().has_top();
+ if !ctx.intersections_vertical.is_empty() && has_horizontal {
+ let mut splits = short_splits(&mut ctx.intersections_vertical, ctx.size.height);
+ squash_splits(&mut splits);
+
+ let c = cfg.get_borders().right_intersection.unwrap_or(' ');
+ cfg_set_left_chars(cfg, &splits, c)
+ }
+
+ let mut borders = *cfg.get_borders();
+
+ // set top_left
+ {
+ if ctx.kv && ctx.kv_is_first {
+ borders.top_left = borders.top_intersection;
+ }
+
+ if ctx.kv && !ctx.kv_is_first {
+ borders.top_left = borders.intersection;
+ }
+
+ if ctx.kv && ctx.list && !ctx.list_is_first {
+ borders.top_left = borders.left_intersection;
+ }
+
+ if ctx.is_first_col && !ctx.is_first_row {
+ borders.top_left = borders.left_intersection;
+ }
+
+ if ctx.lean_top {
+ borders.top_left = borders.top_intersection;
+ }
+
+ if ctx.top_left {
+ borders.top_left = borders.left_intersection;
+ }
+
+ if ctx.top_intersection {
+ borders.top_left = borders.intersection;
+ }
+ }
+
+ if ctx.is_last_col && !ctx.is_first_row {
+ borders.top_right = borders.right_intersection;
+ }
+
+ if !ctx.is_first_col && ctx.is_last_row {
+ borders.bottom_left = borders.bottom_intersection;
+ }
+
+ if !ctx.is_last_row || ctx.no_bottom {
+ cfg_no_bottom_borders(&mut borders);
+ }
+
+ if ctx.no_right {
+ cfg_no_right_borders(&mut borders);
+ }
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ fn cfg_no_bottom_borders(borders: &mut Borders<char>) {
+ borders.bottom = None;
+ borders.bottom_intersection = None;
+ borders.bottom_left = None;
+ borders.bottom_right = None;
+ borders.horizontal = None;
+ }
+
+ fn cfg_no_right_borders(borders: &mut Borders<char>) {
+ borders.right = None;
+ borders.right_intersection = None;
+ borders.top_right = None;
+ borders.bottom_right = None;
+ borders.vertical = None;
+ }
+
+ fn cfg_set_top_chars(cfg: &mut ColoredConfig, list: &[usize], c: char) {
+ for &split in list {
+ let offset = split;
+ cfg.set_horizontal_char((0, 0), c, Offset::Begin(offset));
+ }
+ }
+
+ fn cfg_set_left_chars(cfg: &mut ColoredConfig, list: &[usize], c: char) {
+ for &offset in list {
+ cfg.set_vertical_char((0, 0), c, Offset::Begin(offset));
+ }
+ }
+
+ struct NoTopBorders;
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for NoTopBorders {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let mut borders = *cfg.get_borders();
+ borders.top = None;
+ borders.top_intersection = None;
+ borders.top_left = None;
+ borders.top_right = None;
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ struct NoBottomBorders;
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for NoBottomBorders {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let mut borders = *cfg.get_borders();
+ borders.bottom = None;
+ borders.bottom_intersection = None;
+ borders.bottom_left = None;
+ borders.bottom_right = None;
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ struct NoRightBorders;
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for NoRightBorders {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let mut borders = *cfg.get_borders();
+ borders.top_right = None;
+ borders.bottom_right = None;
+ borders.right = None;
+ borders.right_intersection = None;
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ struct NoLeftBorders;
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for NoLeftBorders {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let mut borders = *cfg.get_borders();
+ borders.top_left = None;
+ borders.bottom_left = None;
+ borders.left = None;
+ borders.left_intersection = None;
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ struct TopLeftChangeTopIntersection;
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for TopLeftChangeTopIntersection {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let mut borders = *cfg.get_borders();
+ borders.top_left = borders.top_intersection;
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ struct TopLeftChangeIntersection;
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for TopLeftChangeIntersection {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let mut borders = *cfg.get_borders();
+ borders.top_left = borders.intersection;
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ struct TopLeftChangeToLeft;
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for TopLeftChangeToLeft {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let mut borders = *cfg.get_borders();
+ borders.top_left = borders.left_intersection;
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ struct TopRightChangeToRight;
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for TopRightChangeToRight {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let mut borders = *cfg.get_borders();
+ borders.top_right = borders.right_intersection;
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ struct BottomLeftChangeSplit;
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for BottomLeftChangeSplit {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let mut borders = *cfg.get_borders();
+ borders.bottom_left = borders.left_intersection;
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ struct BottomLeftChangeSplitToIntersection;
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for BottomLeftChangeSplitToIntersection {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let mut borders = *cfg.get_borders();
+ borders.bottom_left = borders.intersection;
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ struct BottomRightChangeToRight;
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for BottomRightChangeToRight {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let mut borders = *cfg.get_borders();
+ borders.bottom_right = borders.right_intersection;
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ struct BottomLeftChangeToBottomIntersection;
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for BottomLeftChangeToBottomIntersection {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let mut borders = *cfg.get_borders();
+ borders.bottom_left = borders.bottom_intersection;
+
+ cfg.set_borders(borders);
+ }
+ }
+
+ struct SetBottomChars<'a>(&'a [usize], char);
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for SetBottomChars<'_>
+ where
+ R: Records,
+ for<'a> &'a R: Records,
+ for<'a> D: Dimension + Estimate<&'a R, ColoredConfig>,
+ {
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
+ dims.estimate(&*records, cfg);
+
+ let table_width = (0..records.count_columns())
+ .map(|col| dims.get_width(col))
+ .sum::<usize>()
+ + cfg.count_vertical(records.count_columns());
+ let mut current_width = 0;
+
+ for pos in self.0 {
+ current_width += pos;
+ if current_width > table_width {
+ break;
+ }
+
+ let split_char = self.1;
+ cfg.set_horizontal_char((1, 0), split_char, Offset::Begin(current_width));
+
+ current_width += 1;
+ }
+ }
+ }
+
+ struct SetTopChars<'a>(&'a [usize], char);
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for SetTopChars<'_> {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ for &split in self.0 {
+ let offset = split;
+ cfg.set_horizontal_char((0, 0), self.1, Offset::Begin(offset));
+ }
+ }
+ }
+
+ struct SetLeftChars<'a>(&'a [usize], char);
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for SetLeftChars<'_> {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ for &offset in self.0 {
+ cfg.set_vertical_char((0, 0), self.1, Offset::Begin(offset));
+ }
+ }
+ }
+
+ struct GetTopIntersection(char);
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for &mut GetTopIntersection {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ self.0 = cfg.get_borders().top_intersection.unwrap_or(' ');
+ }
+ }
+
+ struct GetBottomIntersection(char);
+
+ impl<R, D> TableOption<R, D, ColoredConfig> for &mut GetBottomIntersection {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ self.0 = cfg.get_borders().bottom_intersection.unwrap_or(' ');
+ }
+ }
+
+ #[derive(Debug, Default)]
+ struct Dimensions {
+ all: HashMap<usize, Dim>,
+ arrays: HashMap<usize, ArrayDimensions>,
+ }
+
+ #[derive(Debug, Default, Clone, Copy)]
+ struct Dim {
+ width: usize,
+ height: usize,
+ }
+
+ impl Dim {
+ fn new(width: usize, height: usize) -> Self {
+ Self { width, height }
+ }
+ }
+
+ #[derive(Debug, Default)]
+ struct ArrayDimensions {
+ max: Dim,
+ index: HashMap<usize, usize>,
+ }
+
+ fn collect_table_dimensions(val: &TableValue, cfg: &CompactMultilineConfig) -> Dimensions {
+ let mut buf = Dimensions::default();
+ let (dim, _) = __collect_table_dims(&mut buf, val, cfg, 0);
+ let _ = buf.all.insert(0, dim);
+ buf
+ }
+
+ fn __collect_table_dims(
+ buf: &mut Dimensions,
+ val: &TableValue,
+ cfg: &CompactMultilineConfig,
+ pos: usize,
+ ) -> (Dim, usize) {
+ match val {
+ TableValue::Cell(text) => (str_dimension(text, cfg), 0),
+ TableValue::Row(list) => {
+ if list.is_empty() {
+ return (empty_dimension(cfg), 0);
+ }
+
+ let mut index = ArrayDimensions {
+ max: Dim::default(),
+ index: HashMap::with_capacity(list.len()),
+ };
+
+ let mut total_width = 0;
+
+ let mut count_elements = list.len();
+ let mut val_pos = pos + 1;
+ for (i, value) in list.iter().enumerate() {
+ let (dim, elements) = __collect_table_dims(buf, value, cfg, val_pos);
+ count_elements += elements;
+
+ total_width += dim.width;
+
+ index.max.width = max(index.max.width, dim.width);
+ index.max.height = max(index.max.height, dim.height);
+
+ let _ = buf.all.insert(val_pos, dim);
+
+ let _ = index.index.insert(i, val_pos);
+
+ val_pos += 1 + elements;
+ }
+
+ let max_height = index.max.height;
+
+ let _ = buf.arrays.insert(pos, index);
+
+ let has_vertical = cfg.get_borders().has_left();
+ total_width += has_vertical as usize * (list.len() - 1);
+
+ (Dim::new(total_width, max_height), count_elements)
+ }
+ TableValue::Column(list) => {
+ if list.is_empty() {
+ return (empty_dimension(cfg), 0);
+ }
+
+ let mut index = ArrayDimensions {
+ max: Dim::default(),
+ index: HashMap::with_capacity(list.len()),
+ };
+
+ let mut total_height = 0;
+
+ let mut count_elements = list.len();
+ let mut val_pos = pos + 1;
+ for (i, value) in list.iter().enumerate() {
+ let (dim, elements) = __collect_table_dims(buf, value, cfg, val_pos);
+ count_elements += elements;
+
+ total_height += dim.height;
+
+ index.max.width = max(index.max.width, dim.width);
+ index.max.height = max(index.max.height, dim.height);
+
+ let _ = buf.all.insert(val_pos, dim);
+
+ let _ = index.index.insert(i, val_pos);
+
+ val_pos += 1 + elements;
+ }
+
+ let max_width = index.max.width;
+
+ let _ = buf.arrays.insert(pos, index);
+
+ let has_horizontal = cfg.get_borders().has_top();
+ total_height += has_horizontal as usize * (list.len() - 1);
+
+ (Dim::new(max_width, total_height), count_elements)
+ }
+ }
+ }
+
+ fn empty_dimension(cfg: &CompactMultilineConfig) -> Dim {
+ Dim::new(get_padding_horizontal(cfg), 1 + get_padding_vertical(cfg))
+ }
+
+ fn str_dimension(text: &str, cfg: &CompactMultilineConfig) -> Dim {
+ let (count_lines, width) = string_dimension(text);
+ let w = width + get_padding_horizontal(cfg);
+ let h = count_lines + get_padding_vertical(cfg);
+ Dim::new(w, h)
+ }
+
+ fn get_padding_horizontal(cfg: &CompactMultilineConfig) -> usize {
+ let pad = cfg.get_padding();
+ pad.left.size + pad.right.size
+ }
+
+ fn get_padding_vertical(cfg: &CompactMultilineConfig) -> usize {
+ let pad = cfg.get_padding();
+ pad.top.size + pad.bottom.size
+ }
+
+ fn split_value(value: usize, by: usize) -> (usize, usize) {
+ let val = value / by;
+ let rest = value - val * by;
+ (val, rest)
+ }
+
+ fn indent_vertical(al: AlignmentVertical, available: usize, real: usize) -> (usize, usize) {
+ let top = indent_top(al, available, real);
+ let bottom = available - real - top;
+ (top, bottom)
+ }
+
+ fn indent_horizontal(al: AlignmentHorizontal, available: usize, real: usize) -> (usize, usize) {
+ let top = indent_left(al, available, real);
+ let right = available - real - top;
+ (top, right)
+ }
+
+ fn indent_top(al: AlignmentVertical, available: usize, real: usize) -> usize {
+ match al {
+ AlignmentVertical::Top => 0,
+ AlignmentVertical::Bottom => available - real,
+ AlignmentVertical::Center => (available - real) / 2,
+ }
+ }
+
+ fn indent_left(al: AlignmentHorizontal, available: usize, real: usize) -> usize {
+ match al {
+ AlignmentHorizontal::Left => 0,
+ AlignmentHorizontal::Right => available - real,
+ AlignmentHorizontal::Center => (available - real) / 2,
+ }
+ }
+
+ fn short_splits(splits: &mut Vec<usize>, width: usize) -> Vec<usize> {
+ if splits.is_empty() {
+ return Vec::new();
+ }
+
+ let mut out = Vec::new();
+ let mut pos = 0;
+ for &split in splits.iter() {
+ if pos + split >= width {
+ break;
+ }
+
+ pos += split;
+ out.push(pos);
+ }
+
+ let _ = splits.drain(..out.len());
+
+ if !splits.is_empty() && pos <= width {
+ let rest = width - pos;
+ splits[0] -= rest;
+ }
+
+ out
+ }
+
+ fn short_splits3(splits: &mut Vec<usize>, width: usize) -> (bool, Vec<usize>) {
+ if splits.is_empty() {
+ return (false, Vec::new());
+ }
+
+ let mut out = Vec::new();
+ let mut pos = 0;
+ for &split in splits.iter() {
+ if pos + split >= width {
+ break;
+ }
+
+ pos += split + 1;
+ out.push(split);
+ }
+
+ let _ = splits.drain(..out.len());
+
+ if splits.is_empty() {
+ return (false, out);
+ }
+
+ if pos <= width {
+ splits[0] -= width - pos;
+ if splits[0] > 0 {
+ splits[0] -= 1;
+ } else {
+ let _ = splits.remove(0);
+ return (true, out);
+ }
+ }
+
+ (false, out)
+ }
+
+ fn squash_splits(splits: &mut [usize]) {
+ splits.iter_mut().enumerate().for_each(|(i, s)| *s += i);
+ }
+
+ fn set_margin(table: &str, margin: Sides<Indent>, color: Sides<Option<StaticColor>>) -> String {
+ if table.is_empty() {
+ return String::new();
+ }
+
+ let mut buf = String::new();
+ let width = string_width_multiline(table);
+ let top_color = color.top;
+ let bottom_color = color.bottom;
+ let left_color = color.left;
+ let right_color = color.right;
+ for _ in 0..margin.top.size {
+ print_chars(&mut buf, margin.left.fill, left_color, margin.left.size);
+ print_chars(&mut buf, margin.top.fill, top_color, width);
+ print_chars(&mut buf, margin.right.fill, right_color, margin.right.size);
+ buf.push('\n');
+ }
+
+ for line in get_lines(table) {
+ print_chars(&mut buf, margin.left.fill, left_color, margin.left.size);
+ buf.push_str(&line);
+ print_chars(&mut buf, margin.right.fill, right_color, margin.right.size);
+ buf.push('\n');
+ }
+
+ for _ in 0..margin.bottom.size {
+ print_chars(&mut buf, margin.left.fill, left_color, margin.left.size);
+ print_chars(&mut buf, margin.bottom.fill, bottom_color, width);
+ print_chars(&mut buf, margin.right.fill, right_color, margin.right.size);
+ buf.push('\n');
+ }
+
+ let _ = buf.remove(buf.len() - 1);
+
+ buf
+ }
+
+ fn convert_border_colors(pad_color: Sides<StaticColor>) -> Sides<Option<StaticColor>> {
+ Sides::new(
+ (!pad_color.left.is_empty()).then(|| pad_color.left),
+ (!pad_color.right.is_empty()).then(|| pad_color.right),
+ (!pad_color.top.is_empty()).then(|| pad_color.top),
+ (!pad_color.bottom.is_empty()).then(|| pad_color.bottom),
+ )
+ }
+}
diff --git a/vendor/tabled/src/tables/util/mod.rs b/vendor/tabled/src/tables/util/mod.rs
new file mode 100644
index 000000000..ea5453b8f
--- /dev/null
+++ b/vendor/tabled/src/tables/util/mod.rs
@@ -0,0 +1,2 @@
+#[cfg(feature = "std")]
+pub(crate) mod utf8_writer;
diff --git a/vendor/tabled/src/tables/util/utf8_writer.rs b/vendor/tabled/src/tables/util/utf8_writer.rs
new file mode 100644
index 000000000..d4fc515df
--- /dev/null
+++ b/vendor/tabled/src/tables/util/utf8_writer.rs
@@ -0,0 +1,29 @@
+use std::fmt;
+use std::io;
+
+pub(crate) struct UTF8Writer<W>(W);
+
+impl<W> UTF8Writer<W> {
+ pub(crate) fn new(writer: W) -> Self {
+ Self(writer)
+ }
+}
+
+impl<W> fmt::Write for UTF8Writer<W>
+where
+ W: io::Write,
+{
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ let mut buf = s.as_bytes();
+ loop {
+ let n = self.0.write(buf).map_err(|_| fmt::Error)?;
+ if n == buf.len() {
+ break;
+ }
+
+ buf = &buf[n..];
+ }
+
+ Ok(())
+ }
+}