diff options
Diffstat (limited to 'vendor/tabled/src')
111 files changed, 20873 insertions, 0 deletions
diff --git a/vendor/tabled/src/builder/index_builder.rs b/vendor/tabled/src/builder/index_builder.rs new file mode 100644 index 000000000..081f580df --- /dev/null +++ b/vendor/tabled/src/builder/index_builder.rs @@ -0,0 +1,318 @@ +use crate::Table; + +use super::Builder; + +/// [`IndexBuilder`] helps to add an index to the table. +/// +/// Index is a column on the left of the table. +/// +/// It also can be used to transpose the table. +/// +/// Creates a new [`IndexBuilder`] instance. +/// +/// It creates a default index a range from 0 to N. (N - count rows) +/// It also sets a default columns to the range 0 .. N (N - count columns). +///nfo<'a> +/// # Example +/// +/// ``` +/// use tabled::builder::Builder; +/// +/// let mut builder = Builder::default(); +/// builder.set_header(["i", "col-1", "col-2"]); +/// builder.push_record(["0", "value-1", "value-2"]); +/// +/// let table = builder.index().build().to_string(); +/// +/// assert_eq!( +/// table, +/// "+---+---+---------+---------+\n\ +/// | | i | col-1 | col-2 |\n\ +/// +---+---+---------+---------+\n\ +/// | 0 | 0 | value-1 | value-2 |\n\ +/// +---+---+---------+---------+" +/// ) +/// ``` +/// +/// # Example +/// +/// ``` +/// use tabled::builder::Builder; +/// +/// let table = Builder::default() +/// .index() +/// .build(); +/// ``` +#[derive(Debug, Clone)] +pub struct IndexBuilder { + /// Index is an index data. + /// It's always set. + index: Vec<String>, + /// Name of an index + name: Option<String>, + /// A flag which checks if we need to actually use index. + /// + /// It might happen when it's only necessary to [`Self::transpose`] table. + print_index: bool, + /// A flag which checks if table was transposed. + transposed: bool, + /// Data originated in [`Builder`]. + data: Vec<Vec<String>>, +} + +impl IndexBuilder { + /// No flag makes builder to not use an index. + /// + /// It may be useful when only [`Self::transpose`] need to be used. + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder.set_header(["i", "col-1", "col-2"]); + /// builder.push_record(["0", "value-1", "value-2"]); + /// builder.push_record(["2", "value-3", "value-4"]); + /// + /// let table = builder.index().hide().build().to_string(); + /// + /// assert_eq!( + /// table, + /// "+---+---------+---------+\n\ + /// | i | col-1 | col-2 |\n\ + /// +---+---------+---------+\n\ + /// | 0 | value-1 | value-2 |\n\ + /// +---+---------+---------+\n\ + /// | 2 | value-3 | value-4 |\n\ + /// +---+---------+---------+" + /// ) + /// ``` + pub fn hide(mut self) -> Self { + self.print_index = false; + self + } + + /// Set an index name. + /// + /// When [`None`] the name won't be used. + /// + /// # Example + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder.set_header(["i", "column1", "column2"]); + /// builder.push_record(["0", "value1", "value2"]); + /// + /// let table = builder.index() + /// .column(1) + /// .name(Some(String::from("index"))) + /// .build(); + /// + /// assert_eq!( + /// table.to_string(), + /// "+--------+---+---------+\n\ + /// | | i | column2 |\n\ + /// +--------+---+---------+\n\ + /// | index | | |\n\ + /// +--------+---+---------+\n\ + /// | value1 | 0 | value2 |\n\ + /// +--------+---+---------+" + /// ) + /// ``` + pub fn name(mut self, name: Option<String>) -> Self { + self.name = name; + self + } + + /// Sets a index to the chosen column. + /// + /// Also sets a name of the index to the column name. + /// + /// # Example + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder.set_header(["i", "column1", "column2"]); + /// builder.push_record(["0", "value1", "value2"]); + /// + /// let table = builder.index().column(1).build(); + /// + /// assert_eq!( + /// table.to_string(), + /// "+---------+---+---------+\n\ + /// | | i | column2 |\n\ + /// +---------+---+---------+\n\ + /// | column1 | | |\n\ + /// +---------+---+---------+\n\ + /// | value1 | 0 | value2 |\n\ + /// +---------+---+---------+" + /// ) + /// ``` + pub fn column(mut self, column: usize) -> Self { + if column >= matrix_count_columns(&self.data) { + return self; + } + + self.index = get_column(&mut self.data, column); + + let name = remove_or_default(&mut self.index, 0); + self.name = Some(name); + + self + } + + /// Transpose index and columns. + /// + /// # Example + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder.set_header(["i", "column-1", "column-2", "column-3"]); + /// builder.push_record(["0", "value-1", "value-2", "value-3"]); + /// builder.push_record(["1", "value-4", "value-5", "value-6"]); + /// builder.push_record(["2", "value-7", "value-8", "value-9"]); + /// + /// let table = builder.index().column(1).transpose().build(); + /// + /// assert_eq!( + /// table.to_string(), + /// "+----------+---------+---------+---------+\n\ + /// | column-1 | value-1 | value-4 | value-7 |\n\ + /// +----------+---------+---------+---------+\n\ + /// | i | 0 | 1 | 2 |\n\ + /// +----------+---------+---------+---------+\n\ + /// | column-2 | value-2 | value-5 | value-8 |\n\ + /// +----------+---------+---------+---------+\n\ + /// | column-3 | value-3 | value-6 | value-9 |\n\ + /// +----------+---------+---------+---------+" + /// ) + /// ``` + pub fn transpose(mut self) -> Self { + let columns = &mut self.data[0]; + std::mem::swap(&mut self.index, columns); + + let columns = self.data.remove(0); + + make_rows_columns(&mut self.data); + + self.data.insert(0, columns); + + self.transposed = !self.transposed; + + self + } + + /// Builds a table. + pub fn build(self) -> Table { + let builder: Builder = self.into(); + builder.build() + } +} + +impl From<Builder> for IndexBuilder { + fn from(builder: Builder) -> Self { + let has_header = builder.has_header(); + + let mut data: Vec<Vec<_>> = builder.into(); + + if !has_header { + let count_columns = matrix_count_columns(&data); + data.insert(0, build_range_index(count_columns)); + } + + // we exclude first row which contains a header + let data_len = data.len().saturating_sub(1); + let index = build_range_index(data_len); + + Self { + index, + name: None, + print_index: true, + transposed: false, + data, + } + } +} + +impl From<IndexBuilder> for Builder { + fn from(b: IndexBuilder) -> Self { + build_index(b) + } +} + +fn build_index(mut b: IndexBuilder) -> Builder { + if b.index.is_empty() { + return Builder::default(); + } + + // add index column + if b.print_index { + b.index.insert(0, String::default()); + + insert_column(&mut b.data, b.index, 0); + } + + if let Some(name) = b.name { + if b.transposed && b.print_index { + b.data[0][0] = name; + } else { + b.data.insert(1, vec![name]); + } + } + + Builder::from(b.data) +} + +fn build_range_index(n: usize) -> Vec<String> { + (0..n).map(|i| i.to_string()).collect() +} + +fn remove_or_default<T: Default>(v: &mut Vec<T>, i: usize) -> T { + if v.len() > i { + v.remove(i) + } else { + T::default() + } +} + +fn get_column<T: Default>(v: &mut [Vec<T>], col: usize) -> Vec<T> { + let mut column = Vec::with_capacity(v.len()); + for row in v.iter_mut() { + let value = remove_or_default(row, col); + column.push(value); + } + + column +} + +fn make_rows_columns<T: Default>(v: &mut Vec<Vec<T>>) { + let count_columns = matrix_count_columns(v); + + let mut columns = Vec::with_capacity(count_columns); + for _ in 0..count_columns { + let column = get_column(v, 0); + columns.push(column); + } + + v.clear(); + + for column in columns { + v.push(column); + } +} + +fn insert_column<T: Default>(v: &mut [Vec<T>], mut column: Vec<T>, col: usize) { + for row in v.iter_mut() { + let value = remove_or_default(&mut column, col); + row.insert(col, value); + } +} + +fn matrix_count_columns<T>(v: &[Vec<T>]) -> usize { + v.first().map_or(0, |row| row.len()) +} diff --git a/vendor/tabled/src/builder/mod.rs b/vendor/tabled/src/builder/mod.rs new file mode 100644 index 000000000..9002ba237 --- /dev/null +++ b/vendor/tabled/src/builder/mod.rs @@ -0,0 +1,118 @@ +//! Builder module provides a [`Builder`] type which helps building +//! a [`Table`] dynamically. +//! +//! It also contains [`IndexBuilder`] which can help to build a table with index. +//! +//! # Examples +//! +//! Here's an example of [`IndexBuilder`] usage +//! +#![cfg_attr(feature = "derive", doc = "```")] +#![cfg_attr(not(feature = "derive"), doc = "```ignore")] +//! use tabled::{Table, Tabled, settings::Style}; +//! +//! #[derive(Tabled)] +//! struct Mission { +//! name: &'static str, +//! #[tabled(inline)] +//! status: Status, +//! } +//! +//! #[derive(Tabled)] +//! enum Status { +//! Complete, +//! Started, +//! Ready, +//! Unknown, +//! } +//! +//! let data = [ +//! Mission { name: "Algebra", status: Status::Unknown }, +//! Mission { name: "Apolo", status: Status::Complete }, +//! ]; +//! +//! let mut builder = Table::builder(&data) +//! .index() +//! .column(0) +//! .name(None) +//! .transpose(); +//! +//! let mut table = builder.build(); +//! table.with(Style::modern()); +//! +//! println!("{}", table); +//! +//! assert_eq!( +//! table.to_string(), +//! concat!( +//! "┌──────────┬─────────┬───────┐\n", +//! "│ │ Algebra │ Apolo │\n", +//! "├──────────┼─────────┼───────┤\n", +//! "│ Complete │ │ + │\n", +//! "├──────────┼─────────┼───────┤\n", +//! "│ Started │ │ │\n", +//! "├──────────┼─────────┼───────┤\n", +//! "│ Ready │ │ │\n", +//! "├──────────┼─────────┼───────┤\n", +//! "│ Unknown │ + │ │\n", +//! "└──────────┴─────────┴───────┘", +//! ), +//! ) +//! ``` +//! +//! Example when we don't want to show empty data of enum where not all variants are used. +//! +#![cfg_attr(feature = "derive", doc = "```")] +#![cfg_attr(not(feature = "derive"), doc = "```ignore")] +//! use tabled::{Table, Tabled, settings::Style}; +//! +//! #[derive(Tabled)] +//! enum Status { +//! #[tabled(inline)] +//! Complete { +//! started_timestamp: usize, +//! finihsed_timestamp: usize, +//! }, +//! #[tabled(inline)] +//! Started { +//! timestamp: usize, +//! }, +//! Ready, +//! Unknown, +//! } +//! +//! let data = [ +//! Status::Unknown, +//! Status::Complete { started_timestamp: 123, finihsed_timestamp: 234 }, +//! ]; +//! +//! let mut builder = Table::builder(&data); +//! builder.clean(); +//! +//! let table = builder.build() +//! .with(Style::modern()) +//! .to_string(); +//! +//! println!("{}", table); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "┌───────────────────┬────────────────────┬─────────┐\n", +//! "│ started_timestamp │ finihsed_timestamp │ Unknown │\n", +//! "├───────────────────┼────────────────────┼─────────┤\n", +//! "│ │ │ + │\n", +//! "├───────────────────┼────────────────────┼─────────┤\n", +//! "│ 123 │ 234 │ │\n", +//! "└───────────────────┴────────────────────┴─────────┘", +//! ), +//! ) +//! ``` +//! +//! [`Table`]: crate::Table + +mod index_builder; +mod table_builder; + +pub use index_builder::IndexBuilder; +pub use table_builder::Builder; diff --git a/vendor/tabled/src/builder/table_builder.rs b/vendor/tabled/src/builder/table_builder.rs new file mode 100644 index 000000000..40316494e --- /dev/null +++ b/vendor/tabled/src/builder/table_builder.rs @@ -0,0 +1,506 @@ +use std::iter::FromIterator; + +use crate::{grid::records::vec_records::CellInfo, Table}; + +use super::IndexBuilder; + +/// Builder creates a [`Table`] from dynamic data set. +/// +/// It useful when the amount of columns or rows is not known statically. +/// +/// ```rust +/// use tabled::builder::Builder; +/// +/// let mut builder = Builder::default(); +/// builder.set_header(["index", "measure", "value"]); +/// builder.push_record(["0", "weight", "0.443"]); +/// +/// let table = builder.build(); +/// +/// println!("{}", table); +/// ``` +/// +/// It may be useful to use [`FromIterator`] for building. +/// +/// ```rust +/// use tabled::builder::Builder; +/// use std::iter::FromIterator; +/// +/// let data = vec![ +/// ["column1", "column2"], +/// ["data1", "data2"], +/// ["data3", "data4"], +/// ]; +/// +/// let table = Builder::from_iter(data).build(); +/// +/// println!("{}", table); +/// ``` +#[derive(Debug, Default, Clone)] +pub struct Builder { + /// A list of rows. + data: Vec<Vec<CellInfo<String>>>, + /// A columns row. + columns: Option<Vec<CellInfo<String>>>, + /// A number of columns. + count_columns: usize, + /// A flag that the rows are not consistent. + is_consistent: bool, + /// A content of cells which are created in case rows has different length. + empty_cell_text: Option<String>, +} + +impl Builder { + /// Creates a [`Builder`] instance. + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let builder = Builder::new(); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Creates a [`Builder`] instance with a given row capacity. + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::with_capacity(2); + /// builder.push_record((0..3).map(|i| i.to_string())); + /// builder.push_record(["i", "surname", "lastname"]); + /// ``` + pub fn with_capacity(capacity: usize) -> Self { + let mut b = Self::new(); + b.data = Vec::with_capacity(capacity); + + b + } + + /// Sets a [`Table`] header. + /// + /// ``` + /// # use tabled::builder::Builder; + /// let mut builder = Builder::default(); + /// builder.set_header((0..3).map(|i| i.to_string())); + /// ``` + pub fn set_header<H, T>(&mut self, columns: H) -> &mut Self + where + H: IntoIterator<Item = T>, + T: Into<String>, + { + let list = create_row(columns, self.count_columns); + + self.update_size(list.len()); + self.columns = Some(list); + + self + } + + /// Sets off a [`Table`] header. + /// + /// If not set its a nop. + /// + /// ```rust + /// use tabled::Table; + /// + /// let data = [("Hello", 1u8, false), ("World", 21u8, true)]; + /// + /// let table = Table::builder(data).build().to_string(); + /// + /// assert_eq!( + /// table, + /// "+-------+----+-------+\n\ + /// | &str | u8 | bool |\n\ + /// +-------+----+-------+\n\ + /// | Hello | 1 | false |\n\ + /// +-------+----+-------+\n\ + /// | World | 21 | true |\n\ + /// +-------+----+-------+" + /// ); + /// + /// + /// let mut builder = Table::builder(data); + /// builder.remove_header(); + /// let table = builder.build().to_string(); + /// + /// assert_eq!( + /// table, + /// "+-------+----+-------+\n\ + /// | Hello | 1 | false |\n\ + /// +-------+----+-------+\n\ + /// | World | 21 | true |\n\ + /// +-------+----+-------+" + /// ); + /// + /// ``` + pub fn remove_header(&mut self) -> &mut Self { + self.columns = None; + self.count_columns = self.get_size(); + + self + } + + /// Sets a content of cells which are created in case rows has different length. + /// + /// + /// ```rust + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder + /// .set_default_text("undefined") + /// .set_header((0..3).map(|i| i.to_string())) + /// .push_record(["i"]); + /// ``` + pub fn set_default_text<T>(&mut self, text: T) -> &mut Self + where + T: Into<String>, + { + self.empty_cell_text = Some(text.into()); + self + } + + /// Build creates a [`Table`] instance. + /// + /// ```rust + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder.set_header(["i", "column1", "column2"]); + /// builder.push_record(["0", "value1", "value2"]); + /// ``` + pub fn build(self) -> Table { + Table::from(self) + } + + /// Add an index to the [`Table`]. + /// + /// Default index is a range 0-N where N is amount of records. + /// + /// # Example + /// + /// ``` + /// use tabled::Table; + /// + /// let table = Table::builder(&["Hello", "World", "!"]).index().build(); + /// + /// assert_eq!( + /// table.to_string(), + /// "+---+-------+\n\ + /// | | &str |\n\ + /// +---+-------+\n\ + /// | 0 | Hello |\n\ + /// +---+-------+\n\ + /// | 1 | World |\n\ + /// +---+-------+\n\ + /// | 2 | ! |\n\ + /// +---+-------+" + /// ) + /// ``` + pub fn index(self) -> IndexBuilder { + IndexBuilder::from(self) + } + + /// Adds a row to a [`Table`]. + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder.push_record((0..3).map(|i| i.to_string())); + /// builder.push_record(["i", "surname", "lastname"]); + /// ``` + pub fn push_record<R, T>(&mut self, row: R) -> &mut Self + where + R: IntoIterator<Item = T>, + T: Into<String>, + { + let list = create_row(row, self.count_columns); + + self.update_size(list.len()); + self.data.push(list); + + self + } + + /// Insert a row into a specific position. + /// + /// # Panics + /// + /// Panics if `index > count_rows`. + pub fn insert_record<R>(&mut self, index: usize, record: R) -> bool + where + R: IntoIterator, + R::Item: Into<String>, + { + let list = create_row(record, self.count_columns); + + self.update_size(list.len()); + self.data.insert(index, list); + + true + } + + /// Clean removes empty columns and rows. + /// + /// # Example + /// + /// ``` + /// use tabled::Table; + /// + /// let mut builder = Table::builder(&["Hello", "World", ""]); + /// builder.clean(); + /// + /// let table = builder.build(); + /// + /// assert_eq!( + /// table.to_string(), + /// "+-------+\n\ + /// | &str |\n\ + /// +-------+\n\ + /// | Hello |\n\ + /// +-------+\n\ + /// | World |\n\ + /// +-------+" + /// ) + /// ``` + pub fn clean(&mut self) -> &mut Self { + self.clean_columns(); + self.clean_rows(); + self + } + + /// Set a column size. + /// + /// If it make it lower then it was originally it is considered NOP. + pub fn hint_column_size(&mut self, size: usize) -> &mut Self { + self.count_columns = size; + self.is_consistent = true; + self + } + + /// Returns an amount of columns which would be present in a built table. + pub fn count_columns(&self) -> usize { + self.count_columns + } + + /// Returns an amount of rows which would be present in a built table. + pub fn count_rows(&self) -> usize { + self.data.len() + } + + /// Checks whether a builder contains a header set. + pub fn has_header(&self) -> bool { + self.columns.is_some() + } + + fn clean_columns(&mut self) { + let mut i = 0; + for col in 0..self.count_columns { + let col = col - i; + + let mut is_empty = true; + for row in 0..self.data.len() { + let cell = &self.data[row][col]; + if !cell.as_ref().is_empty() { + is_empty = false; + break; + } + } + + if is_empty { + for row in 0..self.data.len() { + let _ = self.data[row].remove(col); + } + + if let Some(columns) = self.columns.as_mut() { + if columns.len() > col { + let _ = columns.remove(col); + } + } + + i += 1; + } + } + + self.count_columns -= i; + } + + fn clean_rows(&mut self) { + for row in (0..self.data.len()).rev() { + let mut is_empty = true; + for col in 0..self.count_columns { + let cell = &self.data[row][col]; + if !cell.as_ref().is_empty() { + is_empty = false; + break; + } + } + + if is_empty { + let _ = self.data.remove(row); + } + + if row == 0 { + break; + } + } + } + + fn update_size(&mut self, size: usize) { + use std::cmp::Ordering; + + match size.cmp(&self.count_columns) { + Ordering::Less => { + if !self.data.is_empty() { + self.is_consistent = false; + } + } + Ordering::Greater => { + self.count_columns = size; + + if !self.data.is_empty() || self.columns.is_some() { + self.is_consistent = false; + } + } + Ordering::Equal => (), + } + } + + fn get_size(&mut self) -> usize { + let mut max = self.columns.as_ref().map_or(0, Vec::len); + let max_records = self.data.iter().map(Vec::len).max().unwrap_or(0); + max = std::cmp::max(max_records, max); + + max + } + + fn fix_rows(&mut self) { + let empty_cell = self.empty_cell_text.to_owned().unwrap_or_default(); + let empty = CellInfo::new(empty_cell); + + if let Some(header) = self.columns.as_mut() { + if self.count_columns > header.len() { + let count = self.count_columns - header.len(); + append_vec(header, empty.clone(), count); + } + } + + for row in &mut self.data { + if self.count_columns > row.len() { + let count = self.count_columns - row.len(); + append_vec(row, empty.clone(), count); + } + } + } +} + +impl From<Builder> for Vec<Vec<String>> { + fn from(mut builder: Builder) -> Self { + if !builder.is_consistent { + builder.fix_rows(); + } + + if let Some(columns) = builder.columns { + builder.data.insert(0, columns); + } + + builder + .data + .into_iter() + .map(|row| row.into_iter().map(|c| c.into_inner()).collect()) + .collect() + } +} + +impl From<Builder> for Vec<Vec<CellInfo<String>>> { + fn from(mut builder: Builder) -> Self { + if !builder.is_consistent { + builder.fix_rows(); + } + + if let Some(columns) = builder.columns { + builder.data.insert(0, columns); + } + + builder.data + } +} + +impl<R, V> FromIterator<R> for Builder +where + R: IntoIterator<Item = V>, + V: Into<String>, +{ + fn from_iter<T: IntoIterator<Item = R>>(iter: T) -> Self { + let mut builder = Self::default(); + for row in iter { + let _ = builder.push_record(row); + } + + builder + } +} + +impl<D> Extend<D> for Builder +where + D: Into<String>, +{ + fn extend<T: IntoIterator<Item = D>>(&mut self, iter: T) { + let _ = self.push_record(iter); + } +} + +impl From<Vec<Vec<String>>> for Builder { + fn from(data: Vec<Vec<String>>) -> Self { + let count_columns = data.get(0).map_or(0, |row| row.len()); + + let data = data + .into_iter() + .map(|row| row.into_iter().map(CellInfo::new).collect()) + .collect(); + + Self { + data, + count_columns, + columns: None, + is_consistent: false, + empty_cell_text: None, + } + } +} + +impl From<Vec<Vec<CellInfo<String>>>> for Builder { + fn from(data: Vec<Vec<CellInfo<String>>>) -> Self { + let count_columns = data.get(0).map_or(0, |row| row.len()); + + Self { + data, + count_columns, + columns: None, + is_consistent: false, + empty_cell_text: None, + } + } +} + +fn create_row<R, T>(row: R, size: usize) -> Vec<CellInfo<String>> +where + R: IntoIterator<Item = T>, + T: Into<String>, +{ + let mut list = Vec::with_capacity(size); + for text in row { + let text = text.into(); + let info = CellInfo::new(text); + list.push(info); + } + + list +} + +fn append_vec<T: Clone>(v: &mut Vec<T>, value: T, n: usize) { + v.extend((0..n).map(|_| value.clone())); +} diff --git a/vendor/tabled/src/grid/colored_config.rs b/vendor/tabled/src/grid/colored_config.rs new file mode 100644 index 000000000..51a00fbf4 --- /dev/null +++ b/vendor/tabled/src/grid/colored_config.rs @@ -0,0 +1,112 @@ +use std::ops::{Deref, DerefMut}; + +use crate::grid::{ + color::AnsiColor, + config::{Entity, EntityMap, SpannedConfig}, +}; + +/// A spanned configuration plus colors for cells. +#[derive(Debug, Default, PartialEq, Eq, Clone)] +pub struct ColoredConfig { + config: SpannedConfig, + colors: ColorMap, +} + +impl ColoredConfig { + /// Create a new colored config. + pub fn new(config: SpannedConfig) -> Self { + Self { + config, + colors: ColorMap::default(), + } + } + + /// Set a color for a given cell. + /// + /// The outcome is the same as if you'd use [`Format`] and added a color but it'd work only with `color` feature on. + /// While this method works in all contexts. + /// + /// [`Format`]: crate::settings::Format + pub fn set_color(&mut self, pos: Entity, color: AnsiColor<'static>) -> &mut Self { + match self.colors.0.as_mut() { + Some(map) => map.insert(pos, color), + None => { + let mut colors = EntityMap::default(); + colors.insert(pos, color); + self.colors = ColorMap(Some(colors)); + } + } + + self + } + + /// Set a list of colors. + pub fn set_colors(&mut self, colors: EntityMap<AnsiColor<'static>>) -> &mut Self { + self.colors = ColorMap(Some(colors)); + self + } + + /// Remove a color for a given cell. + pub fn remove_color(&mut self, pos: Entity) -> &mut Self { + if let Some(colors) = self.colors.0.as_mut() { + colors.remove(pos); + } + + self + } + + /// Returns a list of colors. + pub fn get_colors(&self) -> &ColorMap { + &self.colors + } + + /// Returns an inner config. + pub fn into_inner(self) -> SpannedConfig { + self.config + } +} + +impl Deref for ColoredConfig { + type Target = SpannedConfig; + + fn deref(&self) -> &Self::Target { + &self.config + } +} + +impl DerefMut for ColoredConfig { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.config + } +} + +impl From<SpannedConfig> for ColoredConfig { + fn from(value: SpannedConfig) -> Self { + Self::new(value) + } +} + +impl AsRef<SpannedConfig> for ColoredConfig { + fn as_ref(&self) -> &SpannedConfig { + &self.config + } +} + +/// A colors structure for [`ColoredConfig`]. +#[derive(Debug, Default, PartialEq, Eq, Clone)] +pub struct ColorMap(Option<EntityMap<AnsiColor<'static>>>); + +impl ColorMap { + /// Checks if any colors is set on. + pub fn is_empty(&self) -> bool { + self.0.is_none() + } +} + +impl crate::grid::colors::Colors for ColorMap { + type Color = AnsiColor<'static>; + + fn get_color(&self, (row, col): (usize, usize)) -> Option<&Self::Color> { + self.0.as_ref().map(|map| map.get(Entity::Cell(row, col))) + } +} diff --git a/vendor/tabled/src/grid/compact_multiline_config.rs b/vendor/tabled/src/grid/compact_multiline_config.rs new file mode 100644 index 000000000..c9056c911 --- /dev/null +++ b/vendor/tabled/src/grid/compact_multiline_config.rs @@ -0,0 +1,211 @@ +use crate::grid::color::StaticColor; +use crate::grid::config::{ + AlignmentHorizontal, AlignmentVertical, Borders, CompactConfig, Indent, Line, Sides, +}; + +/// A [`CompactConfig`] configuration plus vertical alignment. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CompactMultilineConfig { + config: CompactConfig, + alignment_vertical: AlignmentVertical, + formatting: Formatting, +} + +impl CompactMultilineConfig { + /// Create a new colored config. + pub fn new(config: CompactConfig) -> Self { + Self::from(config) + } + + /// Set a horizontal alignment. + pub const fn set_alignment_vertical(mut self, alignment: AlignmentVertical) -> Self { + self.alignment_vertical = alignment; + self + } + + /// Get a alignment horizontal. + pub const fn get_alignment_vertical(&self) -> AlignmentVertical { + self.alignment_vertical + } + + /// Set grid margin. + pub const fn set_margin(mut self, margin: Sides<Indent>) -> Self { + self.config = self.config.set_margin(margin); + self + } + + /// Returns a grid margin. + pub const fn get_margin(&self) -> &Sides<Indent> { + self.config.get_margin() + } + + /// Set the [`Borders`] value as correct one. + pub const fn set_borders(mut self, borders: Borders<char>) -> Self { + self.config = self.config.set_borders(borders); + self + } + + /// Set the first horizontal line. + /// + /// It ignores the [`Borders`] horizontal value if set for 1st row. + pub const fn set_first_horizontal_line(mut self, line: Line<char>) -> Self { + self.config = self.config.set_first_horizontal_line(line); + self + } + + /// Set the first horizontal line. + /// + /// It ignores the [`Borders`] horizontal value if set for 1st row. + pub const fn get_first_horizontal_line(&self) -> Option<Line<char>> { + self.config.get_first_horizontal_line() + } + + /// Returns a current [`Borders`] structure. + pub const fn get_borders(&self) -> &Borders<char> { + self.config.get_borders() + } + + /// Returns a current [`Borders`] structure. + pub const fn get_borders_color(&self) -> &Borders<StaticColor> { + self.config.get_borders_color() + } + + /// Set a padding to a given cells. + pub const fn set_padding(mut self, padding: Sides<Indent>) -> Self { + self.config = self.config.set_padding(padding); + self + } + + /// Get a padding for a given. + pub const fn get_padding(&self) -> &Sides<Indent> { + self.config.get_padding() + } + + /// Set a horizontal alignment. + pub const fn set_alignment_horizontal(mut self, alignment: AlignmentHorizontal) -> Self { + self.config = self.config.set_alignment_horizontal(alignment); + self + } + + /// Get a alignment horizontal. + pub const fn get_alignment_horizontal(&self) -> AlignmentHorizontal { + self.config.get_alignment_horizontal() + } + + /// Sets colors of border carcass on the grid. + pub const fn set_borders_color(mut self, borders: Borders<StaticColor>) -> Self { + self.config = self.config.set_borders_color(borders); + self + } + + /// Set colors for a margin. + pub const fn set_margin_color(mut self, color: Sides<StaticColor>) -> Self { + self.config = self.config.set_margin_color(color); + self + } + + /// Returns a margin color. + pub const fn get_margin_color(&self) -> Sides<StaticColor> { + self.config.get_margin_color() + } + + /// Set a padding color to all cells. + pub const fn set_padding_color(mut self, color: Sides<StaticColor>) -> Self { + self.config = self.config.set_padding_color(color); + self + } + + /// get a padding color. + pub const fn get_padding_color(&self) -> Sides<StaticColor> { + self.config.get_padding_color() + } + + /// Set formatting. + pub const fn set_formatting(mut self, formatting: Formatting) -> Self { + self.formatting = formatting; + self + } + + /// Get formatting. + pub const fn get_formatting(&self) -> Formatting { + self.formatting + } +} + +impl Default for CompactMultilineConfig { + fn default() -> Self { + Self { + config: Default::default(), + alignment_vertical: AlignmentVertical::Top, + formatting: Formatting::default(), + } + } +} + +impl From<CompactConfig> for CompactMultilineConfig { + fn from(config: CompactConfig) -> Self { + Self { + config, + alignment_vertical: AlignmentVertical::Top, + formatting: Formatting::default(), + } + } +} + +impl AsRef<CompactConfig> for CompactMultilineConfig { + fn as_ref(&self) -> &CompactConfig { + &self.config + } +} + +impl AsMut<CompactConfig> for CompactMultilineConfig { + fn as_mut(&mut self) -> &mut CompactConfig { + &mut self.config + } +} + +#[cfg(feature = "std")] +impl From<CompactMultilineConfig> for crate::grid::config::SpannedConfig { + fn from(compact: CompactMultilineConfig) -> Self { + use crate::grid::config::Entity; + + let mut cfg = crate::grid::config::SpannedConfig::from(compact.config); + cfg.set_alignment_vertical(Entity::Global, compact.alignment_vertical); + cfg.set_formatting(Entity::Global, compact.formatting.into()); + + cfg + } +} + +/// Formatting represent a logic of formatting of a cell. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Formatting { + /// An setting to allow horizontal trim. + pub horizontal_trim: bool, + /// An setting to allow vertical trim. + pub vertical_trim: bool, + /// An setting to allow alignment per line. + pub allow_lines_alignment: bool, +} + +impl Formatting { + /// Creates a new [`Formatting`] structure. + pub fn new(horizontal_trim: bool, vertical_trim: bool, allow_lines_alignment: bool) -> Self { + Self { + horizontal_trim, + vertical_trim, + allow_lines_alignment, + } + } +} + +#[cfg(feature = "std")] +impl From<Formatting> for crate::grid::config::Formatting { + fn from(val: Formatting) -> Self { + crate::grid::config::Formatting { + allow_lines_alignment: val.allow_lines_alignment, + horizontal_trim: val.horizontal_trim, + vertical_trim: val.vertical_trim, + } + } +} diff --git a/vendor/tabled/src/grid/dimension/complete_dimension.rs b/vendor/tabled/src/grid/dimension/complete_dimension.rs new file mode 100644 index 000000000..3147cb27a --- /dev/null +++ b/vendor/tabled/src/grid/dimension/complete_dimension.rs @@ -0,0 +1,136 @@ +use std::borrow::Cow; + +use crate::grid::{ + config::{ColoredConfig, SpannedConfig}, + dimension::{Dimension, Estimate, SpannedGridDimension}, + records::Records, +}; + +/// CompleteDimension is a [`Dimension`] implementation for a [`Table`] +/// +/// [`Table`]: crate::Table +#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub struct CompleteDimension<'a> { + width: Option<Cow<'a, [usize]>>, + height: Option<Cow<'a, [usize]>>, +} + +impl CompleteDimension<'_> { + /// Checks whether is the dimensions is set. + pub fn is_complete(&self) -> bool { + self.width.is_some() && self.height.is_some() + } + + /// Checks whether is nothing was set. + pub fn is_empty(&self) -> bool { + self.width.is_none() && self.height.is_none() + } + + /// Set column widths. + /// + /// In general the method is only considered to be useful to a [`TableOption`]. + /// + /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided widths. + /// + /// [`TableOption`]: crate::settings::TableOption + pub fn set_widths(&mut self, columns: Vec<usize>) -> bool { + self.width = Some(Cow::Owned(columns)); + + true + } + + /// Set rows heights. + /// + /// In general the method is only considered to be useful to a [`TableOption`]. + /// + /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided heights. + /// + /// [`TableOption`]: crate::settings::TableOption + pub fn set_heights(&mut self, rows: Vec<usize>) -> bool { + self.height = Some(Cow::Owned(rows)); + + true + } + + /// Force width estimation. + pub fn clear_width(&mut self) { + self.width = None; + } + + /// Force height estimation. + pub fn clear_height(&mut self) { + self.height = None; + } + + /// Copies a reference from self. + pub fn from_origin(&self) -> CompleteDimension<'_> { + let width = self.width.as_deref().map(Cow::Borrowed); + let height = self.height.as_deref().map(Cow::Borrowed); + + CompleteDimension { width, height } + } +} + +impl Dimension for CompleteDimension<'_> { + fn get_width(&self, column: usize) -> usize { + let width = self + .width + .as_ref() + .expect("It must always be Some at this point"); + + width[column] + } + + fn get_height(&self, row: usize) -> usize { + let height = self + .height + .as_ref() + .expect("It must always be Some at this point"); + + height[row] + } +} + +impl<R: Records> Estimate<R, SpannedConfig> for CompleteDimension<'_> { + fn estimate(&mut self, records: R, cfg: &SpannedConfig) { + match (self.width.is_some(), self.height.is_some()) { + (true, true) => {} + (true, false) => { + self.height = Some(Cow::Owned(SpannedGridDimension::height(records, cfg))); + } + (false, true) => { + self.width = Some(Cow::Owned(SpannedGridDimension::width(records, cfg))); + } + (false, false) => { + let mut dims = SpannedGridDimension::default(); + dims.estimate(records, cfg); + + let (width, height) = dims.get_values(); + self.width = Some(Cow::Owned(width)); + self.height = Some(Cow::Owned(height)); + } + } + } +} + +impl<R: Records> Estimate<R, ColoredConfig> for CompleteDimension<'_> { + fn estimate(&mut self, records: R, cfg: &ColoredConfig) { + match (self.width.is_some(), self.height.is_some()) { + (true, true) => {} + (true, false) => { + self.height = Some(Cow::Owned(SpannedGridDimension::height(records, cfg))); + } + (false, true) => { + self.width = Some(Cow::Owned(SpannedGridDimension::width(records, cfg))); + } + (false, false) => { + let mut dims = SpannedGridDimension::default(); + dims.estimate(records, cfg); + + let (width, height) = dims.get_values(); + self.width = Some(Cow::Owned(width)); + self.height = Some(Cow::Owned(height)); + } + } + } +} diff --git a/vendor/tabled/src/grid/dimension/complete_dimension_vec_records.rs b/vendor/tabled/src/grid/dimension/complete_dimension_vec_records.rs new file mode 100644 index 000000000..ddc806a45 --- /dev/null +++ b/vendor/tabled/src/grid/dimension/complete_dimension_vec_records.rs @@ -0,0 +1,128 @@ +use std::borrow::Cow; + +use papergrid::{ + dimension::spanned_vec_records::SpannedVecRecordsDimension, records::vec_records::VecRecords, +}; + +use crate::grid::{ + config::{ColoredConfig, SpannedConfig}, + dimension::{Dimension, Estimate}, + records::vec_records::Cell, +}; + +/// CompleteDimension is a [`Dimension`] implementation for a [`Table`] +/// +/// [`Table`]: crate::Table +#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub struct CompleteDimensionVecRecords<'a> { + width: Option<Cow<'a, [usize]>>, + height: Option<Cow<'a, [usize]>>, +} + +impl CompleteDimensionVecRecords<'_> { + /// Checks whether is the dimensions is set. + pub fn is_complete(&self) -> bool { + self.width.is_some() && self.height.is_some() + } + + /// Checks whether is nothing was set. + pub fn is_empty(&self) -> bool { + self.width.is_none() && self.height.is_none() + } + + /// Set column widths. + /// + /// In general the method is only considered to be useful to a [`TableOption`]. + /// + /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided widths. + /// + /// [`TableOption`]: crate::settings::TableOption + pub fn set_widths(&mut self, columns: Vec<usize>) -> bool { + self.width = Some(Cow::Owned(columns)); + + true + } + + /// Set rows heights. + /// + /// In general the method is only considered to be useful to a [`TableOption`]. + /// + /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided heights. + /// + /// [`TableOption`]: crate::settings::TableOption + pub fn set_heights(&mut self, rows: Vec<usize>) -> bool { + self.height = Some(Cow::Owned(rows)); + + true + } + + /// Force width estimation. + pub fn clear_width(&mut self) { + self.width = None; + } + + /// Force height estimation. + pub fn clear_height(&mut self) { + self.height = None; + } + + /// Copies a reference from self. + pub fn from_origin(&self) -> CompleteDimensionVecRecords<'_> { + let width = self.width.as_deref().map(Cow::Borrowed); + let height = self.height.as_deref().map(Cow::Borrowed); + + CompleteDimensionVecRecords { width, height } + } +} + +impl Dimension for CompleteDimensionVecRecords<'_> { + fn get_width(&self, column: usize) -> usize { + let width = self + .width + .as_ref() + .expect("It must always be Some at this point"); + + width[column] + } + + fn get_height(&self, row: usize) -> usize { + let height = self + .height + .as_ref() + .expect("It must always be Some at this point"); + + height[row] + } +} + +impl<T: AsRef<str> + Cell> Estimate<&VecRecords<T>, SpannedConfig> + for CompleteDimensionVecRecords<'_> +{ + fn estimate(&mut self, records: &VecRecords<T>, cfg: &SpannedConfig) { + match (self.width.is_some(), self.height.is_some()) { + (true, true) => {} + (true, false) => { + self.height = Some(Cow::Owned(SpannedVecRecordsDimension::height(records, cfg))); + } + (false, true) => { + self.width = Some(Cow::Owned(SpannedVecRecordsDimension::width(records, cfg))); + } + (false, false) => { + let mut dims = SpannedVecRecordsDimension::default(); + dims.estimate(records, cfg); + + let (width, height) = dims.get_values(); + self.width = Some(Cow::Owned(width)); + self.height = Some(Cow::Owned(height)); + } + } + } +} + +impl<T: AsRef<str> + Cell> Estimate<&VecRecords<T>, ColoredConfig> + for CompleteDimensionVecRecords<'_> +{ + fn estimate(&mut self, records: &VecRecords<T>, cfg: &ColoredConfig) { + self.estimate(records, cfg.as_ref()) + } +} diff --git a/vendor/tabled/src/grid/dimension/const_dimension.rs b/vendor/tabled/src/grid/dimension/const_dimension.rs new file mode 100644 index 000000000..450b1abfe --- /dev/null +++ b/vendor/tabled/src/grid/dimension/const_dimension.rs @@ -0,0 +1,70 @@ +//! Module contains a dimension estimator for [`CompactTable`] +//! +//! [`CompactTable`]: crate::tables::CompactTable + +use crate::grid::dimension::{Dimension, Estimate}; + +/// A constant size dimension or a value dimension. +#[derive(Debug, Clone, Copy)] +pub struct ConstDimension<const COLUMNS: usize, const ROWS: usize> { + height: ConstSize<ROWS>, + width: ConstSize<COLUMNS>, +} + +impl<const COLUMNS: usize, const ROWS: usize> ConstDimension<COLUMNS, ROWS> { + /// Returns a new dimension object with a given estimates. + pub const fn new(width: ConstSize<COLUMNS>, height: ConstSize<ROWS>) -> Self { + Self { width, height } + } +} + +impl<const COLUMNS: usize, const ROWS: usize> Dimension for ConstDimension<COLUMNS, ROWS> { + fn get_width(&self, column: usize) -> usize { + match self.width { + ConstSize::List(list) => list[column], + ConstSize::Value(val) => val, + } + } + + fn get_height(&self, row: usize) -> usize { + match self.height { + ConstSize::List(list) => list[row], + ConstSize::Value(val) => val, + } + } +} + +impl<const COLUMNS: usize, const ROWS: usize> From<ConstDimension<COLUMNS, ROWS>> + for (ConstSize<COLUMNS>, ConstSize<ROWS>) +{ + fn from(value: ConstDimension<COLUMNS, ROWS>) -> Self { + (value.width, value.height) + } +} + +impl<R, D, const COLUMNS: usize, const ROWS: usize> Estimate<R, D> + for ConstDimension<COLUMNS, ROWS> +{ + fn estimate(&mut self, _: R, _: &D) {} +} + +/// Const size represents either a const array values or a single value which responsible for the whole list. +#[derive(Debug, Clone, Copy)] +pub enum ConstSize<const N: usize> { + /// A constant array of estimates. + List([usize; N]), + /// A value which act as a single estimate for all entries. + Value(usize), +} + +impl From<usize> for ConstSize<0> { + fn from(value: usize) -> Self { + ConstSize::Value(value) + } +} + +impl<const N: usize> From<[usize; N]> for ConstSize<N> { + fn from(value: [usize; N]) -> Self { + ConstSize::List(value) + } +} diff --git a/vendor/tabled/src/grid/dimension/mod.rs b/vendor/tabled/src/grid/dimension/mod.rs new file mode 100644 index 000000000..2f67b402f --- /dev/null +++ b/vendor/tabled/src/grid/dimension/mod.rs @@ -0,0 +1,32 @@ +//! Module contains a list of implementations of [`Estimate`] and [`Dimension`]. + +mod const_dimension; +mod pool_table_dimension; + +#[cfg(feature = "std")] +mod complete_dimension; +#[cfg(feature = "std")] +mod complete_dimension_vec_records; +#[cfg(feature = "std")] +mod peekable_dimension; +#[cfg(feature = "std")] +mod static_dimension; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use self::{ + complete_dimension::CompleteDimension, + complete_dimension_vec_records::CompleteDimensionVecRecords, + peekable_dimension::PeekableDimension, + static_dimension::{DimensionValue, StaticDimension}, +}; +pub use const_dimension::{ConstDimension, ConstSize}; +pub use papergrid::dimension::{Dimension, Estimate}; +pub use pool_table_dimension::{DimensionPriority, PoolTableDimension}; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use papergrid::dimension::{ + compact::CompactGridDimension, spanned::SpannedGridDimension, + spanned_vec_records::SpannedVecRecordsDimension, +}; diff --git a/vendor/tabled/src/grid/dimension/peekable_dimension.rs b/vendor/tabled/src/grid/dimension/peekable_dimension.rs new file mode 100644 index 000000000..1ba6fda21 --- /dev/null +++ b/vendor/tabled/src/grid/dimension/peekable_dimension.rs @@ -0,0 +1,335 @@ +use papergrid::records::vec_records::{CellInfo, VecRecords}; + +use crate::grid::{ + config::SpannedConfig, + dimension::{Dimension, Estimate}, + records::Records, +}; + +/// PeekableDimension is a [`Dimension`] implementation for a [`Table`] +/// +/// [`Table`]: crate::Table +#[derive(Debug, Default, Clone)] +pub struct PeekableDimension { + width: Vec<usize>, + height: Vec<usize>, +} + +impl PeekableDimension { + /// Calculates height of rows. + pub fn height<T: AsRef<str>>( + records: &VecRecords<CellInfo<T>>, + cfg: &SpannedConfig, + ) -> Vec<usize> { + estimation::build_height(records, cfg) + } + + /// Calculates width of columns. + pub fn width<T: AsRef<str>>( + records: &VecRecords<CellInfo<T>>, + cfg: &SpannedConfig, + ) -> Vec<usize> { + estimation::build_width(records, cfg) + } + + /// Return width and height lists. + pub fn get_values(self) -> (Vec<usize>, Vec<usize>) { + (self.width, self.height) + } +} + +impl Dimension for PeekableDimension { + fn get_width(&self, column: usize) -> usize { + self.width[column] + } + + fn get_height(&self, row: usize) -> usize { + self.height[row] + } +} + +impl<T> Estimate<&VecRecords<CellInfo<T>>, SpannedConfig> for PeekableDimension +where + T: AsRef<str>, +{ + fn estimate(&mut self, records: &VecRecords<CellInfo<T>>, cfg: &SpannedConfig) { + let (width, height) = estimation::build_dimensions(records, cfg); + self.width = width; + self.height = height; + } +} + +mod estimation { + use core::cmp::{max, Ordering}; + use std::collections::HashMap; + + use papergrid::{ + config::Position, + records::vec_records::{Cell, CellInfo, VecRecords}, + }; + + use super::*; + + pub(super) fn build_dimensions<T: AsRef<str>>( + records: &VecRecords<CellInfo<T>>, + cfg: &SpannedConfig, + ) -> (Vec<usize>, Vec<usize>) { + let count_columns = records.count_columns(); + + let mut widths = vec![0; count_columns]; + let mut heights = vec![]; + + let mut vspans = HashMap::new(); + let mut hspans = HashMap::new(); + + for (row, columns) in records.iter_rows().enumerate() { + let mut row_height = 0; + for (col, cell) in columns.iter().enumerate() { + let pos = (row, col); + if !cfg.is_cell_visible(pos) { + continue; + } + + let height = cell.count_lines(); + let width = cell.width(); + + let pad = cfg.get_padding(pos.into()); + let width = width + pad.left.size + pad.right.size; + let height = height + pad.top.size + pad.bottom.size; + + match cfg.get_column_span(pos) { + Some(n) if n > 1 => { + let _ = vspans.insert(pos, (n, width)); + } + _ => widths[col] = max(widths[col], width), + } + + match cfg.get_row_span(pos) { + Some(n) if n > 1 => { + let _ = hspans.insert(pos, (n, height)); + } + _ => row_height = max(row_height, height), + } + } + + heights.push(row_height); + } + + let count_rows = heights.len(); + + adjust_vspans(cfg, count_columns, &vspans, &mut widths); + adjust_hspans(cfg, count_rows, &hspans, &mut heights); + + (widths, heights) + } + + fn adjust_hspans( + cfg: &SpannedConfig, + len: usize, + spans: &HashMap<Position, (usize, usize)>, + heights: &mut [usize], + ) { + if spans.is_empty() { + return; + } + + let mut spans_ordered = spans + .iter() + .map(|(k, v)| ((k.0, k.1), *v)) + .collect::<Vec<_>>(); + spans_ordered.sort_unstable_by(|(arow, acol), (brow, bcol)| match arow.cmp(brow) { + Ordering::Equal => acol.cmp(bcol), + ord => ord, + }); + + for ((row, _), (span, height)) in spans_ordered { + adjust_row_range(cfg, height, len, row, row + span, heights); + } + } + + fn adjust_row_range( + cfg: &SpannedConfig, + max_span_height: usize, + len: usize, + start: usize, + end: usize, + heights: &mut [usize], + ) { + let range_height = range_height(cfg, len, start, end, heights); + if range_height >= max_span_height { + return; + } + + inc_range(heights, max_span_height - range_height, start, end); + } + + fn range_height( + cfg: &SpannedConfig, + len: usize, + start: usize, + end: usize, + heights: &[usize], + ) -> usize { + let count_borders = count_horizontal_borders(cfg, len, start, end); + let range_height = heights[start..end].iter().sum::<usize>(); + count_borders + range_height + } + + fn count_horizontal_borders( + cfg: &SpannedConfig, + len: usize, + start: usize, + end: usize, + ) -> usize { + (start..end) + .skip(1) + .filter(|&i| cfg.has_horizontal(i, len)) + .count() + } + + fn inc_range(list: &mut [usize], size: usize, start: usize, end: usize) { + if list.is_empty() { + return; + } + + let span = end - start; + let one = size / span; + let rest = size - span * one; + + let mut i = start; + while i < end { + if i == start { + list[i] += one + rest; + } else { + list[i] += one; + } + + i += 1; + } + } + + fn adjust_vspans( + cfg: &SpannedConfig, + len: usize, + spans: &HashMap<Position, (usize, usize)>, + widths: &mut [usize], + ) { + if spans.is_empty() { + return; + } + + // The overall width distribution will be different depend on the order. + // + // We sort spans in order to prioritize the smaller spans first. + let mut spans_ordered = spans + .iter() + .map(|(k, v)| ((k.0, k.1), *v)) + .collect::<Vec<_>>(); + spans_ordered.sort_unstable_by(|a, b| match a.1 .0.cmp(&b.1 .0) { + Ordering::Equal => a.0.cmp(&b.0), + o => o, + }); + + for ((_, col), (span, width)) in spans_ordered { + adjust_column_range(cfg, width, len, col, col + span, widths); + } + } + + fn adjust_column_range( + cfg: &SpannedConfig, + max_span_width: usize, + len: usize, + start: usize, + end: usize, + widths: &mut [usize], + ) { + let range_width = range_width(cfg, len, start, end, widths); + if range_width >= max_span_width { + return; + } + + inc_range(widths, max_span_width - range_width, start, end); + } + + fn range_width( + cfg: &SpannedConfig, + len: usize, + start: usize, + end: usize, + widths: &[usize], + ) -> usize { + let count_borders = count_vertical_borders(cfg, len, start, end); + let range_width = widths[start..end].iter().sum::<usize>(); + count_borders + range_width + } + + fn count_vertical_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize { + (start..end) + .skip(1) + .filter(|&i| cfg.has_vertical(i, len)) + .count() + } + + pub(super) fn build_height<T: AsRef<str>>( + records: &VecRecords<CellInfo<T>>, + cfg: &SpannedConfig, + ) -> Vec<usize> { + let mut heights = vec![]; + let mut hspans = HashMap::new(); + + for (row, columns) in records.iter_rows().enumerate() { + let mut row_height = 0; + for (col, cell) in columns.iter().enumerate() { + let pos = (row, col); + if !cfg.is_cell_visible(pos) { + continue; + } + + let height = cell.count_lines(); + match cfg.get_row_span(pos) { + Some(n) if n > 1 => { + let _ = hspans.insert(pos, (n, height)); + } + _ => row_height = max(row_height, height), + } + } + + heights.push(row_height); + } + + adjust_hspans(cfg, heights.len(), &hspans, &mut heights); + + heights + } + + pub(super) fn build_width<T: AsRef<str>>( + records: &VecRecords<CellInfo<T>>, + cfg: &SpannedConfig, + ) -> Vec<usize> { + let count_columns = records.count_columns(); + + let mut widths = vec![0; count_columns]; + let mut vspans = HashMap::new(); + + for (row, columns) in records.iter_rows().enumerate() { + for (col, cell) in columns.iter().enumerate() { + let pos = (row, col); + if !cfg.is_cell_visible(pos) { + continue; + } + + let width = cell.width(); + match cfg.get_column_span(pos) { + Some(n) if n > 1 => { + let _ = vspans.insert(pos, (n, width)); + } + _ => widths[col] = max(widths[col], width), + } + } + } + + adjust_vspans(cfg, count_columns, &vspans, &mut widths); + + widths + } +} diff --git a/vendor/tabled/src/grid/dimension/pool_table_dimension.rs b/vendor/tabled/src/grid/dimension/pool_table_dimension.rs new file mode 100644 index 000000000..9909c661d --- /dev/null +++ b/vendor/tabled/src/grid/dimension/pool_table_dimension.rs @@ -0,0 +1,36 @@ +/// PoolTableDimension is a dimension resolve strategy for [`PoolTable`] +/// +/// [`PoolTable`]: crate::tables::PoolTable +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub struct PoolTableDimension { + width: DimensionPriority, + height: DimensionPriority, +} + +impl PoolTableDimension { + /// Creates a new object. + pub fn new(width: DimensionPriority, height: DimensionPriority) -> Self { + Self { width, height } + } + + /// Return a width priority. + pub fn width(&self) -> DimensionPriority { + self.width + } + + /// Return a height priority. + pub fn height(&self) -> DimensionPriority { + self.height + } +} + +/// A control of width/height logic for situations where we must increase some cell to align columns/row. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum DimensionPriority { + /// Increase first cell width/height in a row/column. + First, + /// Increase last cell width/height in a row/column. + Last, + /// Increase cells width/height 1 by 1 in a row/column. + List, +} diff --git a/vendor/tabled/src/grid/dimension/static_dimension.rs b/vendor/tabled/src/grid/dimension/static_dimension.rs new file mode 100644 index 000000000..f9474f212 --- /dev/null +++ b/vendor/tabled/src/grid/dimension/static_dimension.rs @@ -0,0 +1,63 @@ +use crate::grid::dimension::{Dimension, Estimate}; + +/// A constant dimension. +#[derive(Debug, Clone)] +pub struct StaticDimension { + width: DimensionValue, + height: DimensionValue, +} + +impl StaticDimension { + /// Creates a constant dimension. + pub fn new(width: DimensionValue, height: DimensionValue) -> Self { + Self { width, height } + } +} + +impl From<StaticDimension> for (DimensionValue, DimensionValue) { + fn from(value: StaticDimension) -> Self { + (value.width, value.height) + } +} + +impl Dimension for StaticDimension { + fn get_width(&self, column: usize) -> usize { + self.width.get(column) + } + + fn get_height(&self, row: usize) -> usize { + self.height.get(row) + } +} + +impl<R, C> Estimate<R, C> for StaticDimension { + fn estimate(&mut self, _: R, _: &C) {} +} + +/// A dimension value. +#[derive(Debug, Clone)] +pub enum DimensionValue { + /// Const width value. + Exact(usize), + /// A list of width values for columns. + List(Vec<usize>), + /// A list of width values for columns and a value for the rest. + Partial(Vec<usize>, usize), +} + +impl DimensionValue { + /// Get a width by column. + pub fn get(&self, col: usize) -> usize { + match self { + DimensionValue::Exact(val) => *val, + DimensionValue::List(cols) => cols[col], + DimensionValue::Partial(cols, val) => { + if cols.len() > col { + cols[col] + } else { + *val + } + } + } + } +} diff --git a/vendor/tabled/src/grid/mod.rs b/vendor/tabled/src/grid/mod.rs new file mode 100644 index 000000000..cdd8c55c0 --- /dev/null +++ b/vendor/tabled/src/grid/mod.rs @@ -0,0 +1,48 @@ +//! Module is responsible for tables underlyign grid. +//! +//! It might be used when implementing your own [`TableOption`] and [`CellOption`]. +//! +//! [`TableOption`]: crate::settings::TableOption +//! [`CellOption`]: crate::settings::CellOption +#[cfg(feature = "std")] +mod colored_config; + +mod compact_multiline_config; + +pub mod dimension; +pub mod records; + +pub use papergrid::color; +pub use papergrid::colors; +pub use papergrid::util; + +pub mod config { + //! Module contains a list of configs for varios tables/grids. + + pub use papergrid::config::{ + compact::CompactConfig, AlignmentHorizontal, AlignmentVertical, Border, Borders, Entity, + EntityIterator, Indent, Line, Position, Sides, + }; + + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub use papergrid::config::spanned::{ + EntityMap, Formatting, HorizontalLine, Offset, SpannedConfig, VerticalLine, + }; + + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub use super::colored_config::{ColorMap, ColoredConfig}; + + pub use super::compact_multiline_config::CompactMultilineConfig; +} + +pub use papergrid::grid::compact::CompactGrid; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use papergrid::grid::iterable::Grid; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use papergrid::grid::peekable::PeekableGrid; diff --git a/vendor/tabled/src/grid/records/empty_records.rs b/vendor/tabled/src/grid/records/empty_records.rs new file mode 100644 index 000000000..77ebc812d --- /dev/null +++ b/vendor/tabled/src/grid/records/empty_records.rs @@ -0,0 +1,41 @@ +//! An empty [`Records`] implementation. + +use core::iter::{repeat, Repeat, Take}; + +use super::Records; + +/// Empty representation of [`Records`]. +#[derive(Debug, Default, Clone)] +pub struct EmptyRecords { + rows: usize, + cols: usize, +} + +impl EmptyRecords { + /// Constructs an empty representation of [`Records`] with a given shape. + pub fn new(rows: usize, cols: usize) -> Self { + Self { rows, cols } + } +} + +impl From<(usize, usize)> for EmptyRecords { + fn from((count_rows, count_columns): (usize, usize)) -> Self { + Self::new(count_rows, count_columns) + } +} + +impl Records for EmptyRecords { + type Iter = Take<Repeat<Take<Repeat<&'static str>>>>; + + fn iter_rows(self) -> Self::Iter { + repeat(repeat("").take(self.cols)).take(self.rows) + } + + fn count_columns(&self) -> usize { + self.cols + } + + fn hint_count_rows(&self) -> Option<usize> { + Some(self.rows) + } +} diff --git a/vendor/tabled/src/grid/records/into_records/buf_records.rs b/vendor/tabled/src/grid/records/into_records/buf_records.rs new file mode 100644 index 000000000..2db3e45bf --- /dev/null +++ b/vendor/tabled/src/grid/records/into_records/buf_records.rs @@ -0,0 +1,213 @@ +//! A module contains [`BufRows`] and [`BufColumns`] iterators. +//! +//! Almoust always they both can be used interchangeably but [`BufRows`] is supposed to be lighter cause it +//! does not reads columns. + +use crate::grid::records::IntoRecords; + +use super::either_string::EitherString; + +/// BufRecords inspects [`IntoRecords`] iterator and keeps read data buffered. +/// So it can be checking before hand. +#[derive(Debug)] +pub struct BufRows<I, T> { + iter: I, + buf: Vec<T>, +} + +impl BufRows<(), ()> { + /// Creates a new [`BufRows`] structure, filling the buffer. + pub fn new<I: IntoRecords>( + records: I, + sniff: usize, + ) -> BufRows<<I::IterRows as IntoIterator>::IntoIter, I::IterColumns> { + let mut buf = vec![]; + + let mut iter = records.iter_rows().into_iter(); + for _ in 0..sniff { + match iter.next() { + Some(row) => buf.push(row), + None => break, + } + } + + BufRows { iter, buf } + } +} + +impl<I, T> BufRows<I, T> { + /// Returns a slice of a record buffer. + pub fn as_slice(&self) -> &[T] { + &self.buf + } +} + +impl<I, T> From<BufRows<I, T>> for BufColumns<I> +where + T: IntoIterator, + T::Item: AsRef<str>, +{ + fn from(value: BufRows<I, T>) -> Self { + let buf = value + .buf + .into_iter() + .map(|row| row.into_iter().map(|s| s.as_ref().to_string()).collect()) + .collect(); + + BufColumns { + iter: value.iter, + buf, + } + } +} + +impl<I, T> IntoRecords for BufRows<I, T> +where + I: Iterator<Item = T>, + T: IntoIterator, + T::Item: AsRef<str>, +{ + type Cell = T::Item; + type IterColumns = T; + type IterRows = BufRowIter<I, T>; + + fn iter_rows(self) -> Self::IterRows { + BufRowIter { + buf: self.buf.into_iter(), + iter: self.iter, + } + } +} + +/// Buffered [`Iterator`]. +#[derive(Debug)] +pub struct BufRowIter<I, T> { + buf: std::vec::IntoIter<T>, + iter: I, +} + +impl<I, T> Iterator for BufRowIter<I, T> +where + I: Iterator<Item = T>, + T: IntoIterator, + T::Item: AsRef<str>, +{ + type Item = T; + + fn next(&mut self) -> Option<Self::Item> { + match self.buf.next() { + Some(i) => Some(i), + None => self.iter.next(), + } + } +} + +/// BufRecords inspects [`IntoRecords`] iterator and keeps read data buffered. +/// So it can be checking before hand. +/// +/// In contrast to [`BufRows`] it keeps records by columns. +#[derive(Debug)] +pub struct BufColumns<I> { + iter: I, + buf: Vec<Vec<String>>, +} + +impl BufColumns<()> { + /// Creates new [`BufColumns`] structure, filling the buffer. + pub fn new<I: IntoRecords>( + records: I, + sniff: usize, + ) -> BufColumns<<I::IterRows as IntoIterator>::IntoIter> { + let mut buf = vec![]; + + let mut iter = records.iter_rows().into_iter(); + for _ in 0..sniff { + match iter.next() { + Some(row) => { + let row = row + .into_iter() + .map(|cell| cell.as_ref().to_string()) + .collect::<Vec<_>>(); + buf.push(row) + } + None => break, + } + } + + BufColumns { iter, buf } + } +} + +impl<I> BufColumns<I> { + /// Returns a slice of a keeping buffer. + pub fn as_slice(&self) -> &[Vec<String>] { + &self.buf + } +} + +impl<I> IntoRecords for BufColumns<I> +where + I: Iterator, + I::Item: IntoIterator, + <I::Item as IntoIterator>::Item: AsRef<str>, +{ + type Cell = EitherString<<I::Item as IntoIterator>::Item>; + type IterColumns = EitherRowIterator<<I::Item as IntoIterator>::IntoIter>; + type IterRows = BufColumnIter<I>; + + fn iter_rows(self) -> Self::IterRows { + BufColumnIter { + buf: self.buf.into_iter(), + iter: self.iter, + } + } +} + +/// A row iterator for [`BufColumns`] +#[derive(Debug)] +pub struct BufColumnIter<I> { + buf: std::vec::IntoIter<Vec<String>>, + iter: I, +} + +impl<I> Iterator for BufColumnIter<I> +where + I: Iterator, + I::Item: IntoIterator, + <I::Item as IntoIterator>::Item: AsRef<str>, +{ + type Item = EitherRowIterator<<I::Item as IntoIterator>::IntoIter>; + + fn next(&mut self) -> Option<Self::Item> { + match self.buf.next() { + Some(i) => Some(EitherRowIterator::Owned(i.into_iter())), + None => self + .iter + .next() + .map(|i| EitherRowIterator::Some(i.into_iter())), + } + } +} + +/// An iterator over some iterator or allocated buffer. +#[derive(Debug)] +pub enum EitherRowIterator<I> { + /// Allocated iterator. + Owned(std::vec::IntoIter<String>), + /// Given iterator. + Some(I), +} + +impl<I> Iterator for EitherRowIterator<I> +where + I: Iterator, +{ + type Item = EitherString<I::Item>; + + fn next(&mut self) -> Option<Self::Item> { + match self { + EitherRowIterator::Owned(iter) => iter.next().map(EitherString::Owned), + EitherRowIterator::Some(iter) => iter.next().map(EitherString::Some), + } + } +} diff --git a/vendor/tabled/src/grid/records/into_records/either_string.rs b/vendor/tabled/src/grid/records/into_records/either_string.rs new file mode 100644 index 000000000..f4d97290d --- /dev/null +++ b/vendor/tabled/src/grid/records/into_records/either_string.rs @@ -0,0 +1,22 @@ +//! A module with a utility enum [`EitherString`]. + +/// Either allocated string or some type which can be used as a string. +#[derive(Debug)] +pub enum EitherString<T> { + /// Allocated string. + Owned(String), + /// Something which can be used as a string. + Some(T), +} + +impl<T> AsRef<str> for EitherString<T> +where + T: AsRef<str>, +{ + fn as_ref(&self) -> &str { + match self { + EitherString::Owned(s) => s.as_ref(), + EitherString::Some(s) => s.as_ref(), + } + } +} diff --git a/vendor/tabled/src/grid/records/into_records/limit_column_records.rs b/vendor/tabled/src/grid/records/into_records/limit_column_records.rs new file mode 100644 index 000000000..89b2b89ed --- /dev/null +++ b/vendor/tabled/src/grid/records/into_records/limit_column_records.rs @@ -0,0 +1,82 @@ +//! The module contains [`LimitColumns`] records iterator. + +use crate::grid::records::IntoRecords; + +/// An iterator which limits amount of columns. +#[derive(Debug)] +pub struct LimitColumns<I> { + records: I, + limit: usize, +} + +impl LimitColumns<()> { + /// Creates new [`LimitColumns`]. + pub fn new<I: IntoRecords>(records: I, limit: usize) -> LimitColumns<I> { + LimitColumns { records, limit } + } +} + +impl<I> IntoRecords for LimitColumns<I> +where + I: IntoRecords, +{ + type Cell = I::Cell; + type IterColumns = LimitColumnsColumnsIter<<I::IterColumns as IntoIterator>::IntoIter>; + type IterRows = LimitColumnsIter<<I::IterRows as IntoIterator>::IntoIter>; + + fn iter_rows(self) -> Self::IterRows { + LimitColumnsIter { + iter: self.records.iter_rows().into_iter(), + limit: self.limit, + } + } +} + +/// An iterator over rows for [`LimitColumns`]. +#[derive(Debug)] +pub struct LimitColumnsIter<I> { + iter: I, + limit: usize, +} + +impl<I> Iterator for LimitColumnsIter<I> +where + I: Iterator, + I::Item: IntoIterator, + <I::Item as IntoIterator>::Item: AsRef<str>, +{ + type Item = LimitColumnsColumnsIter<<I::Item as IntoIterator>::IntoIter>; + + fn next(&mut self) -> Option<Self::Item> { + let iter = self.iter.next()?; + Some(LimitColumnsColumnsIter { + iter: iter.into_iter(), + limit: self.limit, + }) + } +} + +/// An iterator over columns for [`LimitColumns`]. +#[derive(Debug)] +pub struct LimitColumnsColumnsIter<I> { + iter: I, + limit: usize, +} + +impl<I> Iterator for LimitColumnsColumnsIter<I> +where + I: Iterator, + I::Item: AsRef<str>, +{ + type Item = I::Item; + + fn next(&mut self) -> Option<Self::Item> { + if self.limit == 0 { + return None; + } + + self.limit -= 1; + + self.iter.next() + } +} diff --git a/vendor/tabled/src/grid/records/into_records/limit_row_records.rs b/vendor/tabled/src/grid/records/into_records/limit_row_records.rs new file mode 100644 index 000000000..a461c6682 --- /dev/null +++ b/vendor/tabled/src/grid/records/into_records/limit_row_records.rs @@ -0,0 +1,59 @@ +//! The module contains [`LimitRows`] records iterator. + +use crate::grid::records::IntoRecords; + +/// [`LimitRows`] is an records iterator which limits amount of rows. +#[derive(Debug)] +pub struct LimitRows<I> { + records: I, + limit: usize, +} + +impl LimitRows<()> { + /// Creates new [`LimitRows`] iterator. + pub fn new<I: IntoRecords>(records: I, limit: usize) -> LimitRows<I> { + LimitRows { records, limit } + } +} + +impl<I> IntoRecords for LimitRows<I> +where + I: IntoRecords, +{ + type Cell = I::Cell; + type IterColumns = I::IterColumns; + type IterRows = LimitRowsIter<<I::IterRows as IntoIterator>::IntoIter>; + + fn iter_rows(self) -> Self::IterRows { + LimitRowsIter { + iter: self.records.iter_rows().into_iter(), + limit: self.limit, + } + } +} + +/// A rows iterator for [`LimitRows`] +#[derive(Debug)] +pub struct LimitRowsIter<I> { + iter: I, + limit: usize, +} + +impl<I> Iterator for LimitRowsIter<I> +where + I: Iterator, + I::Item: IntoIterator, + <I::Item as IntoIterator>::Item: AsRef<str>, +{ + type Item = I::Item; + + fn next(&mut self) -> Option<Self::Item> { + if self.limit == 0 { + return None; + } + + self.limit -= 1; + + self.iter.next() + } +} diff --git a/vendor/tabled/src/grid/records/into_records/mod.rs b/vendor/tabled/src/grid/records/into_records/mod.rs new file mode 100644 index 000000000..0a52c41c1 --- /dev/null +++ b/vendor/tabled/src/grid/records/into_records/mod.rs @@ -0,0 +1,26 @@ +//! The module contains a list of helpers for [`IntoRecords`] +//! +//! [`IntoRecords`]: crate::grid::records::IntoRecords + +pub mod limit_column_records; +pub mod limit_row_records; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod buf_records; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod either_string; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod truncate_records; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use buf_records::{BufColumns, BufRows}; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use truncate_records::TruncateContent; + +pub use limit_column_records::LimitColumns; +pub use limit_row_records::LimitRows; diff --git a/vendor/tabled/src/grid/records/into_records/truncate_records.rs b/vendor/tabled/src/grid/records/into_records/truncate_records.rs new file mode 100644 index 000000000..17e7e533e --- /dev/null +++ b/vendor/tabled/src/grid/records/into_records/truncate_records.rs @@ -0,0 +1,136 @@ +//! The module contains [`TruncateContent`] records iterator. + +use std::borrow::Cow; + +use crate::{ + grid::records::IntoRecords, grid::util::string::string_width_multiline, + settings::width::Truncate, +}; + +use super::either_string::EitherString; + +/// A records iterator which truncates all cells to a given width. +#[derive(Debug)] +pub struct TruncateContent<'a, I> { + records: I, + width: ExactValue<'a>, +} + +impl TruncateContent<'_, ()> { + /// Creates new [`TruncateContent`] object. + pub fn new<I: IntoRecords>(records: I, width: ExactValue<'_>) -> TruncateContent<'_, I> { + TruncateContent { records, width } + } +} + +impl<'a, I> IntoRecords for TruncateContent<'a, I> +where + I: IntoRecords, +{ + type Cell = EitherString<I::Cell>; + type IterColumns = TruncateContentColumnsIter<'a, <I::IterColumns as IntoIterator>::IntoIter>; + type IterRows = TruncateContentIter<'a, <I::IterRows as IntoIterator>::IntoIter>; + + fn iter_rows(self) -> Self::IterRows { + TruncateContentIter { + iter: self.records.iter_rows().into_iter(), + width: self.width.clone(), + } + } +} + +/// A row iterator for [`TruncateContent`]. +#[derive(Debug)] +pub struct TruncateContentIter<'a, I> { + iter: I, + width: ExactValue<'a>, +} + +impl<'a, I> Iterator for TruncateContentIter<'a, I> +where + I: Iterator, + I::Item: IntoIterator, + <I::Item as IntoIterator>::Item: AsRef<str>, +{ + type Item = TruncateContentColumnsIter<'a, <I::Item as IntoIterator>::IntoIter>; + + fn next(&mut self) -> Option<Self::Item> { + let iter = self.iter.next()?; + Some(TruncateContentColumnsIter { + iter: iter.into_iter(), + current: 0, + width: self.width.clone(), + }) + } +} + +/// A column iterator for [`TruncateContent`]. +#[derive(Debug)] +pub struct TruncateContentColumnsIter<'a, I> { + iter: I, + width: ExactValue<'a>, + current: usize, +} + +impl<I> Iterator for TruncateContentColumnsIter<'_, I> +where + I: Iterator, + I::Item: AsRef<str>, +{ + type Item = EitherString<I::Item>; + + fn next(&mut self) -> Option<Self::Item> { + let s = self.iter.next()?; + + let width = self.width.get(self.current); + self.current += 1; + + let text_width = string_width_multiline(s.as_ref()); + if text_width <= width { + return Some(EitherString::Some(s)); + } + + let text = Truncate::truncate_text(s.as_ref(), width); + let text = text.into_owned(); + let text = EitherString::Owned(text); + + Some(text) + } +} + +/// A width value. +#[derive(Debug, Clone)] +pub enum ExactValue<'a> { + /// Const width value. + Exact(usize), + /// A list of width values for columns. + List(Cow<'a, [usize]>), +} + +impl<'a> From<&'a [usize]> for ExactValue<'a> { + fn from(value: &'a [usize]) -> Self { + Self::List(value.into()) + } +} + +impl From<Vec<usize>> for ExactValue<'_> { + fn from(value: Vec<usize>) -> Self { + Self::List(value.into()) + } +} + +impl From<usize> for ExactValue<'_> { + fn from(value: usize) -> Self { + Self::Exact(value) + } +} + +impl ExactValue<'_> { + /// Get a width by column. + pub fn get(&self, col: usize) -> usize { + match self { + ExactValue::Exact(val) => *val, + ExactValue::List(cols) => cols[col], + } + } +} diff --git a/vendor/tabled/src/grid/records/mod.rs b/vendor/tabled/src/grid/records/mod.rs new file mode 100644 index 000000000..494002346 --- /dev/null +++ b/vendor/tabled/src/grid/records/mod.rs @@ -0,0 +1,19 @@ +//! The module contains [`Records`], [`ExactRecords`], [`RecordsMut`], [`Resizable`] traits +//! and its implementations. +//! +//! Also it provies a list of helpers for a user built [`Records`] via [`into_records`]. + +mod empty_records; +mod records_mut; +mod resizable; + +pub mod into_records; + +pub use empty_records::EmptyRecords; +pub use papergrid::records::{ExactRecords, IntoRecords, IterRecords, PeekableRecords, Records}; +pub use records_mut::RecordsMut; +pub use resizable::Resizable; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use papergrid::records::vec_records; diff --git a/vendor/tabled/src/grid/records/records_mut.rs b/vendor/tabled/src/grid/records/records_mut.rs new file mode 100644 index 000000000..38c42d2c7 --- /dev/null +++ b/vendor/tabled/src/grid/records/records_mut.rs @@ -0,0 +1,34 @@ +use crate::grid::config::Position; +#[cfg(feature = "std")] +use crate::grid::records::vec_records::{CellInfo, VecRecords}; + +/// A [`Records`] representation which can modify cell by (row, column) index. +/// +/// [`Records`]: crate::grid::records::Records +pub trait RecordsMut<Text> { + /// Sets a text to a given cell by index. + fn set(&mut self, pos: Position, text: Text); +} + +impl<T, Text> RecordsMut<Text> for &'_ mut T +where + T: RecordsMut<Text>, +{ + fn set(&mut self, pos: Position, text: Text) { + T::set(self, pos, text) + } +} + +#[cfg(feature = "std")] +impl RecordsMut<String> for VecRecords<CellInfo<String>> { + fn set(&mut self, (row, col): Position, text: String) { + self[row][col] = CellInfo::new(text); + } +} + +#[cfg(feature = "std")] +impl RecordsMut<&str> for VecRecords<CellInfo<String>> { + fn set(&mut self, (row, col): Position, text: &str) { + self[row][col] = CellInfo::new(text.to_string()); + } +} diff --git a/vendor/tabled/src/grid/records/resizable.rs b/vendor/tabled/src/grid/records/resizable.rs new file mode 100644 index 000000000..29ab38038 --- /dev/null +++ b/vendor/tabled/src/grid/records/resizable.rs @@ -0,0 +1,217 @@ +use papergrid::config::Position; + +#[cfg(feature = "std")] +use crate::grid::records::vec_records::VecRecords; + +/// A records representation which can be modified by moving rows/columns around. +pub trait Resizable { + /// Swap cells with one another. + fn swap(&mut self, lhs: Position, rhs: Position); + /// Swap rows with one another. + fn swap_row(&mut self, lhs: usize, rhs: usize); + /// Swap columns with one another. + fn swap_column(&mut self, lhs: usize, rhs: usize); + /// Adds a new row to a data set. + fn push_row(&mut self); + /// Adds a new column to a data set. + fn push_column(&mut self); + /// Removes a row from a data set by index. + fn remove_row(&mut self, row: usize); + /// Removes a column from a data set by index. + fn remove_column(&mut self, column: usize); + /// Inserts a row at index. + fn insert_row(&mut self, row: usize); + /// Inserts column at index. + fn insert_column(&mut self, column: usize); +} + +impl<T> Resizable for &'_ mut T +where + T: Resizable, +{ + fn swap(&mut self, lhs: Position, rhs: Position) { + T::swap(self, lhs, rhs) + } + + fn swap_row(&mut self, lhs: usize, rhs: usize) { + T::swap_row(self, lhs, rhs) + } + + fn swap_column(&mut self, lhs: usize, rhs: usize) { + T::swap_column(self, lhs, rhs) + } + + fn push_row(&mut self) { + T::push_row(self) + } + + fn push_column(&mut self) { + T::push_column(self) + } + + fn remove_row(&mut self, row: usize) { + T::remove_row(self, row) + } + + fn remove_column(&mut self, column: usize) { + T::remove_column(self, column) + } + + fn insert_row(&mut self, row: usize) { + T::insert_row(self, row) + } + + fn insert_column(&mut self, column: usize) { + T::insert_column(self, column) + } +} + +#[cfg(feature = "std")] +impl<T> Resizable for Vec<Vec<T>> +where + T: Default + Clone, +{ + fn swap(&mut self, lhs: Position, rhs: Position) { + if lhs == rhs { + return; + } + + let t = std::mem::take(&mut self[lhs.0][lhs.1]); + let t = std::mem::replace(&mut self[rhs.0][rhs.1], t); + let _ = std::mem::replace(&mut self[lhs.0][lhs.1], t); + } + + fn swap_row(&mut self, lhs: usize, rhs: usize) { + let t = std::mem::take(&mut self[lhs]); + let t = std::mem::replace(&mut self[rhs], t); + let _ = std::mem::replace(&mut self[lhs], t); + } + + fn swap_column(&mut self, lhs: usize, rhs: usize) { + for row in self.iter_mut() { + row.swap(lhs, rhs); + } + } + + fn push_row(&mut self) { + let count_columns = self.get(0).map(|l| l.len()).unwrap_or(0); + self.push(vec![T::default(); count_columns]); + } + + fn push_column(&mut self) { + for row in self.iter_mut() { + row.push(T::default()); + } + } + + fn remove_row(&mut self, row: usize) { + let _ = self.remove(row); + } + + fn remove_column(&mut self, column: usize) { + for row in self.iter_mut() { + let _ = row.remove(column); + } + } + + fn insert_row(&mut self, row: usize) { + let count_columns = self.get(0).map(|l| l.len()).unwrap_or(0); + self.insert(row, vec![T::default(); count_columns]); + } + + fn insert_column(&mut self, column: usize) { + for row in self { + row.insert(column, T::default()); + } + } +} + +#[cfg(feature = "std")] +impl<T> Resizable for VecRecords<T> +where + T: Default + Clone, +{ + fn swap(&mut self, lhs: Position, rhs: Position) { + if lhs == rhs { + return; + } + + let t = std::mem::take(&mut self[lhs.0][lhs.1]); + let t = std::mem::replace(&mut self[rhs.0][rhs.1], t); + let _ = std::mem::replace(&mut self[lhs.0][lhs.1], t); + } + + fn swap_row(&mut self, lhs: usize, rhs: usize) { + let t = std::mem::take(&mut self[lhs]); + let t = std::mem::replace(&mut self[rhs], t); + let _ = std::mem::replace(&mut self[lhs], t); + } + + fn swap_column(&mut self, lhs: usize, rhs: usize) { + for row in self.iter_mut() { + row.swap(lhs, rhs); + } + } + + fn push_row(&mut self) { + let records = std::mem::replace(self, VecRecords::new(vec![])); + let mut data: Vec<Vec<_>> = records.into(); + + let count_columns = data.get(0).map(|l| l.len()).unwrap_or(0); + data.push(vec![T::default(); count_columns]); + + *self = VecRecords::new(data); + } + + fn push_column(&mut self) { + let records = std::mem::replace(self, VecRecords::new(vec![])); + let mut data: Vec<Vec<_>> = records.into(); + + for row in &mut data { + row.push(T::default()); + } + + *self = VecRecords::new(data); + } + + fn remove_row(&mut self, row: usize) { + let records = std::mem::replace(self, VecRecords::new(vec![])); + let mut data: Vec<Vec<_>> = records.into(); + + let _ = data.remove(row); + + *self = VecRecords::new(data); + } + + fn remove_column(&mut self, column: usize) { + let records = std::mem::replace(self, VecRecords::new(vec![])); + let mut data: Vec<Vec<_>> = records.into(); + + for row in &mut data { + let _ = row.remove(column); + } + + *self = VecRecords::new(data); + } + + fn insert_row(&mut self, row: usize) { + let records = std::mem::replace(self, VecRecords::new(vec![])); + let mut data: Vec<Vec<_>> = records.into(); + + let count_columns = data.get(0).map(|l| l.len()).unwrap_or(0); + data.insert(row, vec![T::default(); count_columns]); + + *self = VecRecords::new(data); + } + + fn insert_column(&mut self, column: usize) { + let records = std::mem::replace(self, VecRecords::new(vec![])); + let mut data: Vec<Vec<_>> = records.into(); + + for row in &mut data { + row.insert(column, T::default()); + } + + *self = VecRecords::new(data); + } +} diff --git a/vendor/tabled/src/lib.rs b/vendor/tabled/src/lib.rs new file mode 100644 index 000000000..fb008b302 --- /dev/null +++ b/vendor/tabled/src/lib.rs @@ -0,0 +1,490 @@ +//! An easy to use library for pretty print tables of Rust `struct`s and `enum`s. +//! +//! The library supports different approaches of table building. +//! You can use [`Tabled`] trait if the data type is known. +//! Or you can use [`Builder`] to construct the table from scratch. +//! +//! ## Usage +//! +//! If you want to build a table for your custom type. +//! A starting point is to a anotate your type with `#[derive(Tabled)]`. +//! +//! Then to provide your collection to [`Table::new`] and you will be set to render table. +//! +#![cfg_attr(all(feature = "derive", feature = "std"), doc = "```")] +#![cfg_attr(not(all(feature = "derive", feature = "std")), doc = "```ignore")] +//! use tabled::{Tabled, Table}; +//! +//! #[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 = Table::new(languages).to_string(); +//! +//! let expected = "+------+----------------+---------------+\n\ +//! | name | designed_by | invented_year |\n\ +//! +------+----------------+---------------+\n\ +//! | C | Dennis Ritchie | 1972 |\n\ +//! +------+----------------+---------------+\n\ +//! | Rust | Graydon Hoare | 2010 |\n\ +//! +------+----------------+---------------+\n\ +//! | Go | Rob Pike | 2009 |\n\ +//! +------+----------------+---------------+"; +//! +//! assert_eq!(table, expected); +//! ``` +//! +//! Not all types can derive [`Tabled`] trait though. +//! The example below can't be compiled. +//! +//! ```rust,compile_fail +//! # use tabled::Tabled; +//! #[derive(Tabled)] +//! struct SomeType { +//! field1: SomeOtherType, +//! } +//! +//! struct SomeOtherType; +//! ``` +//! +//! Because `tabled` must know what we're up to print as a field, so +//! each (almoust) field must implement [`std::fmt::Display`]. +//! +//! ### Default implementations +//! +//! [`Table`] can be build from vast majority of Rust's standard types. +//! This allows you to run the following code. +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Tabled, Table}; +//! let table = Table::new(&[1, 2, 3]); +//! # let expected = "+-----+\n\ +//! # | i32 |\n\ +//! # +-----+\n\ +//! # | 1 |\n\ +//! # +-----+\n\ +//! # | 2 |\n\ +//! # +-----+\n\ +//! # | 3 |\n\ +//! # +-----+"; +//! # assert_eq!(table.to_string(), expected); +//! ``` +//! +//! ### Dynamic table +//! +//! When you data scheme is not known at compile time. +//! You most likely will not able to relay on [`Tabled`] trait. +//! +//! So one option would be is to use [`Builder`]. +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use std::iter; +//! +//! use tabled::{ +//! builder::Builder, +//! settings::{Modify, object::Rows, Alignment, Style} +//! }; +//! +//! let (x, y) = (3, 10); +//! +//! let mut builder = Builder::default(); +//! +//! let header = iter::once(String::from("i")) +//! .chain((0..y) +//! .map(|i| i.to_string())); +//! builder.set_header(header); +//! +//! for i in 0..x { +//! let row = iter::once(i) +//! .chain((0..y).map(|j| i * j)) +//! .map(|i| i.to_string()); +//! builder.push_record(row); +//! } +//! +//! let table = builder.build() +//! .with(Style::rounded()) +//! .with(Modify::new(Rows::new(1..)).with(Alignment::left())) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "╭───┬───┬───┬───┬───┬───┬────┬────┬────┬────┬────╮\n", +//! "│ i │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │\n", +//! "├───┼───┼───┼───┼───┼───┼────┼────┼────┼────┼────┤\n", +//! "│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │\n", +//! "│ 1 │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │\n", +//! "│ 2 │ 0 │ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │ 16 │ 18 │\n", +//! "╰───┴───┴───┴───┴───┴───┴────┴────┴────┴────┴────╯", +//! ) +//! ); +//! ``` +//! +//! ### Build table using [`row!`] and [`col!`] macros. +//! +#![cfg_attr(all(feature = "macros", feature = "std"), doc = "```")] +#![cfg_attr(not(all(feature = "macros", feature = "std")), doc = "```ignore")] +//! use tabled::{row, col}; +//! +//! let table = row![ +//! col!["Hello", "World", "!"], +//! col!["Hello"; 3], +//! col!["World"; 3], +//! ]; +//! +//! assert_eq!( +//! table.to_string(), +//! concat!( +//! "+-----------+-----------+-----------+\n", +//! "| +-------+ | +-------+ | +-------+ |\n", +//! "| | Hello | | | Hello | | | World | |\n", +//! "| +-------+ | +-------+ | +-------+ |\n", +//! "| | World | | | Hello | | | World | |\n", +//! "| +-------+ | +-------+ | +-------+ |\n", +//! "| | ! | | | Hello | | | World | |\n", +//! "| +-------+ | +-------+ | +-------+ |\n", +//! "+-----------+-----------+-----------+", +//! ) +//! ); +//! ``` +//! +//! ### Settings +//! +//! You can use many settings which is found in [`tabled::settings`] module. +//! +//! # Advanced +//! +//! ## Alloc +//! +//! [`Table`] keeps data buffered, which sometimes not ideal choise. +//! For such reason there is [`IterTable`] and [`CompactTable`]. +//! +//! ### Less allocations +//! +//! [`IterTable`] stands on a middle ground between [`Table`] and [`CompactTable`]. +//! +//! It does allocate memory but in a much smaller chunks that a [`Table`] does. +//! The benefit is that it can be used interchangebly with [`Table`]. +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::tables::IterTable; +//! +//! let iterator = (0..3).map(|row| (0..4).map(move |col| format!("{}-{}", row, col))); +//! +//! let table = IterTable::new(iterator).to_string(); +//! +//! assert_eq!( +//! table, +//! "+-----+-----+-----+-----+\n\ +//! | 0-0 | 0-1 | 0-2 | 0-3 |\n\ +//! +-----+-----+-----+-----+\n\ +//! | 1-0 | 1-1 | 1-2 | 1-3 |\n\ +//! +-----+-----+-----+-----+\n\ +//! | 2-0 | 2-1 | 2-2 | 2-3 |\n\ +//! +-----+-----+-----+-----+", +//! ); +//! ``` +//! +//! ## Alloc free (`#nostd`) +//! +//! [`CompactTable`] can be configured ('1) to not make any allocations. +//! But the price is that the set of settings which can be applied to it is limited. +//! +//! It also can be printed directly to [`fmt::Write`] to not have any intermidiaries. +//! +//! '1. It does not make any allocations in case you provide it with `width` and `count_rows`. +//! +//! ``` +//! use tabled::{settings::Style, tables::CompactTable}; +//! use core::fmt::{Write, Result}; +//! +//! struct StubWriter; +//! +//! impl Write for StubWriter { +//! fn write_str(&mut self, _: &str) -> Result { +//! Ok(()) +//! } +//! } +//! +//! 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()); +//! +//! table.fmt(StubWriter); +//! ``` +//! +//! ## More information +//! +//! You can find more examples of settings and attributes in +//! [README.md](https://github.com/zhiburt/tabled/blob/master/README.md) +//! +//! [`Builder`]: crate::builder::Builder +//! [`IterTable`]: crate::tables::IterTable +//! [`CompactTable`]: crate::tables::CompactTable +//! [`fmt::Write`]: core::fmt::Write +//! [`row!`]: crate::row +//! [`col!`]: crate::col + +#![cfg_attr(not(any(feature = "std", test)), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/zhiburt/tabled/86ac146e532ce9f7626608d7fd05072123603a2e/assets/tabled-gear.svg" +)] +#![deny(unused_must_use)] +#![warn( + missing_docs, + rust_2018_idioms, + rust_2018_compatibility, + missing_debug_implementations, + unreachable_pub, + future_incompatible, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unused_extern_crates, + unused_import_braces, + unused_qualifications, + unused_results, + unused_variables, + variant_size_differences +)] +#![allow(clippy::uninlined_format_args)] + +#[cfg(feature = "std")] +mod tabled; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod builder; +pub mod settings; +pub mod tables; + +#[cfg(feature = "macros")] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +pub mod macros; + +pub mod grid; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use crate::{tabled::Tabled, tables::Table}; + +/// A derive to implement a [`Tabled`] trait. +/// +/// The macros available only when `derive` feature in turned on (and it is by default). +/// +/// To be able to use the derive each field must implement `std::fmt::Display`. +/// The following example will cause a error because of that. +/// +/// ```rust,compile_fail +/// use tabled::Tabled; +/// #[derive(Tabled)] +/// struct SomeType { +/// field1: SomeOtherType, +/// } +/// +/// struct SomeOtherType; +/// ``` +/// +/// Bellow you'll find available options for it. +/// +/// ### Override a column name +/// +/// You can use a `#[tabled(rename = "")]` attribute to override a column name. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// struct Person { +/// #[tabled(rename = "Name")] +/// first_name: &'static str, +/// #[tabled(rename = "Surname")] +/// last_name: &'static str, +/// } +/// ``` +/// +/// ### Hide a column +/// +/// You can mark fields as hidden in which case they fill be ignored and not be present on a sheet. +/// +/// A similar affect could be achieved by the means of a `Disable` setting. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// struct Person { +/// id: u8, +/// #[tabled(skip)] +/// number: &'static str, +/// name: &'static str, +/// } +/// ``` +/// +/// ### Set column order +/// +/// You can change the order in which they will be displayed in table. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// struct Person { +/// id: u8, +/// #[tabled(order = 0)] +/// number: &'static str, +/// #[tabled(order = 1)] +/// name: &'static str, +/// } +/// ``` +/// +/// ### Format fields +/// +/// As was said already, using `#[derive(Tabled)]` is possible only when all fields implement a `Display` trait. +/// However, this may be often not the case for example when a field uses the `Option` type. There's 2 common ways how to solve this: +/// +/// - Implement `Tabled` trait manually for a type. +/// - Wrap `Option` to something like `DisplayedOption<T>(Option<T>)` and implement a Display trait for it. +/// +/// Alternatively, you can use the `#[tabled(display_with = "func")]` attribute for the field to specify a display function. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// pub struct MyRecord { +/// pub id: i64, +/// #[tabled(display_with = "display_option")] +/// pub valid: Option<bool> +/// } +/// +/// fn display_option(o: &Option<bool>) -> String { +/// match o { +/// Some(s) => format!("is valid thing = {}", s), +/// None => format!("is not valid"), +/// } +/// } +/// ``` +/// +/// It's also possible to change function argument to be `&self`, +/// using `#[tabled(display_with("some_function", self))]` +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// pub struct MyRecord { +/// pub id: i64, +/// #[tabled(display_with("Self::display_valid", self))] +/// pub valid: Option<bool> +/// } +/// +/// impl MyRecord { +/// fn display_valid(&self) -> String { +/// match self.valid { +/// Some(s) => format!("is valid thing = {}", s), +/// None => format!("is not valid"), +/// } +/// } +/// } +/// ``` +/// +/// ### Format headers +/// +/// Beside `#[tabled(rename = "")]` you can change a format of a column name using +/// `#[tabled(rename_all = "UPPERCASE")]`. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// #[tabled(rename_all = "CamelCase")] +/// struct Person { +/// id: u8, +/// number: &'static str, +/// name: &'static str, +/// #[tabled(rename_all = "snake_case")] +/// middle_name: &'static str, +/// } +/// ``` +/// +/// ### Inline +/// +/// It's possible to inline internal data if it implements the `Tabled` trait using `#[tabled(inline)]`. +/// You can also set a prefix which will be used for all inlined elements by `#[tabled(inline("prefix>>"))]`. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// struct Person { +/// id: u8, +/// name: &'static str, +/// #[tabled(inline)] +/// ed: Education, +/// } +/// +/// #[derive(Tabled)] +/// struct Education { +/// uni: &'static str, +/// graduated: bool, +/// } +/// ``` +/// +/// And it works for enums as well. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// enum Vehicle { +/// #[tabled(inline("Auto::"))] +/// Auto { +/// model: &'static str, +/// engine: &'static str, +/// }, +/// #[tabled(inline)] +/// Bikecycle( +/// &'static str, +/// #[tabled(inline)] Bike, +/// ), +/// } +/// +/// #[derive(Tabled)] +/// struct Bike { +/// brand: &'static str, +/// price: f32, +/// } +/// ``` +#[cfg(feature = "derive")] +#[cfg_attr(docsrs, doc(cfg(feature = "derive")))] +pub use tabled_derive::Tabled; diff --git a/vendor/tabled/src/macros/col.rs b/vendor/tabled/src/macros/col.rs new file mode 100644 index 000000000..1fd67de8a --- /dev/null +++ b/vendor/tabled/src/macros/col.rs @@ -0,0 +1,50 @@ +/// Creates a [`Table`] with [`Display`] arguments nested within. +/// +/// The macros allows several tables to be displayed vertically. +/// +/// Companion to [`row!`]. +/// +/// # Examples +/// ```rust,no_run +/// # use tabled::{row, col, Table}; +/// # let (table1, table2, table3) = (Table::new(&[String::new()]), Table::new(&[String::new()]), Table::new(&[String::new()])); +/// let new_table = col![table1, table2]; +/// let new_table_of_clones = col![table1; 3]; +/// let columns_and_rows = col![ +/// table1, +/// row![table2, table3] +/// ]; +/// ``` +/// +/// [`row!`]: crate::row +/// [`Table`]: crate::Table +/// [`Display`]: std::fmt::Display +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! col { + // Vertical + ( $($table:expr), * $(,)? ) => {{ + let mut builder = $crate::builder::Builder::default(); + + $( + builder.push_record([$table.to_string()]); + )* + + builder.build() + }}; + + // Duplicate single item + ( $table:expr; $N:expr) => {{ + let mut builder = $crate::builder::Builder::default(); + + let n = $N; + if n > 0 { + let t = $table.to_string(); + for _ in 0..$N { + builder.push_record([t.clone()]); + } + } + + builder.build() + }}; +} diff --git a/vendor/tabled/src/macros/mod.rs b/vendor/tabled/src/macros/mod.rs new file mode 100644 index 000000000..7db0a9caa --- /dev/null +++ b/vendor/tabled/src/macros/mod.rs @@ -0,0 +1,6 @@ +//! This module contains macro functions for dynamic [`Table`] construction. +//! +//! [`Table`]: crate::Table + +mod col; +mod row; diff --git a/vendor/tabled/src/macros/row.rs b/vendor/tabled/src/macros/row.rs new file mode 100644 index 000000000..23775b90d --- /dev/null +++ b/vendor/tabled/src/macros/row.rs @@ -0,0 +1,44 @@ +/// Creates a [`Table`] with [`Display`] arguments nested within. +/// +/// The macros allows several tables to be displayed horizontally. +/// +/// Companion to [`col!`]. +/// +/// # Examples +/// ```rust,no_run +/// # use tabled::{row, col, Table}; +/// # let (table1, table2, table3) = (Table::new(&[String::new()]), Table::new(&[String::new()]), Table::new(&[String::new()])); +/// let new_table = row![table1, table2]; +/// let new_table_of_clones = row![table1; 3]; +/// let rows_and_columns = row![ +/// table1, +/// col![table2, table3] +/// ]; +/// ``` +/// +/// [`col!`]: crate::col +/// [`Table`]: crate::Table +/// [`Display`]: std::fmt::Display +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! row { + // Horizontal Display + ( $($table:expr), * $(,)? ) => {{ + let mut builder = $crate::builder::Builder::default(); + + let record = [ $($table.to_string(),)* ]; + builder.push_record(record); + + builder.build() + }}; + + // Duplicate single item + ( $table:expr; $N:expr) => {{ + let mut builder = $crate::builder::Builder::default(); + + let duplicates = vec![$table.to_string(); $N]; + builder.push_record(duplicates); + + builder.build() + }}; +} diff --git a/vendor/tabled/src/settings/alignment/mod.rs b/vendor/tabled/src/settings/alignment/mod.rs new file mode 100644 index 000000000..81d1717a6 --- /dev/null +++ b/vendor/tabled/src/settings/alignment/mod.rs @@ -0,0 +1,189 @@ +//! This module contains an [`Alignment`] setting for cells on the [`Table`]. +//! +//! # Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! # use tabled::{Table, settings::{Alignment, Modify, object::Rows}}; +//! # let data: Vec<&'static str> = Vec::new(); +//! let mut table = Table::new(&data); +//! table.with(Modify::new(Rows::single(0)).with(Alignment::center())); +//! ``` +//! +//! [`Table`]: crate::Table + +use crate::{ + grid::config::CompactConfig, + grid::config::{AlignmentHorizontal, AlignmentVertical, CompactMultilineConfig}, + settings::TableOption, +}; + +use AlignmentInner::*; + +#[cfg(feature = "std")] +use crate::grid::config::{ColoredConfig, Entity}; + +/// Alignment represent a horizontal and vertical alignment setting for any cell on a [`Table`]. +/// +/// An alignment strategy can be set by [`AlignmentStrategy`]. +/// +/// # Example +/// +#[cfg_attr(feature = "std", doc = "```")] +#[cfg_attr(not(feature = "std"), doc = "```ignore")] +/// use tabled::{ +/// Table, +/// settings::{ +/// formatting::AlignmentStrategy, +/// object::Segment, Alignment, Modify, Style, +/// } +/// }; +/// +/// let data = [ +/// ["1", "2", "3"], +/// ["Some\nMulti\nLine\nText", "and a line", "here"], +/// ["4", "5", "6"], +/// ]; +/// +/// let mut table = Table::new(&data); +/// table +/// .with(Style::modern()) +/// .with( +/// Modify::new(Segment::all()) +/// .with(Alignment::right()) +/// .with(Alignment::center()) +/// .with(AlignmentStrategy::PerCell) +/// ); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "┌───────┬────────────┬──────┐\n", +/// "│ 0 │ 1 │ 2 │\n", +/// "├───────┼────────────┼──────┤\n", +/// "│ 1 │ 2 │ 3 │\n", +/// "├───────┼────────────┼──────┤\n", +/// "│ Some │ and a line │ here │\n", +/// "│ Multi │ │ │\n", +/// "│ Line │ │ │\n", +/// "│ Text │ │ │\n", +/// "├───────┼────────────┼──────┤\n", +/// "│ 4 │ 5 │ 6 │\n", +/// "└───────┴────────────┴──────┘", +/// ), +/// ) +/// ``` +/// +/// [`Table`]: crate::Table +/// [`AlignmentStrategy`]: crate::settings::formatting::AlignmentStrategy +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Alignment { + inner: AlignmentInner, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +enum AlignmentInner { + /// A horizontal alignment. + Horizontal(AlignmentHorizontal), + /// A vertical alignment. + Vertical(AlignmentVertical), +} + +impl Alignment { + /// Left constructs a horizontal alignment to [`AlignmentHorizontal::Left`] + pub fn left() -> Self { + Self::horizontal(AlignmentHorizontal::Left) + } + + /// Right constructs a horizontal alignment to [`AlignmentHorizontal::Right`] + /// + /// ## Notice + /// + /// When you use [`MinWidth`] the alignment might not work as you expected. + /// You could try to apply [`TrimStrategy`] which may help. + /// + /// [`MinWidth`]: crate::settings::width::MinWidth + /// [`TrimStrategy`]: crate::settings::formatting::TrimStrategy + pub fn right() -> Self { + Self::horizontal(AlignmentHorizontal::Right) + } + + /// Center constructs a horizontal alignment to [`AlignmentHorizontal::Center`] + /// + /// ## Notice + /// + /// When you use [`MinWidth`] the alignment might not work as you expected. + /// You could try to apply [`TrimStrategy`] which may help. + /// + /// [`MinWidth`]: crate::settings::width::MinWidth + /// [`TrimStrategy`]: crate::settings::formatting::TrimStrategy + pub const fn center() -> Self { + Self::horizontal(AlignmentHorizontal::Center) + } + + /// Top constructs a vertical alignment to [`AlignmentVertical::Top`] + pub const fn top() -> Self { + Self::vertical(AlignmentVertical::Top) + } + + /// Bottom constructs a vertical alignment to [`AlignmentVertical::Bottom`] + pub const fn bottom() -> Self { + Self::vertical(AlignmentVertical::Bottom) + } + + /// `Center_vertical` constructs a vertical alignment to [`AlignmentVertical::Center`] + pub const fn center_vertical() -> Self { + Self::vertical(AlignmentVertical::Center) + } + + /// Returns an alignment with the given horizontal alignment. + const fn horizontal(alignment: AlignmentHorizontal) -> Self { + Self::new(Horizontal(alignment)) + } + + /// Returns an alignment with the given vertical alignment. + const fn vertical(alignment: AlignmentVertical) -> Self { + Self::new(Vertical(alignment)) + } + + const fn new(inner: AlignmentInner) -> Self { + Self { inner } + } +} + +#[cfg(feature = "std")] +impl<R> crate::settings::CellOption<R, ColoredConfig> for Alignment { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + match self.inner { + Horizontal(a) => cfg.set_alignment_horizontal(entity, a), + Vertical(a) => cfg.set_alignment_vertical(entity, a), + } + } +} + +#[cfg(feature = "std")] +impl<R, D> TableOption<R, D, ColoredConfig> for Alignment { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + match self.inner { + Horizontal(a) => cfg.set_alignment_horizontal(Entity::Global, a), + Vertical(a) => cfg.set_alignment_vertical(Entity::Global, a), + } + } +} + +impl<R, D> TableOption<R, D, CompactConfig> for Alignment { + fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { + if let Horizontal(a) = self.inner { + *cfg = cfg.set_alignment_horizontal(a) + } + } +} + +impl<R, D> TableOption<R, D, CompactMultilineConfig> for Alignment { + fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { + match self.inner { + Horizontal(a) => *cfg = cfg.set_alignment_horizontal(a), + Vertical(a) => *cfg = cfg.set_alignment_vertical(a), + } + } +} diff --git a/vendor/tabled/src/settings/cell_option.rs b/vendor/tabled/src/settings/cell_option.rs new file mode 100644 index 000000000..f589ac47f --- /dev/null +++ b/vendor/tabled/src/settings/cell_option.rs @@ -0,0 +1,55 @@ +use crate::grid::{ + config::Entity, + records::{ExactRecords, Records, RecordsMut}, +}; + +/// A trait for configuring a single cell. +/// +/// ~~~~ Where cell represented by 'row' and 'column' indexes. ~~~~ +/// +/// A cell can be targeted by [`Cell`]. +/// +/// [`Cell`]: crate::object::Cell +pub trait CellOption<R, C> { + /// Modification function of a single cell. + fn change(self, records: &mut R, cfg: &mut C, entity: Entity); +} + +#[cfg(feature = "std")] +impl<R, C> CellOption<R, C> for String +where + R: Records + ExactRecords + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut C, entity: Entity) { + (&self).change(records, cfg, entity); + } +} + +#[cfg(feature = "std")] +impl<R, C> CellOption<R, C> for &String +where + R: Records + ExactRecords + RecordsMut<String>, +{ + fn change(self, records: &mut R, _: &mut C, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + for pos in entity.iter(count_rows, count_cols) { + records.set(pos, self.clone()); + } + } +} + +impl<'a, R, C> CellOption<R, C> for &'a str +where + R: Records + ExactRecords + RecordsMut<&'a str>, +{ + fn change(self, records: &mut R, _: &mut C, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + for pos in entity.iter(count_rows, count_cols) { + records.set(pos, self); + } + } +} diff --git a/vendor/tabled/src/settings/color/mod.rs b/vendor/tabled/src/settings/color/mod.rs new file mode 100644 index 000000000..f4bfa5c32 --- /dev/null +++ b/vendor/tabled/src/settings/color/mod.rs @@ -0,0 +1,346 @@ +//! This module contains a configuration of a [`Border`] or a [`Table`] to set its borders color via [`Color`]. +//! +//! [`Border`]: crate::settings::Border +//! [`Table`]: crate::Table + +use std::{borrow::Cow, ops::BitOr}; + +use crate::{ + grid::{ + color::{AnsiColor, StaticColor}, + config::{ColoredConfig, Entity}, + }, + settings::{CellOption, TableOption}, +}; + +/// Color represents a color which can be set to things like [`Border`], [`Padding`] and [`Margin`]. +/// +/// # Example +/// +/// ``` +/// use tabled::{settings::Color, Table}; +/// +/// let data = [ +/// (0u8, "Hello"), +/// (1u8, "World"), +/// ]; +/// +/// let table = Table::new(data) +/// .with(Color::BG_BLUE) +/// .to_string(); +/// +/// println!("{}", table); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +/// [`Margin`]: crate::settings::Margin +/// [`Border`]: crate::settings::Border +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Color(AnsiColor<'static>); + +// todo: Add | operation to combine colors + +#[rustfmt::skip] +impl Color { + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[30m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[34m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[90m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[94m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[96m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[92m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[95m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[91m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[97m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[93m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[36m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[32m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[35m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[31m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[37m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[33m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + + pub const BG_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[40m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[44m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[100m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[104m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[106m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[102m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[105m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[101m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[107m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[103m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[46m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[42m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[45m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[41m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[47m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[43m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BOLD: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[1m"), Cow::Borrowed("\u{1b}[22m"))); +} + +impl Color { + /// Creates a new [`Color`]` instance, with ANSI prefix and ANSI suffix. + /// You can use [`TryFrom`] to construct it from [`String`]. + pub fn new(prefix: String, suffix: String) -> Self { + Self(AnsiColor::new(prefix.into(), suffix.into())) + } +} + +impl From<Color> for AnsiColor<'static> { + fn from(c: Color) -> Self { + c.0 + } +} + +impl From<AnsiColor<'static>> for Color { + fn from(c: AnsiColor<'static>) -> Self { + Self(c) + } +} + +impl From<StaticColor> for Color { + fn from(c: StaticColor) -> Self { + Self(AnsiColor::new( + Cow::Borrowed(c.get_prefix()), + Cow::Borrowed(c.get_suffix()), + )) + } +} + +impl BitOr for Color { + type Output = Color; + + fn bitor(self, rhs: Self) -> Self::Output { + let l_prefix = self.0.get_prefix(); + let l_suffix = self.0.get_suffix(); + let r_prefix = rhs.0.get_prefix(); + let r_suffix = rhs.0.get_suffix(); + + let mut prefix = l_prefix.to_string(); + if l_prefix != r_prefix { + prefix.push_str(r_prefix); + } + + let mut suffix = l_suffix.to_string(); + if l_suffix != r_suffix { + suffix.push_str(r_suffix); + } + + Self::new(prefix, suffix) + } +} + +#[cfg(feature = "color")] +impl std::convert::TryFrom<&str> for Color { + type Error = (); + + fn try_from(value: &str) -> Result<Self, Self::Error> { + AnsiColor::try_from(value).map(Color) + } +} + +#[cfg(feature = "color")] +impl std::convert::TryFrom<String> for Color { + type Error = (); + + fn try_from(value: String) -> Result<Self, Self::Error> { + AnsiColor::try_from(value).map(Color) + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for Color { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let _ = cfg.set_color(Entity::Global, self.0.clone()); + } +} + +impl<R> CellOption<R, ColoredConfig> for Color { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let _ = cfg.set_color(entity, self.0.clone()); + } +} + +impl<R> CellOption<R, ColoredConfig> for &Color { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let _ = cfg.set_color(entity, self.0.clone()); + } +} + +impl crate::grid::color::Color for Color { + fn fmt_prefix<W: std::fmt::Write>(&self, f: &mut W) -> std::fmt::Result { + self.0.fmt_prefix(f) + } + + fn fmt_suffix<W: std::fmt::Write>(&self, f: &mut W) -> std::fmt::Result { + self.0.fmt_suffix(f) + } + + fn colorize<W: std::fmt::Write>(&self, f: &mut W, text: &str) -> std::fmt::Result { + self.0.colorize(f, text) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(feature = "color")] + use ::{owo_colors::OwoColorize, std::convert::TryFrom}; + + #[test] + fn test_xor_operation() { + assert_eq!( + Color::FG_BLACK | Color::FG_BLUE, + Color::new( + String::from("\u{1b}[30m\u{1b}[34m"), + String::from("\u{1b}[39m") + ) + ); + assert_eq!( + Color::FG_BRIGHT_GREEN | Color::BG_BLUE, + Color::new( + String::from("\u{1b}[92m\u{1b}[44m"), + String::from("\u{1b}[39m\u{1b}[49m") + ) + ); + assert_eq!( + Color::new(String::from("..."), String::from("!!!")) + | Color::new(String::from("@@@"), String::from("###")), + Color::new(String::from("...@@@"), String::from("!!!###")) + ); + assert_eq!( + Color::new(String::from("..."), String::from("!!!")) + | Color::new(String::from("@@@"), String::from("###")) + | Color::new(String::from("$$$"), String::from("%%%")), + Color::new(String::from("...@@@$$$"), String::from("!!!###%%%")) + ); + } + + #[cfg(feature = "color")] + #[test] + fn test_try_from() { + assert_eq!(Color::try_from(""), Err(())); + assert_eq!(Color::try_from("".red().on_green().to_string()), Err(())); + assert_eq!( + Color::try_from("."), + Ok(Color::new(String::new(), String::new())) + ); + assert_eq!( + Color::try_from("...."), + Ok(Color::new(String::new(), String::new())) + ); + assert_eq!( + Color::try_from(".".red().on_green().to_string()), + Ok(Color::new( + String::from("\u{1b}[31m\u{1b}[42m"), + String::from("\u{1b}[39m\u{1b}[49m") + )) + ); + assert_eq!( + Color::try_from("....".red().on_green().to_string()), + Ok(Color::new( + String::from("\u{1b}[31m\u{1b}[42m"), + String::from("\u{1b}[39m\u{1b}[49m") + )) + ); + } +} diff --git a/vendor/tabled/src/settings/concat/mod.rs b/vendor/tabled/src/settings/concat/mod.rs new file mode 100644 index 000000000..23b0dcda1 --- /dev/null +++ b/vendor/tabled/src/settings/concat/mod.rs @@ -0,0 +1,171 @@ +//! This module contains a [`Concat`] primitive which can be in order to combine 2 [`Table`]s into 1. +//! +//! # Example +//! +//! ``` +//! use tabled::{Table, settings::Concat}; +//! let table1 = Table::new([0, 1, 2, 3]); +//! let table2 = Table::new(["A", "B", "C", "D"]); +//! +//! let mut table3 = table1; +//! table3.with(Concat::horizontal(table2)); +//! ``` + +use std::borrow::Cow; + +use crate::{ + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut, Resizable}, + settings::TableOption, + Table, +}; + +/// [`Concat`] concatenates tables along a particular axis [Horizontal | Vertical]. +/// It doesn't do any key or column comparisons like SQL's join does. +/// +/// When the tables has different sizes, empty cells will be created by default. +/// +/// [`Concat`] in horizontal mode has similar behaviour to tuples `(a, b)`. +/// But it behaves on tables rather than on an actual data. +/// +/// # Example +/// +/// +#[cfg_attr(feature = "derive", doc = "```")] +#[cfg_attr(not(feature = "derive"), doc = "```ignore")] +/// use tabled::{Table, Tabled, settings::{Style, Concat}}; +/// +/// #[derive(Tabled)] +/// struct Message { +/// id: &'static str, +/// text: &'static str, +/// } +/// +/// #[derive(Tabled)] +/// struct Department(#[tabled(rename = "department")] &'static str); +/// +/// let messages = [ +/// Message { id: "0", text: "Hello World" }, +/// Message { id: "1", text: "Do do do something", }, +/// ]; +/// +/// let departments = [ +/// Department("Admins"), +/// Department("DevOps"), +/// Department("R&D"), +/// ]; +/// +/// let mut table = Table::new(messages); +/// table +/// .with(Concat::horizontal(Table::new(departments))) +/// .with(Style::extended()); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "╔════╦════════════════════╦════════════╗\n", +/// "║ id ║ text ║ department ║\n", +/// "╠════╬════════════════════╬════════════╣\n", +/// "║ 0 ║ Hello World ║ Admins ║\n", +/// "╠════╬════════════════════╬════════════╣\n", +/// "║ 1 ║ Do do do something ║ DevOps ║\n", +/// "╠════╬════════════════════╬════════════╣\n", +/// "║ ║ ║ R&D ║\n", +/// "╚════╩════════════════════╩════════════╝", +/// ) +/// ) +/// ``` + +#[derive(Debug)] +pub struct Concat { + table: Table, + mode: ConcatMode, + default_cell: Cow<'static, str>, +} + +#[derive(Debug)] +enum ConcatMode { + Vertical, + Horizontal, +} + +impl Concat { + fn new(table: Table, mode: ConcatMode) -> Self { + Self { + table, + mode, + default_cell: Cow::Borrowed(""), + } + } + + /// Concatenate 2 tables horizontally (along axis=0) + pub fn vertical(table: Table) -> Self { + Self::new(table, ConcatMode::Vertical) + } + + /// Concatenate 2 tables vertically (along axis=1) + pub fn horizontal(table: Table) -> Self { + Self::new(table, ConcatMode::Horizontal) + } + + /// Sets a cell's content for cases where 2 tables has different sizes. + pub fn default_cell(mut self, cell: impl Into<Cow<'static, str>>) -> Self { + self.default_cell = cell.into(); + self + } +} + +impl<R, D, C> TableOption<R, D, C> for Concat +where + R: Records + ExactRecords + Resizable + PeekableRecords + RecordsMut<String>, +{ + fn change(mut self, records: &mut R, _: &mut C, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + let rhs = &mut self.table; + match self.mode { + ConcatMode::Horizontal => { + for _ in 0..rhs.count_columns() { + records.push_column(); + } + + for row in count_rows..rhs.count_rows() { + records.push_row(); + + for col in 0..records.count_columns() { + records.set((row, col), self.default_cell.to_string()); + } + } + + for row in 0..rhs.shape().0 { + for col in 0..rhs.shape().1 { + let text = rhs.get_records().get_text((row, col)).to_string(); + let col = col + count_cols; + records.set((row, col), text); + } + } + } + ConcatMode::Vertical => { + for _ in 0..rhs.count_rows() { + records.push_row(); + } + + for col in count_cols..rhs.shape().1 { + records.push_column(); + + for row in 0..records.count_rows() { + records.set((row, col), self.default_cell.to_string()); + } + } + + for row in 0..rhs.shape().0 { + for col in 0..rhs.shape().1 { + let text = rhs.get_records().get_text((row, col)).to_string(); + let row = row + count_rows; + records.set((row, col), text); + } + } + } + } + } +} diff --git a/vendor/tabled/src/settings/disable/mod.rs b/vendor/tabled/src/settings/disable/mod.rs new file mode 100644 index 000000000..bfbf03db0 --- /dev/null +++ b/vendor/tabled/src/settings/disable/mod.rs @@ -0,0 +1,196 @@ +//! This module contains a [`Disable`] structure which helps to +//! remove an etheir column or row from a [`Table`]. +//! +//! # Example +//! +//! ```rust,no_run +//! # use tabled::{Table, settings::{Disable, object::Rows}}; +//! # let data: Vec<&'static str> = Vec::new(); +//! let table = Table::new(&data).with(Disable::row(Rows::first())); +//! ``` +//! +//! [`Table`]: crate::Table + +use std::marker::PhantomData; + +use crate::{ + grid::records::{ExactRecords, Records, Resizable}, + settings::{locator::Locator, TableOption}, +}; + +/// Disable removes particular rows/columns from a [`Table`]. +/// +/// It tries to keeps track of style changes which may occur. +/// But it's not guaranteed will be the way you would expect it to be. +/// +/// Generally you should avoid use of [`Disable`] because it's a slow function and modifies the underlying records. +/// Providing correct data right away is better. +/// +/// # Example +/// +/// ``` +/// use tabled::{Table, settings::{Disable, object::Rows}}; +/// +/// let data = vec!["Hello", "World", "!!!"]; +/// +/// let table = Table::new(data).with(Disable::row(Rows::new(1..2))).to_string(); +/// +/// assert_eq!( +/// table, +/// "+-------+\n\ +/// | &str |\n\ +/// +-------+\n\ +/// | World |\n\ +/// +-------+\n\ +/// | !!! |\n\ +/// +-------+" +/// ); +/// +/// ``` +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Disable<L, Target> { + locator: L, + target: PhantomData<Target>, +} + +impl<L> Disable<L, TargetColumn> { + /// Disable columns. + /// + /// Available locators are: + /// + /// - [`Columns`] + /// - [`Column`] + /// - [`FirstColumn`] + /// - [`LastColumn`] + /// - [`ByColumnName`] + /// + /// ```rust + /// use tabled::{builder::Builder, settings::{Disable, locator::ByColumnName, object::Columns}}; + /// + /// let mut builder = Builder::default(); + /// + /// builder.push_record(["col1", "col2", "col3"]); + /// builder.push_record(["Hello", "World", "1"]); + /// + /// let table = builder.build() + /// .with(Disable::column(ByColumnName::new("col3"))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+-------+-------+\n\ + /// | col1 | col2 |\n\ + /// +-------+-------+\n\ + /// | Hello | World |\n\ + /// +-------+-------+" + /// ); + /// ``` + /// + /// [`Columns`]: crate::settings::object::Columns + /// [`Column`]: crate::settings::object::Column + /// [`FirstColumn`]: crate::settings::object::FirstColumn + /// [`LastColumn`]: crate::settings::object::LastColumn + /// [`ByColumnName`]: crate::settings::locator::ByColumnName + pub fn column(locator: L) -> Self { + Self { + locator, + target: PhantomData, + } + } +} + +impl<L> Disable<L, TargetRow> { + /// Disable rows. + /// + /// Available locators are: + /// + /// - [`Rows`] + /// - [`Row`] + /// - [`FirstRow`] + /// - [`LastRow`] + /// + /// ```rust + /// use tabled::{settings::{Disable, object::Rows}, builder::Builder}; + /// + /// let mut builder = Builder::default(); + /// builder.push_record(["col1", "col2", "col3"]); + /// builder.push_record(["Hello", "World", "1"]); + /// + /// let table = builder.build() + /// .with(Disable::row(Rows::first())) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+-------+-------+---+\n\ + /// | Hello | World | 1 |\n\ + /// +-------+-------+---+" + /// ); + /// ``` + /// + /// [`Rows`]: crate::settings::object::Rows + /// [`Row`]: crate::settings::object::Row + /// [`FirstRow`]: crate::settings::object::FirstRow + /// [`LastRow`]: crate::settings::object::LastRow + pub fn row(locator: L) -> Self { + Self { + locator, + target: PhantomData, + } + } +} + +/// A marker struct for [`Disable`]. +#[derive(Debug)] +pub struct TargetRow; + +/// A marker struct for [`Disable`]. +#[derive(Debug)] +pub struct TargetColumn; + +impl<L, R, D, C> TableOption<R, D, C> for Disable<L, TargetColumn> +where + for<'a> L: Locator<&'a R, Coordinate = usize>, + R: Records + Resizable, +{ + fn change(mut self, records: &mut R, _: &mut C, _: &mut D) { + let columns = self.locator.locate(records).into_iter().collect::<Vec<_>>(); + + let mut shift = 0; + for col in columns.into_iter() { + if col - shift > records.count_columns() { + continue; + } + + records.remove_column(col - shift); + shift += 1; + } + + // fixme: I am pretty sure that we violate span constrains by removing rows/cols + // Because span may be bigger then the max number of rows/cols + } +} + +impl<L, R, D, C> TableOption<R, D, C> for Disable<L, TargetRow> +where + for<'a> L: Locator<&'a R, Coordinate = usize>, + R: ExactRecords + Resizable, +{ + fn change(mut self, records: &mut R, _: &mut C, _: &mut D) { + let rows = self.locator.locate(records).into_iter().collect::<Vec<_>>(); + + let mut shift = 0; + for row in rows.into_iter() { + if row - shift > records.count_rows() { + continue; + } + + records.remove_row(row - shift); + shift += 1; + } + + // fixme: I am pretty sure that we violate span constrains by removing rows/cols + // Because span may be bigger then the max number of rows/cols + } +} diff --git a/vendor/tabled/src/settings/duplicate/mod.rs b/vendor/tabled/src/settings/duplicate/mod.rs new file mode 100644 index 000000000..dec967bd6 --- /dev/null +++ b/vendor/tabled/src/settings/duplicate/mod.rs @@ -0,0 +1,150 @@ +//! This module contains an [`Dup`] setting the [`Table`]. +//! +//! # Example +//! +//! ``` +//! # use tabled::{Table, settings::{Dup, object::{Columns, Rows}}}; +//! # let data: Vec<&'static str> = Vec::new(); +//! let mut table = Table::new(&data); +//! table.with(Dup::new(Rows::first(), Columns::first())); +//! ``` +//! +//! [`Table`]: crate::Table + +use papergrid::config::Position; + +use crate::{ + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + settings::{object::Object, TableOption}, +}; + +/// [`Dup`] duplicates a given set of cells into another set of ones [`Table`]. +/// +/// # Example +/// +/// ``` +/// use tabled::{Table, settings::{object::Rows, Dup}}; +/// +/// let data = [ +/// ["1", "2", "3"], +/// ["Some\nMulti\nLine\nText", "and a line", "here"], +/// ["4", "5", "6"], +/// ]; +/// +/// let mut table = Table::new(&data); +/// table.with(Dup::new(Rows::single(1), Rows::single(2))); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-------+------------+------+\n\ +/// | 0 | 1 | 2 |\n\ +/// +-------+------------+------+\n\ +/// | Some | and a line | here |\n\ +/// | Multi | | |\n\ +/// | Line | | |\n\ +/// | Text | | |\n\ +/// +-------+------------+------+\n\ +/// | Some | and a line | here |\n\ +/// | Multi | | |\n\ +/// | Line | | |\n\ +/// | Text | | |\n\ +/// +-------+------------+------+\n\ +/// | 4 | 5 | 6 |\n\ +/// +-------+------------+------+", +/// ) +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Dup<Dst, Src> { + src: Src, + dst: Dst, +} + +impl<Dst, Src> Dup<Dst, Src> { + /// New creates a new [`Dup`] modifier. + /// + /// # Example + /// + /// ``` + /// # use tabled::{Table, settings::{Dup, object::{Columns, Rows}}}; + /// # let data: Vec<&'static str> = Vec::new(); + /// let mut table = Table::new(&data); + /// table.with(Dup::new(Rows::first(), Columns::last())); + /// ``` + pub fn new(dst: Dst, src: Src) -> Self { + Self { src, dst } + } +} + +impl<Dst, Src, R, D, C> TableOption<R, D, C> for Dup<Dst, Src> +where + Dst: Object<R>, + Src: Object<R>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + fn change(self, records: &mut R, _: &mut C, _: &mut D) { + let input = collect_input(records, self.src); + set_cells(records, &input, self.dst); + } +} + +fn collect_input<R, O>(records: &mut R, src: O) -> Vec<String> +where + O: Object<R>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let mut input = Vec::new(); + for entity in src.cells(records) { + for pos in entity.iter(count_rows, count_columns) { + if !is_valid_cell(pos, count_rows, count_columns) { + continue; + } + + let text = records.get_text(pos).to_owned(); + input.push(text); + } + } + + input +} + +fn set_cells<R, O>(records: &mut R, src: &[String], dst: O) +where + O: Object<R>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + if src.is_empty() { + return; + } + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + for entity in dst.cells(records) { + let mut source = src.iter().cycle(); + for pos in entity.iter(count_rows, count_columns) { + if !is_valid_cell(pos, count_rows, count_columns) { + continue; + } + + let text = source.next().unwrap().clone(); + records.set(pos, text); + } + } +} + +fn is_valid_cell((row, col): Position, count_rows: usize, count_columns: usize) -> bool { + if row > count_rows { + return false; + } + + if col > count_columns { + return false; + } + + true +} diff --git a/vendor/tabled/src/settings/extract/mod.rs b/vendor/tabled/src/settings/extract/mod.rs new file mode 100644 index 000000000..bba90a0db --- /dev/null +++ b/vendor/tabled/src/settings/extract/mod.rs @@ -0,0 +1,257 @@ +//! This module contains an [`Extract`] structure which is used to +//! obtain an ordinary segment from the [`Table`]. +//! +//! There's a similar structure [`Highlight`] which does a highlighting a of segments. +//! +//! [`Table`]: crate::Table +//! [`Highlight`]: crate::settings::highlight::Highlight + +use core::cmp::min; +use core::ops::{Bound, RangeBounds, RangeFull}; + +use crate::{ + grid::records::{ExactRecords, Records, Resizable}, + settings::TableOption, +}; + +/// Returns a new [`Table`] that reflects a segment of the referenced [`Table`] +/// +/// # Example +/// +#[cfg_attr(feature = "std", doc = "```")] +#[cfg_attr(not(feature = "std"), doc = "```ignore")] +/// use tabled::{Table, settings::{Format, object::Rows, Modify, Extract}}; +/// +/// let data = vec![ +/// (0, "Grodno", true), +/// (1, "Minsk", true), +/// (2, "Hamburg", false), +/// (3, "Brest", true), +/// ]; +/// +/// let table = Table::new(&data) +/// .with(Modify::new(Rows::new(1..)).with(Format::content(|s| format!(": {} :", s)))) +/// .with(Extract::segment(1..=2, 1..)) +/// .to_string(); +/// +/// assert_eq!(table, "+------------+----------+\n\ +/// | : Grodno : | : true : |\n\ +/// +------------+----------+\n\ +/// | : Minsk : | : true : |\n\ +/// +------------+----------+"); +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Extract<R, C> { + rows: R, + columns: C, +} + +impl<R, C> Extract<R, C> +where + R: RangeBounds<usize>, + C: RangeBounds<usize>, +{ + /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`] + /// + /// ```rust,no_run + /// # use tabled::settings::Extract; + /// let rows = 1..3; + /// let columns = 1..; + /// Extract::segment(rows, columns); + /// ``` + /// + /// # Range + /// + /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`] + /// + /// If a [`RangeBounds`] argument is malformed or too large the thread will panic + /// + /// ```text + /// // Empty Full Out of bounds + /// Extract::segment(0..0, 0..0) Extract::segment(.., ..) Extract::segment(0..1, ..4) + /// []. . . [O O O [O O O X] //ERROR + /// . . . O O O . . . + /// . . . O O O] . . . + /// ``` + /// + /// [`Table`]: crate::Table + pub fn segment(rows: R, columns: C) -> Self { + Extract { rows, columns } + } +} + +impl<R> Extract<R, RangeFull> +where + R: RangeBounds<usize>, +{ + /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`] + /// + /// The segment is defined by [`RangeBounds`] for Rows + /// + /// ```rust,no_run + /// # use tabled::settings::Extract; + /// Extract::rows(1..3); + /// ``` + /// + /// # Range + /// + /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`] + /// + /// If a [`RangeBounds`] argument is malformed or too large the thread will panic + /// + /// ```text + /// // Empty Full Out of bounds + /// Extract::rows(0..0) Extract::rows(..) Extract::rows(0..4) + /// []. . . [O O O [O O O + /// . . . O O O O O O + /// . . . O O O] O O O + /// X X X] // ERROR + /// ``` + /// + /// [`Table`]: crate::Table + pub fn rows(rows: R) -> Self { + Extract { rows, columns: .. } + } +} + +impl<C> Extract<RangeFull, C> +where + C: RangeBounds<usize>, +{ + /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`] + /// + /// The segment is defined by [`RangeBounds`] for columns. + /// + /// ```rust,no_run + /// # use tabled::settings::Extract; + /// Extract::columns(1..3); + /// ``` + /// + /// # Range + /// + /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`] + /// + /// If a [`RangeBounds`] argument is malformed or too large the thread will panic + /// + /// ```text + /// // Empty Full Out of bounds + /// Extract::columns(0..0) Extract::columns(..) Extract::columns(0..4) + /// []. . . [O O O [O O O X + /// . . . O O O O O O X + /// . . . O O O] O O O X] // ERROR + /// ``` + /// + /// [`Table`]: crate::Table + pub fn columns(columns: C) -> Self { + Extract { rows: .., columns } + } +} + +impl<R, C, RR, D, Cfg> TableOption<RR, D, Cfg> for Extract<R, C> +where + R: RangeBounds<usize> + Clone, + C: RangeBounds<usize> + Clone, + RR: Records + ExactRecords + Resizable, +{ + fn change(self, records: &mut RR, _: &mut Cfg, _: &mut D) { + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let mut rows = bounds_to_usize(self.rows.start_bound(), self.rows.end_bound(), count_rows); + let mut cols = bounds_to_usize( + self.columns.start_bound(), + self.columns.end_bound(), + count_columns, + ); + + // Cleanup table in case if boundaries are exceeded. + // + // todo: can be optimized by adding a clear() method to Resizable + rows.0 = min(rows.0, count_rows); + cols.0 = min(cols.0, count_columns); + + extract(records, (count_rows, count_columns), rows, cols); + } +} + +/// Returns a new [`Grid`] that reflects a segment of the referenced [`Grid`]. +/// +/// # Example +/// +/// ```text +/// grid +/// +---+---+---+ +/// |0-0|0-1|0-2| +/// +---+---+---+ +/// |1-0|1-1|1-2| +/// +---+---+---+ +/// |2-0|2-1|2-2| +/// +---+---+---+ +/// +/// let rows = ..; +/// let columns = ..1; +/// grid.extract(rows, columns) +/// +/// grid +/// +---+ +/// |0-0| +/// +---+ +/// |1-0| +/// +---+ +/// |2-0| +/// +---+ +/// ``` +fn extract<R>( + records: &mut R, + (count_rows, count_cols): (usize, usize), + (start_row, end_row): (usize, usize), + (start_col, end_col): (usize, usize), +) where + R: Resizable, +{ + for (i, row) in (0..start_row).enumerate() { + let row = row - i; + records.remove_row(row); + } + + let count_rows = count_rows - start_row; + let end_row = end_row - start_row; + for (i, row) in (end_row..count_rows).enumerate() { + let row = row - i; + records.remove_row(row); + } + + for (i, col) in (0..start_col).enumerate() { + let col = col - i; + records.remove_column(col); + } + + let count_cols = count_cols - start_col; + let end_col = end_col - start_col; + for (i, col) in (end_col..count_cols).enumerate() { + let col = col - i; + records.remove_column(col); + } +} + +fn bounds_to_usize( + left: Bound<&usize>, + right: Bound<&usize>, + count_elements: usize, +) -> (usize, usize) { + match (left, right) { + (Bound::Included(x), Bound::Included(y)) => (*x, y + 1), + (Bound::Included(x), Bound::Excluded(y)) => (*x, *y), + (Bound::Included(x), Bound::Unbounded) => (*x, count_elements), + (Bound::Unbounded, Bound::Unbounded) => (0, count_elements), + (Bound::Unbounded, Bound::Included(y)) => (0, y + 1), + (Bound::Unbounded, Bound::Excluded(y)) => (0, *y), + (Bound::Excluded(_), Bound::Unbounded) + | (Bound::Excluded(_), Bound::Included(_)) + | (Bound::Excluded(_), Bound::Excluded(_)) => { + unreachable!("A start bound can't be excluded") + } + } +} diff --git a/vendor/tabled/src/settings/format/format_config.rs b/vendor/tabled/src/settings/format/format_config.rs new file mode 100644 index 000000000..247ead344 --- /dev/null +++ b/vendor/tabled/src/settings/format/format_config.rs @@ -0,0 +1,14 @@ +use crate::settings::TableOption; + +/// This is a struct wrapper for a lambda which changes config. +#[derive(Debug)] +pub struct FormatConfig<F>(pub(crate) F); + +impl<F, R, D, C> TableOption<R, D, C> for FormatConfig<F> +where + F: FnMut(&mut C), +{ + fn change(mut self, _: &mut R, cfg: &mut C, _: &mut D) { + (self.0)(cfg); + } +} diff --git a/vendor/tabled/src/settings/format/format_content.rs b/vendor/tabled/src/settings/format/format_content.rs new file mode 100644 index 000000000..c14eee64f --- /dev/null +++ b/vendor/tabled/src/settings/format/format_content.rs @@ -0,0 +1,86 @@ +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + settings::{CellOption, TableOption}, +}; + +/// A lambda which formats cell content. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct FormatContent<F> { + f: F, + multiline: bool, +} + +impl<F> FormatContent<F> { + pub(crate) fn new(f: F) -> Self { + Self { + f, + multiline: false, + } + } +} + +impl<F> FormatContent<F> { + /// Multiline a helper function for changing multiline content of cell. + /// Using this formatting applied for all rows not to a string as a whole. + /// + /// ```rust,no_run + /// use tabled::{Table, settings::{Format, object::Segment, Modify}}; + /// + /// let data: Vec<&'static str> = Vec::new(); + /// let table = Table::new(&data) + /// .with(Modify::new(Segment::all()).with(Format::content(|s| s.to_string()).multiline())) + /// .to_string(); + /// ``` + pub fn multiline(mut self) -> Self { + self.multiline = true; + self + } +} + +impl<F, R, D, C> TableOption<R, D, C> for FormatContent<F> +where + F: FnMut(&str) -> String + Clone, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut C, _: &mut D) { + CellOption::change(self, records, cfg, Entity::Global); + } +} + +impl<F, R, C> CellOption<R, C> for FormatContent<F> +where + F: FnMut(&str) -> String + Clone, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + fn change(mut self, records: &mut R, _: &mut C, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + for pos in entity.iter(count_rows, count_cols) { + let is_valid_pos = pos.0 < count_rows && pos.1 < count_cols; + if !is_valid_pos { + continue; + } + + let content = records.get_text(pos); + let content = if self.multiline { + multiline(self.f.clone())(content) + } else { + (self.f)(content) + }; + records.set(pos, content); + } + } +} + +fn multiline<F: FnMut(&str) -> String>(mut f: F) -> impl FnMut(&str) -> String { + move |s: &str| { + let mut v = Vec::new(); + for line in s.lines() { + v.push(f(line)); + } + + v.join("\n") + } +} diff --git a/vendor/tabled/src/settings/format/format_positioned.rs b/vendor/tabled/src/settings/format/format_positioned.rs new file mode 100644 index 000000000..e121f007e --- /dev/null +++ b/vendor/tabled/src/settings/format/format_positioned.rs @@ -0,0 +1,51 @@ +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + settings::{CellOption, TableOption}, +}; + +/// [`FormatContentPositioned`] is like a [`FormatContent`] an abstraction over a function you can use against a cell. +/// +/// It different from [`FormatContent`] that it provides a row and column index. +/// +/// [`FormatContent`]: crate::settings::format::FormatContent +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct FormatContentPositioned<F>(F); + +impl<F> FormatContentPositioned<F> { + pub(crate) fn new(f: F) -> Self { + Self(f) + } +} + +impl<F, R, D, C> TableOption<R, D, C> for FormatContentPositioned<F> +where + F: FnMut(&str, (usize, usize)) -> String, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut C, _: &mut D) { + CellOption::change(self, records, cfg, Entity::Global); + } +} + +impl<F, R, C> CellOption<R, C> for FormatContentPositioned<F> +where + F: FnMut(&str, (usize, usize)) -> String, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + fn change(mut self, records: &mut R, _: &mut C, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + for pos in entity.iter(count_rows, count_cols) { + let is_valid_pos = pos.0 < count_rows && pos.1 < count_cols; + if !is_valid_pos { + continue; + } + + let content = records.get_text(pos); + let content = (self.0)(content, pos); + records.set(pos, content); + } + } +} diff --git a/vendor/tabled/src/settings/format/mod.rs b/vendor/tabled/src/settings/format/mod.rs new file mode 100644 index 000000000..e8221ae32 --- /dev/null +++ b/vendor/tabled/src/settings/format/mod.rs @@ -0,0 +1,144 @@ +//! This module contains a list of primitives to help to modify a [`Table`]. +//! +//! [`Table`]: crate::Table + +mod format_config; +mod format_content; +mod format_positioned; + +pub use format_config::FormatConfig; +pub use format_content::FormatContent; +pub use format_positioned::FormatContentPositioned; + +/// A formatting function of particular cells on a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Format; + +impl Format { + /// This function creates a new [`Format`] instance, so + /// it can be used as a grid setting. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::{Format, object::Rows, Modify}}; + /// + /// let data = vec![ + /// (0, "Grodno", true), + /// (1, "Minsk", true), + /// (2, "Hamburg", false), + /// (3, "Brest", true), + /// ]; + /// + /// let table = Table::new(&data) + /// .with(Modify::new(Rows::new(1..)).with(Format::content(|s| format!(": {} :", s)))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+-------+-------------+-----------+\n\ + /// | i32 | &str | bool |\n\ + /// +-------+-------------+-----------+\n\ + /// | : 0 : | : Grodno : | : true : |\n\ + /// +-------+-------------+-----------+\n\ + /// | : 1 : | : Minsk : | : true : |\n\ + /// +-------+-------------+-----------+\n\ + /// | : 2 : | : Hamburg : | : false : |\n\ + /// +-------+-------------+-----------+\n\ + /// | : 3 : | : Brest : | : true : |\n\ + /// +-------+-------------+-----------+" + /// ); + /// ``` + pub fn content<F>(f: F) -> FormatContent<F> + where + F: FnMut(&str) -> String, + { + FormatContent::new(f) + } + + /// This function creates a new [`FormatContentPositioned`], so + /// it can be used as a grid setting. + /// + /// It's different from [`Format::content`] as it also provides a row and column index. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::{Format, object::Rows, Modify}}; + /// + /// let data = vec![ + /// (0, "Grodno", true), + /// (1, "Minsk", true), + /// (2, "Hamburg", false), + /// (3, "Brest", true), + /// ]; + /// + /// let table = Table::new(&data) + /// .with(Modify::new(Rows::single(0)).with(Format::positioned(|_, (_, col)| col.to_string()))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+---+---------+-------+\n\ + /// | 0 | 1 | 2 |\n\ + /// +---+---------+-------+\n\ + /// | 0 | Grodno | true |\n\ + /// +---+---------+-------+\n\ + /// | 1 | Minsk | true |\n\ + /// +---+---------+-------+\n\ + /// | 2 | Hamburg | false |\n\ + /// +---+---------+-------+\n\ + /// | 3 | Brest | true |\n\ + /// +---+---------+-------+" + /// ); + /// ``` + pub fn positioned<F>(f: F) -> FormatContentPositioned<F> + where + F: FnMut(&str, (usize, usize)) -> String, + { + FormatContentPositioned::new(f) + } + + /// This function creates [`FormatConfig`] function to modify a table config. + /// + /// # Example + /// + /// ``` + /// use tabled::{ + /// Table, + /// settings::{Format, object::Rows, Modify}, + /// grid::config::ColoredConfig, + /// }; + /// + /// let data = vec![ + /// (0, "Grodno", true), + /// (1, "Minsk", true), + /// (2, "Hamburg", false), + /// (3, "Brest", true), + /// ]; + /// + /// let table = Table::new(&data) + /// .with(Format::config(|cfg: &mut ColoredConfig| cfg.set_justification((0,1).into(), '.'))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+-----+---------+-------+\n\ + /// | i32 | &str... | bool |\n\ + /// +-----+---------+-------+\n\ + /// | 0 | Grodno | true |\n\ + /// +-----+---------+-------+\n\ + /// | 1 | Minsk | true |\n\ + /// +-----+---------+-------+\n\ + /// | 2 | Hamburg | false |\n\ + /// +-----+---------+-------+\n\ + /// | 3 | Brest | true |\n\ + /// +-----+---------+-------+" + /// ); + /// ``` + pub fn config<F>(f: F) -> FormatConfig<F> { + FormatConfig(f) + } +} diff --git a/vendor/tabled/src/settings/formatting/alignment_strategy.rs b/vendor/tabled/src/settings/formatting/alignment_strategy.rs new file mode 100644 index 000000000..c95facc5c --- /dev/null +++ b/vendor/tabled/src/settings/formatting/alignment_strategy.rs @@ -0,0 +1,172 @@ +use crate::{ + grid::config::{ColoredConfig, CompactMultilineConfig, Entity}, + settings::{CellOption, TableOption}, +}; + +/// `AlignmentStrategy` is a responsible for a flow how we apply an alignment. +/// It mostly matters for multiline strings. +/// +/// # Examples +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{ +/// Style, Modify, Alignment, object::Segment, +/// formatting::{AlignmentStrategy, TrimStrategy} +/// } +/// }; +/// +/// // sample_from: https://opensource.adobe.com/Spry/samples/data_region/JSONDataSetSample.html +/// let json = r#" +/// { +/// "id": "0001", +/// "type": "donut", +/// "name": "Cake", +/// "ppu": 0.55, +/// "batters": { +/// "batter": [ +/// { "id": "1001", "type": "Regular" }, +/// { "id": "1002", "type": "Chocolate" }, +/// ] +/// }, +/// "topping": [ +/// { "id": "5001", "type": "None" }, +/// { "id": "5006", "type": "Chocolate with Sprinkles" }, +/// { "id": "5003", "type": "Chocolate" }, +/// { "id": "5004", "type": "Maple" } +/// ] +/// }"#; +/// +/// let mut table = Table::new(&[json]); +/// table +/// .with(Style::modern()) +/// .with(Modify::new(Segment::all()).with(Alignment::right())) +/// .with(Modify::new(Segment::all()).with(TrimStrategy::None)); +/// +/// println!("{}", table); +/// +/// assert_eq!( +/// format!("\n{}", table), +/// r#" +/// ┌───────────────────────────────────────────────────────────────┐ +/// │ &str │ +/// ├───────────────────────────────────────────────────────────────┤ +/// │ │ +/// │ { │ +/// │ "id": "0001", │ +/// │ "type": "donut", │ +/// │ "name": "Cake", │ +/// │ "ppu": 0.55, │ +/// │ "batters": { │ +/// │ "batter": [ │ +/// │ { "id": "1001", "type": "Regular" }, │ +/// │ { "id": "1002", "type": "Chocolate" }, │ +/// │ ] │ +/// │ }, │ +/// │ "topping": [ │ +/// │ { "id": "5001", "type": "None" }, │ +/// │ { "id": "5006", "type": "Chocolate with Sprinkles" }, │ +/// │ { "id": "5003", "type": "Chocolate" }, │ +/// │ { "id": "5004", "type": "Maple" } │ +/// │ ] │ +/// │ } │ +/// └───────────────────────────────────────────────────────────────┘"#); +/// +/// table +/// .with(Modify::new(Segment::all()).with(AlignmentStrategy::PerCell)) +/// .with(Modify::new(Segment::all()).with(TrimStrategy::Horizontal)); +/// +/// assert_eq!( +/// format!("\n{}", table), +/// r#" +/// ┌───────────────────────────────────────────────────────────────┐ +/// │ &str │ +/// ├───────────────────────────────────────────────────────────────┤ +/// │ │ +/// │ { │ +/// │ "id": "0001", │ +/// │ "type": "donut", │ +/// │ "name": "Cake", │ +/// │ "ppu": 0.55, │ +/// │ "batters": { │ +/// │ "batter": [ │ +/// │ { "id": "1001", "type": "Regular" }, │ +/// │ { "id": "1002", "type": "Chocolate" }, │ +/// │ ] │ +/// │ }, │ +/// │ "topping": [ │ +/// │ { "id": "5001", "type": "None" }, │ +/// │ { "id": "5006", "type": "Chocolate with Sprinkles" }, │ +/// │ { "id": "5003", "type": "Chocolate" }, │ +/// │ { "id": "5004", "type": "Maple" } │ +/// │ ] │ +/// │ } │ +/// └───────────────────────────────────────────────────────────────┘"#); +/// +/// table.with(Modify::new(Segment::all()).with(AlignmentStrategy::PerLine)); +/// +/// assert_eq!( +/// format!("\n{}", table), +/// r#" +/// ┌───────────────────────────────────────────────────────────────┐ +/// │ &str │ +/// ├───────────────────────────────────────────────────────────────┤ +/// │ │ +/// │ { │ +/// │ "id": "0001", │ +/// │ "type": "donut", │ +/// │ "name": "Cake", │ +/// │ "ppu": 0.55, │ +/// │ "batters": { │ +/// │ "batter": [ │ +/// │ { "id": "1001", "type": "Regular" }, │ +/// │ { "id": "1002", "type": "Chocolate" }, │ +/// │ ] │ +/// │ }, │ +/// │ "topping": [ │ +/// │ { "id": "5001", "type": "None" }, │ +/// │ { "id": "5006", "type": "Chocolate with Sprinkles" }, │ +/// │ { "id": "5003", "type": "Chocolate" }, │ +/// │ { "id": "5004", "type": "Maple" } │ +/// │ ] │ +/// │ } │ +/// └───────────────────────────────────────────────────────────────┘"#); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum AlignmentStrategy { + /// Apply alignment for cell content as a whole. + PerCell, + /// Apply alignment for each line of a cell content as a whole. + PerLine, +} + +impl<R> CellOption<R, ColoredConfig> for AlignmentStrategy { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let mut formatting = *cfg.get_formatting(entity); + match &self { + AlignmentStrategy::PerCell => formatting.allow_lines_alignment = false, + AlignmentStrategy::PerLine => formatting.allow_lines_alignment = true, + } + + cfg.set_formatting(entity, formatting); + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for AlignmentStrategy { + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + <Self as CellOption<R, ColoredConfig>>::change(self, records, cfg, Entity::Global) + } +} + +impl<R, D> TableOption<R, D, CompactMultilineConfig> for AlignmentStrategy { + fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { + let mut f = cfg.get_formatting(); + match &self { + AlignmentStrategy::PerCell => f.allow_lines_alignment = false, + AlignmentStrategy::PerLine => f.allow_lines_alignment = true, + } + + *cfg = cfg.set_formatting(f); + } +} diff --git a/vendor/tabled/src/settings/formatting/charset.rs b/vendor/tabled/src/settings/formatting/charset.rs new file mode 100644 index 000000000..c58effcd8 --- /dev/null +++ b/vendor/tabled/src/settings/formatting/charset.rs @@ -0,0 +1,108 @@ +use papergrid::{ + config::Entity, + records::{ExactRecords, PeekableRecords}, +}; + +use crate::{ + grid::records::{Records, RecordsMut}, + settings::{CellOption, TableOption}, +}; + +/// A structure to handle special chars. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Charset; + +impl Charset { + /// Returns [`CleanCharset`] which removes all `\t` and `\r` occurences. + /// + /// Notice that tab is just removed rather then being replaced with spaces. + /// You might be better call [`TabSize`] first if you not expect such behavior. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::formatting::Charset}; + /// + /// let text = "Some\ttext\t\twith \\tabs"; + /// + /// let mut table = Table::new([text]); + /// table.with(Charset::clean()); + /// + /// assert_eq!( + /// table.to_string(), + /// "+--------------------+\n\ + /// | &str |\n\ + /// +--------------------+\n\ + /// | Sometextwith \\tabs |\n\ + /// +--------------------+" + /// ) + /// ``` + /// + /// [`TabSize`]: crate::settings::formatting::TabSize + pub fn clean() -> CleanCharset { + CleanCharset + } +} + +/// [`CleanCharset`] removes all `\t` and `\r` occurences. +/// +/// # Example +/// +/// ``` +/// use tabled::{Table, settings::formatting::Charset}; +/// +/// let text = "Some text which was created on windows \r\n yes they use this \\r\\n"; +/// +/// let mut builder = Table::builder([text]); +/// builder.set_header(["win. text"]); +/// +/// let mut table = builder.build(); +/// table.with(Charset::clean()); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-----------------------------------------+\n\ +/// | win. text |\n\ +/// +-----------------------------------------+\n\ +/// | Some text which was created on windows |\n\ +/// | yes they use this \\r\\n |\n\ +/// +-----------------------------------------+" +/// ) +/// ``` +#[derive(Debug, Default, Clone)] +pub struct CleanCharset; + +impl<R, D, C> TableOption<R, D, C> for CleanCharset +where + for<'a> &'a R: Records, + R: RecordsMut<String>, +{ + fn change(self, records: &mut R, _: &mut C, _: &mut D) { + let mut list = vec![]; + for (row, cells) in records.iter_rows().into_iter().enumerate() { + for (col, text) in cells.into_iter().enumerate() { + let text = text.as_ref().replace(['\t', '\r'], ""); + list.push(((row, col), text)); + } + } + + for (pos, text) in list { + records.set(pos, text); + } + } +} + +impl<R, C> CellOption<R, C> for CleanCharset +where + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + fn change(self, records: &mut R, _: &mut C, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + for pos in entity.iter(count_rows, count_cols) { + let text = records.get_text(pos); + let text = text.replace(['\t', '\r'], ""); + records.set(pos, text); + } + } +} diff --git a/vendor/tabled/src/settings/formatting/justification.rs b/vendor/tabled/src/settings/formatting/justification.rs new file mode 100644 index 000000000..a427eb95e --- /dev/null +++ b/vendor/tabled/src/settings/formatting/justification.rs @@ -0,0 +1,127 @@ +use crate::{ + grid::{ + color::AnsiColor, + config::{ColoredConfig, Entity}, + }, + settings::{CellOption, Color, TableOption}, +}; + +/// Set a justification character and a color. +/// +/// Default value is `' '` (`<space>`) with no color. +/// +/// # Examples +/// +/// Setting a justification character. +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::formatting::Justification, +/// }; +/// +/// let mut table = Table::new(&[("Hello", ""), ("", "World")]); +/// table.with(Justification::new('#')); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-------+-------+\n\ +/// | &str# | &str# |\n\ +/// +-------+-------+\n\ +/// | Hello | ##### |\n\ +/// +-------+-------+\n\ +/// | ##### | World |\n\ +/// +-------+-------+" +/// ); +/// ``` +/// +/// Setting a justification color. +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{formatting::Justification, Color}, +/// }; +/// +/// let mut table = Table::new(&[("Hello", ""), ("", "World")]); +/// table.with(Justification::default().color(Color::BG_BRIGHT_RED)); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-------+-------+\n\ +/// | &str\u{1b}[101m \u{1b}[49m | &str\u{1b}[101m \u{1b}[49m |\n\ +/// +-------+-------+\n\ +/// | Hello | \u{1b}[101m \u{1b}[49m |\n\ +/// +-------+-------+\n\ +/// | \u{1b}[101m \u{1b}[49m | World |\n\ +/// +-------+-------+" +/// ); +/// ``` +/// +/// Use different justification for different columns. +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{Modify, object::Columns, formatting::Justification}, +/// }; +/// +/// let mut table = Table::new(&[("Hello", ""), ("", "World")]); +/// table.with(Modify::new(Columns::single(0)).with(Justification::new('#'))); +/// table.with(Modify::new(Columns::single(1)).with(Justification::new('@'))); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-------+-------+\n\ +/// | &str# | &str@ |\n\ +/// +-------+-------+\n\ +/// | Hello | @@@@@ |\n\ +/// +-------+-------+\n\ +/// | ##### | World |\n\ +/// +-------+-------+" +/// ); +/// ``` +/// +#[derive(Debug, Default, Clone)] +pub struct Justification { + c: Option<char>, + color: Option<AnsiColor<'static>>, +} + +impl Justification { + /// Creates new [`Justification`] object. + pub fn new(c: char) -> Self { + Self { + c: Some(c), + color: None, + } + } + + /// Sets a color for a justification. + pub fn color(self, color: Color) -> Self { + Self { + c: self.c, + color: Some(color.into()), + } + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for Justification { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let c = self.c.unwrap_or(' '); + let color = self.color; + + cfg.set_justification(Entity::Global, c); + cfg.set_justification_color(Entity::Global, color); + } +} + +impl<R> CellOption<R, ColoredConfig> for Justification { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let c = self.c.unwrap_or(' '); + let color = self.color; + + cfg.set_justification(entity, c); + cfg.set_justification_color(entity, color); + } +} diff --git a/vendor/tabled/src/settings/formatting/mod.rs b/vendor/tabled/src/settings/formatting/mod.rs new file mode 100644 index 000000000..e3a56092f --- /dev/null +++ b/vendor/tabled/src/settings/formatting/mod.rs @@ -0,0 +1,20 @@ +//! This module contains settings for render strategy of papergrid. +//! +//! - [`TrimStrategy`] and [`AlignmentStrategy`] allows to set [`Alignment`] settings. +//! - [`TabSize`] sets a default tab size. +//! - [`Charset`] responsible for special char treatment. +//! - [`Justification`] responsible for justification space of content. +//! +//! [`Alignment`]: crate::settings::Alignment + +mod alignment_strategy; +mod charset; +mod justification; +mod tab_size; +mod trim_strategy; + +pub use alignment_strategy::AlignmentStrategy; +pub use charset::{Charset, CleanCharset}; +pub use justification::Justification; +pub use tab_size::TabSize; +pub use trim_strategy::TrimStrategy; diff --git a/vendor/tabled/src/settings/formatting/tab_size.rs b/vendor/tabled/src/settings/formatting/tab_size.rs new file mode 100644 index 000000000..677d0d613 --- /dev/null +++ b/vendor/tabled/src/settings/formatting/tab_size.rs @@ -0,0 +1,62 @@ +use crate::{ + grid::records::{Records, RecordsMut}, + settings::TableOption, +}; + +/// Set a tab size. +/// +/// The size is used in order to calculate width correctly. +/// +/// Default value is 4 (basically 1 '\t' equals 4 spaces). +/// +/// IMPORTANT: The tab character might be not present in output, +/// it might be replaced by spaces. +/// +/// # Example +/// +/// ``` +/// use tabled::{Table, settings::formatting::TabSize}; +/// +/// let text = "Some\ttext\t\twith \\tabs"; +/// +/// let mut table = Table::new([text]); +/// table.with(TabSize::new(4)); +/// +/// assert_eq!( +/// table.to_string(), +/// "+--------------------------------+\n\ +/// | &str |\n\ +/// +--------------------------------+\n\ +/// | Some text with \\tabs |\n\ +/// +--------------------------------+" +/// ) +/// ``` +#[derive(Debug, Default, Clone)] +pub struct TabSize(usize); + +impl TabSize { + /// Creates new [`TabSize`] object. + pub fn new(size: usize) -> Self { + Self(size) + } +} + +impl<R, D, C> TableOption<R, D, C> for TabSize +where + for<'a> &'a R: Records, + R: RecordsMut<String>, +{ + fn change(self, records: &mut R, _: &mut C, _: &mut D) { + let mut list = vec![]; + for (row, cells) in records.iter_rows().into_iter().enumerate() { + for (col, text) in cells.into_iter().enumerate() { + let text = text.as_ref().replace('\t', &" ".repeat(self.0)); + list.push(((row, col), text)); + } + } + + for (pos, text) in list { + records.set(pos, text); + } + } +} diff --git a/vendor/tabled/src/settings/formatting/trim_strategy.rs b/vendor/tabled/src/settings/formatting/trim_strategy.rs new file mode 100644 index 000000000..64ec7e10a --- /dev/null +++ b/vendor/tabled/src/settings/formatting/trim_strategy.rs @@ -0,0 +1,118 @@ +use crate::{ + grid::config::ColoredConfig, + grid::config::Entity, + settings::{CellOption, TableOption}, +}; + +/// `TrimStrategy` determines if it's allowed to use empty space while doing [`Alignment`]. +/// +/// # Examples +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{ +/// Style, Modify, Alignment, object::Segment, +/// formatting::{TrimStrategy, AlignmentStrategy} +/// } +/// }; +/// +/// let mut table = Table::new(&[" Hello World"]); +/// table +/// .with(Style::modern()) +/// .with( +/// Modify::new(Segment::all()) +/// .with(Alignment::left()) +/// .with(TrimStrategy::Horizontal) +/// ); +/// +/// // Note that nothing was changed exactly. +/// +/// assert_eq!( +/// table.to_string(), +/// "┌────────────────┐\n\ +/// │ &str │\n\ +/// ├────────────────┤\n\ +/// │ Hello World │\n\ +/// └────────────────┘" +/// ); +/// +/// // To trim lines you would need also set [`AlignmentStrategy`]. +/// table.with(Modify::new(Segment::all()).with(AlignmentStrategy::PerLine)); +/// +/// assert_eq!( +/// table.to_string(), +/// "┌────────────────┐\n\ +/// │ &str │\n\ +/// ├────────────────┤\n\ +/// │ Hello World │\n\ +/// └────────────────┘" +/// ); +/// +/// let mut table = Table::new(&[" \n\n\n Hello World"]); +/// table +/// .with(Style::modern()) +/// .with( +/// Modify::new(Segment::all()) +/// .with(Alignment::center()) +/// .with(Alignment::top()) +/// .with(TrimStrategy::Vertical) +/// ); +/// +/// assert_eq!( +/// table.to_string(), +/// "┌─────────────────┐\n\ +/// │ &str │\n\ +/// ├─────────────────┤\n\ +/// │ Hello World │\n\ +/// │ │\n\ +/// │ │\n\ +/// │ │\n\ +/// └─────────────────┘" +/// ); +/// ``` +/// +/// [`Alignment`]: crate::settings::Alignment +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum TrimStrategy { + /// Allow vertical trim. + Vertical, + /// Allow horizontal trim. + Horizontal, + /// Allow horizontal and vertical trim. + Both, + /// Doesn't allow any trim. + None, +} + +impl<R> CellOption<R, ColoredConfig> for TrimStrategy { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let mut formatting = *cfg.get_formatting(entity); + + // todo: could be changed to be a struct an enum like consts in `impl` block. + match self { + TrimStrategy::Vertical => { + formatting.vertical_trim = true; + } + TrimStrategy::Horizontal => { + formatting.horizontal_trim = true; + } + TrimStrategy::Both => { + formatting.vertical_trim = true; + formatting.horizontal_trim = true; + } + TrimStrategy::None => { + formatting.vertical_trim = false; + formatting.horizontal_trim = false; + } + } + + cfg.set_formatting(entity, formatting); + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for TrimStrategy { + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + <Self as CellOption<_, _>>::change(self, records, cfg, Entity::Global) + } +} diff --git a/vendor/tabled/src/settings/height/cell_height_increase.rs b/vendor/tabled/src/settings/height/cell_height_increase.rs new file mode 100644 index 000000000..39887f9f6 --- /dev/null +++ b/vendor/tabled/src/settings/height/cell_height_increase.rs @@ -0,0 +1,98 @@ +use crate::{ + grid::config::ColoredConfig, + grid::config::Entity, + grid::dimension::CompleteDimensionVecRecords, + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + grid::util::string::count_lines, + settings::{measurement::Measurement, peaker::Peaker, CellOption, Height, TableOption}, +}; + +use super::TableHeightIncrease; + +/// A modification for cell/table to increase its height. +/// +/// If used for a [`Table`] [`PriorityNone`] is used. +/// +/// [`PriorityNone`]: crate::settings::peaker::PriorityNone +/// [`Table`]: crate::Table +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CellHeightIncrease<W = usize> { + height: W, +} + +impl<W> CellHeightIncrease<W> { + /// Creates a new object of the structure. + pub fn new(height: W) -> Self + where + W: Measurement<Height>, + { + Self { height } + } + + /// The priority makes scence only for table, so the function + /// converts it to [`TableHeightIncrease`] with a given priority. + pub fn priority<P>(self) -> TableHeightIncrease<W, P> + where + P: Peaker, + W: Measurement<Height>, + { + TableHeightIncrease::new(self.height).priority::<P>() + } +} + +impl<W, R> CellOption<R, ColoredConfig> for CellHeightIncrease<W> +where + W: Measurement<Height>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let height = self.height.measure(&*records, cfg); + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + for pos in entity.iter(count_rows, count_columns) { + let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns; + if !is_valid_pos { + continue; + } + + let text = records.get_text(pos); + + let cell_height = count_lines(text); + if cell_height >= height { + continue; + } + + let content = add_lines(text, height - cell_height); + records.set(pos, content); + } + } +} + +impl<R, W> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> + for CellHeightIncrease<W> +where + W: Measurement<Height>, + R: Records + ExactRecords + PeekableRecords, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + let height = self.height.measure(&*records, cfg); + TableHeightIncrease::new(height).change(records, cfg, dims) + } +} + +fn add_lines(s: &str, n: usize) -> String { + let mut text = String::with_capacity(s.len() + n); + text.push_str(s); + text.extend(std::iter::repeat('\n').take(n)); + + text +} diff --git a/vendor/tabled/src/settings/height/cell_height_limit.rs b/vendor/tabled/src/settings/height/cell_height_limit.rs new file mode 100644 index 000000000..788f0300c --- /dev/null +++ b/vendor/tabled/src/settings/height/cell_height_limit.rs @@ -0,0 +1,103 @@ +use crate::{ + grid::{ + config::{ColoredConfig, Entity}, + dimension::CompleteDimensionVecRecords, + records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + util::string::{count_lines, get_lines}, + }, + settings::{measurement::Measurement, peaker::Peaker, CellOption, Height, TableOption}, +}; + +use super::table_height_limit::TableHeightLimit; + +/// A modification for cell/table to increase its height. +/// +/// If used for a [`Table`] [`PriorityNone`] is used. +/// +/// [`PriorityNone`]: crate::settings::peaker::PriorityNone +/// [`Table`]: crate::Table +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CellHeightLimit<W = usize> { + height: W, +} + +impl<W> CellHeightLimit<W> { + /// Constructs a new object. + pub fn new(height: W) -> Self + where + W: Measurement<Height>, + { + Self { height } + } + + /// Set's a priority by which the limit logic will be applied. + pub fn priority<P>(self) -> TableHeightLimit<W, P> + where + P: Peaker, + W: Measurement<Height>, + { + TableHeightLimit::new(self.height).priority::<P>() + } +} + +impl<W, R> CellOption<R, ColoredConfig> for CellHeightLimit<W> +where + W: Measurement<Height>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let height = self.height.measure(&*records, cfg); + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + for pos in entity.iter(count_rows, count_columns) { + let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns; + if !is_valid_pos { + continue; + } + + let text = records.get_text(pos); + let count_lines = count_lines(text); + + if count_lines <= height { + continue; + } + + let content = limit_lines(text, height); + records.set(pos, content); + } + } +} + +impl<R, W> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> + for CellHeightLimit<W> +where + W: Measurement<Height>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + let height = self.height.measure(&*records, cfg); + TableHeightLimit::new(height).change(records, cfg, dims) + } +} + +fn limit_lines(s: &str, n: usize) -> String { + let mut text = String::new(); + for (i, line) in get_lines(s).take(n).enumerate() { + if i > 0 { + text.push('\n'); + } + + text.push_str(&line); + } + + text +} diff --git a/vendor/tabled/src/settings/height/height_list.rs b/vendor/tabled/src/settings/height/height_list.rs new file mode 100644 index 000000000..7861dd4d2 --- /dev/null +++ b/vendor/tabled/src/settings/height/height_list.rs @@ -0,0 +1,47 @@ +use std::iter::FromIterator; + +use crate::{ + grid::dimension::CompleteDimensionVecRecords, + grid::records::{ExactRecords, Records}, + settings::TableOption, +}; + +/// A structure used to set [`Table`] height via a list of rows heights. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct HeightList { + list: Vec<usize>, +} + +impl HeightList { + /// Creates a new object. + pub fn new(list: Vec<usize>) -> Self { + Self { list } + } +} + +impl From<Vec<usize>> for HeightList { + fn from(list: Vec<usize>) -> Self { + Self::new(list) + } +} + +impl FromIterator<usize> for HeightList { + fn from_iter<T: IntoIterator<Item = usize>>(iter: T) -> Self { + Self::new(iter.into_iter().collect()) + } +} + +impl<R, C> TableOption<R, CompleteDimensionVecRecords<'static>, C> for HeightList +where + R: ExactRecords + Records, +{ + fn change(self, records: &mut R, _: &mut C, dims: &mut CompleteDimensionVecRecords<'static>) { + if self.list.len() < records.count_rows() { + return; + } + + let _ = dims.set_heights(self.list); + } +} diff --git a/vendor/tabled/src/settings/height/mod.rs b/vendor/tabled/src/settings/height/mod.rs new file mode 100644 index 000000000..ad9caff77 --- /dev/null +++ b/vendor/tabled/src/settings/height/mod.rs @@ -0,0 +1,228 @@ +//! The module contains [`Height`] structure which is responsible for a table and cell height. + +mod cell_height_increase; +mod cell_height_limit; +mod height_list; +mod table_height_increase; +mod table_height_limit; +mod util; + +use crate::settings::measurement::Measurement; + +pub use cell_height_increase::CellHeightIncrease; +pub use cell_height_limit::CellHeightLimit; +pub use height_list::HeightList; +pub use table_height_increase::TableHeightIncrease; +pub use table_height_limit::TableHeightLimit; + +/// Height is a abstract factory for height settings. +/// +/// # Example +/// +/// ``` +/// use tabled::{Table, settings::{Height, Settings}}; +/// +/// let data = vec![ +/// ("Some data", "here", "and here"), +/// ("Some data on a next", "line", "right here"), +/// ]; +/// +/// let table = Table::new(data) +/// .with(Settings::new(Height::limit(10), Height::increase(10))) +/// .to_string(); +/// +/// assert_eq!( +/// table, +/// "+---------------------+------+------------+\n\ +/// | &str | &str | &str |\n\ +/// | | | |\n\ +/// +---------------------+------+------------+\n\ +/// | Some data | here | and here |\n\ +/// | | | |\n\ +/// +---------------------+------+------------+\n\ +/// | Some data on a next | line | right here |\n\ +/// | | | |\n\ +/// +---------------------+------+------------+", +/// ) +/// ``` +#[derive(Debug)] +pub struct Height; + +impl Height { + /// Create [`CellHeightIncrease`] to set a table/cell height. + /// + /// # Example + /// + /// ## Cell height + /// + /// ``` + /// use tabled::{Table, settings::{Height, Modify, object::Columns}}; + /// + /// let data = vec![ + /// ("Some data", "here", "and here"), + /// ("Some data on a next", "line", "right here"), + /// ]; + /// + /// let table = Table::new(data) + /// .with(Modify::new(Columns::first()).with(Height::increase(5))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+---------------------+------+------------+\n\ + /// | &str | &str | &str |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// +---------------------+------+------------+\n\ + /// | Some data | here | and here |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// +---------------------+------+------------+\n\ + /// | Some data on a next | line | right here |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// +---------------------+------+------------+" + /// ) + /// ``` + /// + /// ## Table height + /// + /// ``` + /// use tabled::{Table, settings::Height}; + /// + /// let data = vec![ + /// ("Some data", "here", "and here"), + /// ("Some data on a next", "line", "right here"), + /// ]; + /// + /// let table = Table::new(data) + /// .with(Height::increase(10)) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+---------------------+------+------------+\n\ + /// | &str | &str | &str |\n\ + /// | | | |\n\ + /// +---------------------+------+------------+\n\ + /// | Some data | here | and here |\n\ + /// | | | |\n\ + /// +---------------------+------+------------+\n\ + /// | Some data on a next | line | right here |\n\ + /// | | | |\n\ + /// +---------------------+------+------------+", + /// ) + /// ``` + pub fn increase<W: Measurement<Height>>(width: W) -> CellHeightIncrease<W> { + CellHeightIncrease::new(width) + } + + /// Create [`CellHeightLimit`] to set a table/cell height. + /// + /// # Example + /// + /// ## Cell height + /// + /// ``` + /// use tabled::{Table, settings::{Height, Modify, object::Columns}}; + /// + /// let data = vec![ + /// ("Some\ndata", "here", "and here"), + /// ("Some\ndata on a next", "line", "right here"), + /// ]; + /// + /// let table = Table::new(data) + /// .with(Modify::new(Columns::first()).with(Height::limit(1))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+------+------+------------+\n\ + /// | &str | &str | &str |\n\ + /// +------+------+------------+\n\ + /// | Some | here | and here |\n\ + /// +------+------+------------+\n\ + /// | Some | line | right here |\n\ + /// +------+------+------------+" + /// ) + /// ``` + /// + /// ## Table height + /// + /// ``` + /// use tabled::{Table, settings::Height}; + /// + /// let data = vec![ + /// ("Some\ndata", "here", "and here"), + /// ("Some\ndata on a next", "line", "right here"), + /// ]; + /// + /// let table = Table::new(&data) + /// .with(Height::limit(6)) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+------+------+------------+\n\ + /// +------+------+------------+\n\ + /// | Some | here | and here |\n\ + /// +------+------+------------+\n\ + /// | Some | line | right here |\n\ + /// +------+------+------------+", + /// ); + /// + /// let table = Table::new(&data) + /// .with(Height::limit(1)) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+--+--+--+\n\ + /// +--+--+--+\n\ + /// +--+--+--+\n\ + /// +--+--+--+", + /// ); + /// ``` + pub fn limit<W: Measurement<Height>>(width: W) -> CellHeightLimit<W> { + CellHeightLimit::new(width) + } + + /// Create [`HeightList`] to set a table height to a constant list of row heights. + /// + /// Notice if you provide a list with `.len()` less than `Table::count_rows` then it will have no affect. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::{Height, Modify, object::Columns}}; + /// + /// let data = vec![ + /// ("Some\ndata", "here", "and here"), + /// ("Some\ndata on a next", "line", "right here"), + /// ]; + /// + /// let table = Table::new(data) + /// .with(Height::list([1, 0, 2])) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+----------------+------+------------+\n\ + /// | &str | &str | &str |\n\ + /// +----------------+------+------------+\n\ + /// +----------------+------+------------+\n\ + /// | Some | line | right here |\n\ + /// | data on a next | | |\n\ + /// +----------------+------+------------+", + /// ) + /// ``` + pub fn list<I: IntoIterator<Item = usize>>(rows: I) -> HeightList { + HeightList::new(rows.into_iter().collect()) + } +} diff --git a/vendor/tabled/src/settings/height/table_height_increase.rs b/vendor/tabled/src/settings/height/table_height_increase.rs new file mode 100644 index 000000000..f727bf041 --- /dev/null +++ b/vendor/tabled/src/settings/height/table_height_increase.rs @@ -0,0 +1,90 @@ +use crate::{ + grid::{ + config::ColoredConfig, + dimension::CompleteDimensionVecRecords, + records::{ExactRecords, PeekableRecords, Records}, + }, + settings::{ + measurement::Measurement, + peaker::{Peaker, PriorityNone}, + Height, TableOption, + }, +}; + +use super::util::get_table_height; + +/// A modification of a table to increase the table height. +#[derive(Debug, Clone)] +pub struct TableHeightIncrease<W = usize, P = PriorityNone> { + height: W, + priority: P, +} + +impl<W> TableHeightIncrease<W, PriorityNone> { + /// Creates a new object. + pub fn new(height: W) -> Self + where + W: Measurement<Height>, + { + Self { + height, + priority: PriorityNone::default(), + } + } + + /// Sets a different priority logic. + pub fn priority<P>(self) -> TableHeightIncrease<W, P> + where + P: Peaker, + { + TableHeightIncrease { + priority: P::create(), + height: self.height, + } + } +} + +impl<R, W, P> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> + for TableHeightIncrease<W, P> +where + W: Measurement<Height>, + P: Peaker + Clone, + R: Records + ExactRecords + PeekableRecords, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + if records.count_rows() == 0 || records.count_columns() == 0 { + return; + } + + let height = self.height.measure(&*records, cfg); + let (total, mut heights) = get_table_height(&*records, cfg); + if total >= height { + return; + } + + get_increase_list(&mut heights, height, total, self.priority); + + let _ = dims.set_heights(heights); + } +} + +fn get_increase_list<P>(list: &mut [usize], total: usize, mut current: usize, mut peaker: P) +where + P: Peaker, +{ + while current != total { + let col = match peaker.peak(&[], list) { + Some(col) => col, + None => break, + }; + + list[col] += 1; + current += 1; + } +} diff --git a/vendor/tabled/src/settings/height/table_height_limit.rs b/vendor/tabled/src/settings/height/table_height_limit.rs new file mode 100644 index 000000000..2a3d19bb7 --- /dev/null +++ b/vendor/tabled/src/settings/height/table_height_limit.rs @@ -0,0 +1,123 @@ +use crate::{ + grid::{ + config::ColoredConfig, + dimension::CompleteDimensionVecRecords, + records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + util::string::{count_lines, get_lines}, + }, + settings::{ + measurement::Measurement, + peaker::{Peaker, PriorityNone}, + Height, TableOption, + }, +}; + +use super::util::get_table_height; + +/// A modification of a table to decrease the table height. +#[derive(Debug)] +pub struct TableHeightLimit<W = usize, P = PriorityNone> { + height: W, + priority: P, +} + +impl<W> TableHeightLimit<W, PriorityNone> { + /// Creates a new object. + pub fn new(height: W) -> Self + where + W: Measurement<Height>, + { + Self { + height, + priority: PriorityNone::default(), + } + } + + /// Sets a different priority logic. + pub fn priority<P>(self) -> TableHeightLimit<W, P> + where + P: Peaker, + { + TableHeightLimit { + priority: P::create(), + height: self.height, + } + } +} + +impl<R, W, P> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> + for TableHeightLimit<W, P> +where + W: Measurement<Height>, + P: Peaker + Clone, + R: ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + let count_rows = records.count_rows(); + let count_cols = (&*records).count_columns(); + + if count_rows == 0 || count_cols == 0 { + return; + } + + let height = self.height.measure(&*records, cfg); + let (total, mut heights) = get_table_height(&*records, cfg); + if total <= height { + return; + } + + decrease_list(&mut heights, total, height, self.priority); + + for (row, &height) in heights.iter().enumerate() { + for col in 0..count_cols { + let text = records.get_text((row, col)); + let count_lines = count_lines(text); + + if count_lines <= height { + continue; + } + + let text = limit_lines(text, height); + + records.set((row, col), text); + } + } + + let _ = dims.set_heights(heights); + } +} + +fn decrease_list<P>(list: &mut [usize], total: usize, mut value: usize, mut peaker: P) +where + P: Peaker, +{ + while value != total { + let p = peaker.peak(&[], list); + let row = match p { + Some(row) => row, + None => break, + }; + + list[row] -= 1; + value += 1; + } +} + +fn limit_lines(s: &str, n: usize) -> String { + let mut text = String::new(); + for (i, line) in get_lines(s).take(n).enumerate() { + if i > 0 { + text.push('\n'); + } + + text.push_str(&line); + } + + text +} diff --git a/vendor/tabled/src/settings/height/util.rs b/vendor/tabled/src/settings/height/util.rs new file mode 100644 index 000000000..c6917d925 --- /dev/null +++ b/vendor/tabled/src/settings/height/util.rs @@ -0,0 +1,22 @@ +use crate::grid::{ + config::SpannedConfig, + dimension::SpannedGridDimension, + records::{ExactRecords, Records}, +}; + +pub(crate) fn get_table_height<R: Records + ExactRecords>( + records: R, + cfg: &SpannedConfig, +) -> (usize, Vec<usize>) { + let count_horizontals = cfg.count_horizontal(records.count_rows()); + + let margin = cfg.get_margin(); + let margin_size = margin.top.size + margin.bottom.size; + + let list = SpannedGridDimension::height(records, cfg); + let total = list.iter().sum::<usize>(); + + let total = total + count_horizontals + margin_size; + + (total, list) +} diff --git a/vendor/tabled/src/settings/highlight/mod.rs b/vendor/tabled/src/settings/highlight/mod.rs new file mode 100644 index 000000000..57df867b3 --- /dev/null +++ b/vendor/tabled/src/settings/highlight/mod.rs @@ -0,0 +1,452 @@ +//! This module contains a [`Highlight`] primitive, which helps +//! changing a [`Border`] style of any segment on a [`Table`]. +//! +//! [`Table`]: crate::Table + +use std::collections::HashSet; + +use crate::{ + grid::{ + config::{Border as GridBorder, ColoredConfig, Entity, Position, SpannedConfig}, + records::{ExactRecords, Records}, + }, + settings::{object::Object, style::BorderColor, Border, TableOption}, +}; + +/// Highlight modifies a table style by changing a border of a target [`Table`] segment. +/// +/// # Example +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{Highlight, Border, Style, object::Segment} +/// }; +/// +/// let data = [ +/// ("ELF", "Extensible Linking Format", true), +/// ("DWARF", "", true), +/// ("PE", "Portable Executable", false), +/// ]; +/// +/// let table = Table::new(data.iter().enumerate()) +/// .with(Style::markdown()) +/// .with(Highlight::new(Segment::all(), Border::default().top('^').bottom('v'))) +/// .to_string(); +/// +/// assert_eq!( +/// table, +/// concat!( +/// " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \n", +/// "| usize | &str | &str | bool |\n", +/// "|-------|-------|---------------------------|-------|\n", +/// "| 0 | ELF | Extensible Linking Format | true |\n", +/// "| 1 | DWARF | | true |\n", +/// "| 2 | PE | Portable Executable | false |\n", +/// " vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ", +/// ), +/// ); +/// ``` +/// +/// It's possible to use [`Highlight`] for many kinds of figures. +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{ +/// Highlight, Border, Style, +/// object::{Segment, Object} +/// } +/// }; +/// +/// let data = [ +/// ("ELF", "Extensible Linking Format", true), +/// ("DWARF", "", true), +/// ("PE", "Portable Executable", false), +/// ]; +/// +/// let table = Table::new(data.iter().enumerate()) +/// .with(Style::markdown()) +/// .with(Highlight::new(Segment::all().not((0,0).and((1, 0)).and((0, 1)).and((0, 3))), Border::filled('*'))) +/// .to_string(); +/// +/// println!("{}", table); +/// +/// assert_eq!( +/// table, +/// concat!( +/// " ***************************** \n", +/// "| usize | &str * &str * bool |\n", +/// "|-------*********---------------------------*********\n", +/// "| 0 * ELF | Extensible Linking Format | true *\n", +/// "********* *\n", +/// "* 1 | DWARF | | true *\n", +/// "* *\n", +/// "* 2 | PE | Portable Executable | false *\n", +/// "*****************************************************", +/// ), +/// ); +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Highlight<O> { + target: O, + border: Border, +} + +// todo: Add BorderColor. + +impl<O> Highlight<O> { + /// Build a new instance of [`Highlight`] + /// + /// BE AWARE: if target exceeds boundaries it may panic. + pub fn new(target: O, border: Border) -> Self { + Self { target, border } + } +} + +impl<O> Highlight<O> { + /// Build a new instance of [`HighlightColored`] + pub fn colored(target: O, border: BorderColor) -> HighlightColored<O> { + HighlightColored { target, border } + } +} + +impl<O, R, D> TableOption<R, D, ColoredConfig> for Highlight<O> +where + O: Object<R>, + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + let cells = self.target.cells(records); + let segments = split_segments(cells, count_rows, count_cols); + + for sector in segments { + set_border(cfg, §or, self.border); + } + } +} + +/// A [`Highlight`] object which works with a [`BorderColored`] +/// +/// [`BorderColored`]: crate::settings::style::BorderColor +#[derive(Debug)] +pub struct HighlightColored<O> { + target: O, + border: BorderColor, +} + +impl<O, R, D> TableOption<R, D, ColoredConfig> for HighlightColored<O> +where + O: Object<R>, + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + let cells = self.target.cells(records); + let segments = split_segments(cells, count_rows, count_cols); + + for sector in segments { + set_border_color(cfg, sector, &self.border); + } + } +} + +fn set_border_color( + cfg: &mut SpannedConfig, + sector: HashSet<(usize, usize)>, + border: &BorderColor, +) { + if sector.is_empty() { + return; + } + let color = border.clone().into(); + for &(row, col) in §or { + let border = build_cell_border(§or, (row, col), &color); + cfg.set_border_color((row, col), border); + } +} + +fn split_segments( + cells: impl Iterator<Item = Entity>, + count_rows: usize, + count_cols: usize, +) -> Vec<HashSet<(usize, usize)>> { + let mut segments: Vec<HashSet<(usize, usize)>> = Vec::new(); + for entity in cells { + for cell in entity.iter(count_rows, count_cols) { + let found_segment = segments + .iter_mut() + .find(|s| s.iter().any(|&c| is_cell_connected(cell, c))); + + match found_segment { + Some(segment) => { + let _ = segment.insert(cell); + } + None => { + let mut segment = HashSet::new(); + let _ = segment.insert(cell); + segments.push(segment); + } + } + } + } + + let mut squashed_segments: Vec<HashSet<(usize, usize)>> = Vec::new(); + while !segments.is_empty() { + let mut segment = segments.remove(0); + + let mut i = 0; + while i < segments.len() { + if is_segment_connected(&segment, &segments[i]) { + segment.extend(&segments[i]); + let _ = segments.remove(i); + } else { + i += 1; + } + } + + squashed_segments.push(segment); + } + + squashed_segments +} + +fn is_cell_connected((row1, col1): (usize, usize), (row2, col2): (usize, usize)) -> bool { + if col1 == col2 && row1 == row2 + 1 { + return true; + } + + if col1 == col2 && (row2 > 0 && row1 == row2 - 1) { + return true; + } + + if row1 == row2 && col1 == col2 + 1 { + return true; + } + + if row1 == row2 && (col2 > 0 && col1 == col2 - 1) { + return true; + } + + false +} + +fn is_segment_connected( + segment1: &HashSet<(usize, usize)>, + segment2: &HashSet<(usize, usize)>, +) -> bool { + for &cell1 in segment1.iter() { + for &cell2 in segment2.iter() { + if is_cell_connected(cell1, cell2) { + return true; + } + } + } + + false +} + +fn set_border(cfg: &mut SpannedConfig, sector: &HashSet<(usize, usize)>, border: Border) { + if sector.is_empty() { + return; + } + + let border = border.into(); + for &pos in sector { + let border = build_cell_border(sector, pos, &border); + cfg.set_border(pos, border); + } +} + +fn build_cell_border<T>( + sector: &HashSet<(usize, usize)>, + (row, col): Position, + border: &GridBorder<T>, +) -> GridBorder<T> +where + T: Default + Clone, +{ + let cell_has_top_neighbor = cell_has_top_neighbor(sector, row, col); + let cell_has_bottom_neighbor = cell_has_bottom_neighbor(sector, row, col); + let cell_has_left_neighbor = cell_has_left_neighbor(sector, row, col); + let cell_has_right_neighbor = cell_has_right_neighbor(sector, row, col); + + let this_has_left_top_neighbor = is_there_left_top_cell(sector, row, col); + let this_has_right_top_neighbor = is_there_right_top_cell(sector, row, col); + let this_has_left_bottom_neighbor = is_there_left_bottom_cell(sector, row, col); + let this_has_right_bottom_neighbor = is_there_right_bottom_cell(sector, row, col); + + let mut cell_border = GridBorder::default(); + if let Some(c) = border.top.clone() { + if !cell_has_top_neighbor { + cell_border.top = Some(c.clone()); + + if cell_has_right_neighbor && !this_has_right_top_neighbor { + cell_border.right_top_corner = Some(c); + } + } + } + if let Some(c) = border.bottom.clone() { + if !cell_has_bottom_neighbor { + cell_border.bottom = Some(c.clone()); + + if cell_has_right_neighbor && !this_has_right_bottom_neighbor { + cell_border.right_bottom_corner = Some(c); + } + } + } + if let Some(c) = border.left.clone() { + if !cell_has_left_neighbor { + cell_border.left = Some(c.clone()); + + if cell_has_bottom_neighbor && !this_has_left_bottom_neighbor { + cell_border.left_bottom_corner = Some(c); + } + } + } + if let Some(c) = border.right.clone() { + if !cell_has_right_neighbor { + cell_border.right = Some(c.clone()); + + if cell_has_bottom_neighbor && !this_has_right_bottom_neighbor { + cell_border.right_bottom_corner = Some(c); + } + } + } + if let Some(c) = border.left_top_corner.clone() { + if !cell_has_left_neighbor && !cell_has_top_neighbor { + cell_border.left_top_corner = Some(c); + } + } + if let Some(c) = border.left_bottom_corner.clone() { + if !cell_has_left_neighbor && !cell_has_bottom_neighbor { + cell_border.left_bottom_corner = Some(c); + } + } + if let Some(c) = border.right_top_corner.clone() { + if !cell_has_right_neighbor && !cell_has_top_neighbor { + cell_border.right_top_corner = Some(c); + } + } + if let Some(c) = border.right_bottom_corner.clone() { + if !cell_has_right_neighbor && !cell_has_bottom_neighbor { + cell_border.right_bottom_corner = Some(c); + } + } + { + if !cell_has_bottom_neighbor { + if !cell_has_left_neighbor && this_has_left_top_neighbor { + if let Some(c) = border.right_top_corner.clone() { + cell_border.left_top_corner = Some(c); + } + } + + if cell_has_left_neighbor && this_has_left_bottom_neighbor { + if let Some(c) = border.left_top_corner.clone() { + cell_border.left_bottom_corner = Some(c); + } + } + + if !cell_has_right_neighbor && this_has_right_top_neighbor { + if let Some(c) = border.left_top_corner.clone() { + cell_border.right_top_corner = Some(c); + } + } + + if cell_has_right_neighbor && this_has_right_bottom_neighbor { + if let Some(c) = border.right_top_corner.clone() { + cell_border.right_bottom_corner = Some(c); + } + } + } + + if !cell_has_top_neighbor { + if !cell_has_left_neighbor && this_has_left_bottom_neighbor { + if let Some(c) = border.right_bottom_corner.clone() { + cell_border.left_bottom_corner = Some(c); + } + } + + if cell_has_left_neighbor && this_has_left_top_neighbor { + if let Some(c) = border.left_bottom_corner.clone() { + cell_border.left_top_corner = Some(c); + } + } + + if !cell_has_right_neighbor && this_has_right_bottom_neighbor { + if let Some(c) = border.left_bottom_corner.clone() { + cell_border.right_bottom_corner = Some(c); + } + } + + if cell_has_right_neighbor && this_has_right_top_neighbor { + if let Some(c) = border.right_bottom_corner.clone() { + cell_border.right_top_corner = Some(c); + } + } + } + } + + cell_border +} + +fn cell_has_top_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + row > 0 && sector.contains(&(row - 1, col)) +} + +fn cell_has_bottom_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + sector.contains(&(row + 1, col)) +} + +fn cell_has_left_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + col > 0 && sector.contains(&(row, col - 1)) +} + +fn cell_has_right_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + sector.contains(&(row, col + 1)) +} + +fn is_there_left_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + row > 0 && col > 0 && sector.contains(&(row - 1, col - 1)) +} + +fn is_there_right_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + row > 0 && sector.contains(&(row - 1, col + 1)) +} + +fn is_there_left_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + col > 0 && sector.contains(&(row + 1, col - 1)) +} + +fn is_there_right_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + sector.contains(&(row + 1, col + 1)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_connected() { + assert!(is_cell_connected((0, 0), (0, 1))); + assert!(is_cell_connected((0, 0), (1, 0))); + assert!(!is_cell_connected((0, 0), (1, 1))); + + assert!(is_cell_connected((0, 1), (0, 0))); + assert!(is_cell_connected((1, 0), (0, 0))); + assert!(!is_cell_connected((1, 1), (0, 0))); + + assert!(is_cell_connected((1, 1), (0, 1))); + assert!(is_cell_connected((1, 1), (1, 0))); + assert!(is_cell_connected((1, 1), (2, 1))); + assert!(is_cell_connected((1, 1), (1, 2))); + assert!(!is_cell_connected((1, 1), (1, 1))); + } +} diff --git a/vendor/tabled/src/settings/locator/mod.rs b/vendor/tabled/src/settings/locator/mod.rs new file mode 100644 index 000000000..b48391bb7 --- /dev/null +++ b/vendor/tabled/src/settings/locator/mod.rs @@ -0,0 +1,202 @@ +//! The module contains a [`Locator`] trait and implementations for it. + +use core::ops::Bound; +use std::{ + iter::{self, Once}, + ops::{Range, RangeBounds}, +}; + +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, PeekableRecords, Records}, + settings::object::{ + Column, Columns, FirstColumn, FirstRow, LastColumn, LastRow, Object, Row, Rows, + }, +}; + +/// Locator is an interface which searches for a particular thing in the [`Records`], +/// and returns coordinate of the foundings if any. +pub trait Locator<Records> { + /// A coordinate of the finding. + type Coordinate; + /// An iterator of the coordinates. + /// If it's empty it's considered that nothing is found. + type IntoIter: IntoIterator<Item = Self::Coordinate>; + + /// Search for the thing in [`Records`], returning a list of coordinates. + fn locate(&mut self, records: Records) -> Self::IntoIter; +} + +impl<B, R> Locator<R> for Columns<B> +where + B: RangeBounds<usize>, + R: Records, +{ + type Coordinate = usize; + type IntoIter = Range<usize>; + + fn locate(&mut self, records: R) -> Self::IntoIter { + let range = self.get_range(); + let max = records.count_columns(); + let (from, to) = bounds_to_usize(range.start_bound(), range.end_bound(), max); + + from..to + } +} + +impl<R> Locator<R> for Column { + type Coordinate = usize; + type IntoIter = Once<usize>; + + fn locate(&mut self, _: R) -> Self::IntoIter { + iter::once((*self).into()) + } +} + +impl<R> Locator<R> for FirstColumn { + type Coordinate = usize; + type IntoIter = Once<usize>; + + fn locate(&mut self, _: R) -> Self::IntoIter { + iter::once(0) + } +} + +impl<R> Locator<R> for LastColumn +where + R: Records, +{ + type Coordinate = usize; + type IntoIter = Once<usize>; + + fn locate(&mut self, records: R) -> Self::IntoIter { + if records.count_columns() > 0 { + iter::once(records.count_columns() - 1) + } else { + iter::once(0) + } + } +} + +impl<B, R> Locator<R> for Rows<B> +where + R: Records, + B: RangeBounds<usize>, +{ + type Coordinate = usize; + type IntoIter = Range<usize>; + + fn locate(&mut self, records: R) -> Self::IntoIter { + let (from, to) = bounds_to_usize( + self.get_range().start_bound(), + self.get_range().end_bound(), + records.count_columns(), + ); + + from..to + } +} + +impl<R> Locator<R> for Row { + type Coordinate = usize; + type IntoIter = Once<usize>; + + fn locate(&mut self, _: R) -> Self::IntoIter { + iter::once((*self).into()) + } +} + +impl<R> Locator<R> for FirstRow { + type Coordinate = usize; + type IntoIter = Once<usize>; + + fn locate(&mut self, _: R) -> Self::IntoIter { + iter::once(0) + } +} + +impl<R> Locator<R> for LastRow +where + R: ExactRecords, +{ + type Coordinate = usize; + type IntoIter = Once<usize>; + + fn locate(&mut self, records: R) -> Self::IntoIter { + if records.count_rows() > 0 { + iter::once(records.count_rows() - 1) + } else { + iter::once(0) + } + } +} + +/// The structure is an implementation of [`Locator`] to search for a column by it's name. +/// A name is considered be a value in a first row. +/// +/// So even if in reality there's no header, the first row will be considered to be one. +#[derive(Debug, Clone, Copy)] +pub struct ByColumnName<S>(S); + +impl<S> ByColumnName<S> { + /// Constructs a new object of the structure. + pub fn new(text: S) -> Self + where + S: AsRef<str>, + { + Self(text) + } +} + +impl<R, S> Locator<R> for ByColumnName<S> +where + S: AsRef<str>, + R: Records + ExactRecords + PeekableRecords, +{ + type Coordinate = usize; + type IntoIter = Vec<usize>; + + fn locate(&mut self, records: R) -> Self::IntoIter { + // todo: can be optimized by creating Iterator + (0..records.count_columns()) + .filter(|col| records.get_text((0, *col)) == self.0.as_ref()) + .collect::<Vec<_>>() + } +} + +impl<S, R> Object<R> for ByColumnName<S> +where + S: AsRef<str>, + R: Records + PeekableRecords + ExactRecords, +{ + type Iter = std::vec::IntoIter<Entity>; + + fn cells(&self, records: &R) -> Self::Iter { + // todo: can be optimized by creating Iterator + (0..records.count_columns()) + .filter(|col| records.get_text((0, *col)) == self.0.as_ref()) + .map(Entity::Column) + .collect::<Vec<_>>() + .into_iter() + } +} + +fn bounds_to_usize( + left: Bound<&usize>, + right: Bound<&usize>, + count_elements: usize, +) -> (usize, usize) { + match (left, right) { + (Bound::Included(x), Bound::Included(y)) => (*x, y + 1), + (Bound::Included(x), Bound::Excluded(y)) => (*x, *y), + (Bound::Included(x), Bound::Unbounded) => (*x, count_elements), + (Bound::Unbounded, Bound::Unbounded) => (0, count_elements), + (Bound::Unbounded, Bound::Included(y)) => (0, y + 1), + (Bound::Unbounded, Bound::Excluded(y)) => (0, *y), + (Bound::Excluded(_), Bound::Unbounded) + | (Bound::Excluded(_), Bound::Included(_)) + | (Bound::Excluded(_), Bound::Excluded(_)) => { + unreachable!("A start bound can't be excluded") + } + } +} diff --git a/vendor/tabled/src/settings/margin/mod.rs b/vendor/tabled/src/settings/margin/mod.rs new file mode 100644 index 000000000..b86a1d3e2 --- /dev/null +++ b/vendor/tabled/src/settings/margin/mod.rs @@ -0,0 +1,137 @@ +//! This module contains a Margin settings of a [`Table`]. +//! +//! # Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{settings::{Margin, Style}, Table}; +//! +//! let data = vec!["Hello", "World", "!"]; +//! +//! let mut table = Table::new(data); +//! table.with(Style::markdown()).with(Margin::new(3, 3, 1, 0)); +//! +//! assert_eq!( +//! table.to_string(), +//! concat!( +//! " \n", +//! " | &str | \n", +//! " |-------| \n", +//! " | Hello | \n", +//! " | World | \n", +//! " | ! | ", +//! ) +//! ); +//! ``` +//! +//! [`Table`]: crate::Table + +use crate::{ + grid::{ + color::StaticColor, + config::{CompactConfig, CompactMultilineConfig}, + config::{Indent, Sides}, + }, + settings::TableOption, +}; + +#[cfg(feature = "std")] +use crate::grid::{color::AnsiColor, config::ColoredConfig}; + +/// Margin is responsible for a left/right/top/bottom outer indent of a grid. +/// +#[cfg_attr(feature = "std", doc = "```")] +#[cfg_attr(not(feature = "std"), doc = "```ignore")] +/// # use tabled::{settings::Margin, Table}; +/// # let data: Vec<&'static str> = Vec::new(); +/// let table = Table::new(&data) +/// .with(Margin::new(1, 1, 1, 1).fill('>', '<', 'V', '^')); +/// ``` +#[derive(Debug, Clone)] +pub struct Margin<C = StaticColor> { + indent: Sides<Indent>, + colors: Option<Sides<C>>, +} + +impl Margin { + /// Construct's an Margin object. + /// + /// It uses space(' ') as a default fill character. + /// To set a custom character you can use [`Margin::fill`] function. + pub const fn new(left: usize, right: usize, top: usize, bottom: usize) -> Self { + Self { + indent: Sides::new( + Indent::spaced(left), + Indent::spaced(right), + Indent::spaced(top), + Indent::spaced(bottom), + ), + colors: None, + } + } +} + +impl<Color> Margin<Color> { + /// The function, sets a characters for the margin on an each side. + pub const fn fill(mut self, left: char, right: char, top: char, bottom: char) -> Self { + self.indent.left.fill = left; + self.indent.right.fill = right; + self.indent.top.fill = top; + self.indent.bottom.fill = bottom; + self + } + + /// The function, sets a characters for the margin on an each side. + pub fn colorize<C>(self, left: C, right: C, top: C, bottom: C) -> Margin<C> { + Margin { + indent: self.indent, + colors: Some(Sides::new(left, right, top, bottom)), + } + } +} + +#[cfg(feature = "std")] +impl<R, D, C> TableOption<R, D, ColoredConfig> for Margin<C> +where + C: Into<AnsiColor<'static>> + Clone, +{ + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let indent = self.indent; + let margin = Sides::new(indent.left, indent.right, indent.top, indent.bottom); + cfg.set_margin(margin); + + if let Some(colors) = &self.colors { + let margin = Sides::new( + Some(colors.left.clone().into()), + Some(colors.right.clone().into()), + Some(colors.top.clone().into()), + Some(colors.bottom.clone().into()), + ); + cfg.set_margin_color(margin); + } + } +} + +impl<R, D, C> TableOption<R, D, CompactConfig> for Margin<C> +where + C: Into<StaticColor> + Clone, +{ + fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { + *cfg = cfg.set_margin(self.indent); + + if let Some(c) = self.colors { + // todo: make a new method (BECAUSE INTO doesn't work) try_into(); + let colors = Sides::new(c.left.into(), c.right.into(), c.top.into(), c.bottom.into()); + *cfg = cfg.set_margin_color(colors); + } + } +} + +impl<R, D, C> TableOption<R, D, CompactMultilineConfig> for Margin<C> +where + C: Into<StaticColor> + Clone, +{ + fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) { + self.change(records, cfg.as_mut(), dimension) + } +} diff --git a/vendor/tabled/src/settings/measurement/mod.rs b/vendor/tabled/src/settings/measurement/mod.rs new file mode 100644 index 000000000..f93cc6b4e --- /dev/null +++ b/vendor/tabled/src/settings/measurement/mod.rs @@ -0,0 +1,161 @@ +//! The module contains [`Measurement`] trait and its implementations to be used in [`Height`] and [`Width`].; + +use crate::{ + grid::config::SpannedConfig, + grid::dimension::SpannedGridDimension, + grid::records::{ExactRecords, PeekableRecords, Records}, + grid::util::string::{self, string_width_multiline}, + settings::{Height, Width}, +}; + +/// A width value which can be obtained on behalf of [`Table`]. +/// +/// [`Table`]: crate::Table +pub trait Measurement<Attribute> { + /// Returns a measurement value. + fn measure<R: Records + ExactRecords + PeekableRecords>( + &self, + records: R, + cfg: &SpannedConfig, + ) -> usize; +} + +impl<T> Measurement<T> for usize { + fn measure<R>(&self, _: R, _: &SpannedConfig) -> usize { + *self + } +} + +/// Max width value. +#[derive(Debug)] +pub struct Max; + +impl Measurement<Width> for Max { + fn measure<R: Records + ExactRecords + PeekableRecords>( + &self, + records: R, + _: &SpannedConfig, + ) -> usize { + grid_widths(&records) + .map(|r| r.max().unwrap_or(0)) + .max() + .unwrap_or(0) + } +} + +impl Measurement<Height> for Max { + fn measure<R: Records + ExactRecords + PeekableRecords>( + &self, + records: R, + _: &SpannedConfig, + ) -> usize { + records_heights(&records) + .map(|r| r.max().unwrap_or(0)) + .max() + .unwrap_or(0) + } +} + +/// Min width value. +#[derive(Debug)] +pub struct Min; + +impl Measurement<Width> for Min { + fn measure<R: Records + ExactRecords + PeekableRecords>( + &self, + records: R, + _: &SpannedConfig, + ) -> usize { + grid_widths(&records) + .map(|r| r.min().unwrap_or(0)) + .max() + .unwrap_or(0) + } +} + +impl Measurement<Height> for Min { + fn measure<R: Records + ExactRecords + PeekableRecords>( + &self, + records: R, + _: &SpannedConfig, + ) -> usize { + records_heights(&records) + .map(|r| r.max().unwrap_or(0)) + .min() + .unwrap_or(0) + } +} + +/// Percent from a total table width. +#[derive(Debug)] +pub struct Percent(pub usize); + +impl Measurement<Width> for Percent { + fn measure<R>(&self, records: R, cfg: &SpannedConfig) -> usize + where + R: Records, + { + let (_, total) = get_table_widths_with_total(records, cfg); + (total * self.0) / 100 + } +} + +impl Measurement<Height> for Percent { + fn measure<R>(&self, records: R, cfg: &SpannedConfig) -> usize + where + R: Records + ExactRecords, + { + let (_, total) = get_table_heights_width_total(records, cfg); + (total * self.0) / 100 + } +} + +fn grid_widths<R: Records + ExactRecords + PeekableRecords>( + records: &R, +) -> impl Iterator<Item = impl Iterator<Item = usize> + '_> + '_ { + let (count_rows, count_cols) = (records.count_rows(), records.count_columns()); + (0..count_rows).map(move |row| { + (0..count_cols).map(move |col| string_width_multiline(records.get_text((row, col)))) + }) +} + +fn get_table_widths_with_total<R>(records: R, cfg: &SpannedConfig) -> (Vec<usize>, usize) +where + R: Records, +{ + let widths = SpannedGridDimension::width(records, cfg); + let total_width = get_table_total_width(&widths, cfg); + (widths, total_width) +} + +fn get_table_total_width(list: &[usize], cfg: &SpannedConfig) -> usize { + let total = list.iter().sum::<usize>(); + + total + cfg.count_vertical(list.len()) +} + +fn records_heights<R>(records: &R) -> impl Iterator<Item = impl Iterator<Item = usize> + '_> + '_ +where + R: Records + ExactRecords + PeekableRecords, +{ + (0..records.count_rows()).map(move |row| { + (0..records.count_columns()) + .map(move |col| string::count_lines(records.get_text((row, col)))) + }) +} + +fn get_table_heights_width_total<R>(records: R, cfg: &SpannedConfig) -> (Vec<usize>, usize) +where + R: Records, +{ + let list = SpannedGridDimension::height(records, cfg); + let total = get_table_total_height(&list, cfg); + (list, total) +} + +fn get_table_total_height(list: &[usize], cfg: &SpannedConfig) -> usize { + let total = list.iter().sum::<usize>(); + let counth = cfg.count_horizontal(list.len()); + + total + counth +} diff --git a/vendor/tabled/src/settings/merge/mod.rs b/vendor/tabled/src/settings/merge/mod.rs new file mode 100644 index 000000000..51fa2bbf6 --- /dev/null +++ b/vendor/tabled/src/settings/merge/mod.rs @@ -0,0 +1,196 @@ +//! The module contains a set of methods to merge cells together via [`Span`]s. +//! +//! [`Span`]: crate::settings::span::Span + +use crate::{ + grid::config::ColoredConfig, + grid::records::{ExactRecords, PeekableRecords, Records}, + settings::TableOption, +}; + +/// Merge to combine duplicates together, using [`Span`]. +/// +/// [`Span`]: crate::settings::span::Span +#[derive(Debug)] +pub struct Merge; + +impl Merge { + /// Vertical merge. + pub fn vertical() -> MergeDuplicatesVertical { + MergeDuplicatesVertical + } + + /// Horizontal merge. + pub fn horizontal() -> MergeDuplicatesHorizontal { + MergeDuplicatesHorizontal + } +} + +/// A modificator for [`Table`] which looks up for duplicates in columns and +/// in case of duplicate merges the cells together using [`Span`]. +/// +/// [`Table`]: crate::Table +/// [`Span`]: crate::settings::span::Span +#[derive(Debug)] +pub struct MergeDuplicatesVertical; + +impl<R, D> TableOption<R, D, ColoredConfig> for MergeDuplicatesVertical +where + R: Records + PeekableRecords + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + if count_rows == 0 || count_cols == 0 { + return; + } + + for column in 0..count_cols { + let mut repeat_length = 0; + let mut repeat_value = String::new(); + let mut repeat_is_set = false; + let mut last_is_row_span = false; + for row in (0..count_rows).rev() { + if last_is_row_span { + last_is_row_span = false; + continue; + } + + // we need to mitigate messing existing spans + let is_cell_visible = cfg.is_cell_visible((row, column)); + let is_row_span_cell = cfg.get_column_span((row, column)).is_some(); + + if !repeat_is_set { + if !is_cell_visible { + continue; + } + + if is_row_span_cell { + continue; + } + + repeat_length = 1; + repeat_value = records.get_text((row, column)).to_owned(); + repeat_is_set = true; + continue; + } + + if is_row_span_cell { + repeat_is_set = false; + last_is_row_span = true; + continue; + } + + if !is_cell_visible { + repeat_is_set = false; + continue; + } + + let text = records.get_text((row, column)); + let is_duplicate = text == repeat_value; + + if is_duplicate { + repeat_length += 1; + continue; + } + + if repeat_length > 1 { + cfg.set_row_span((row + 1, column), repeat_length); + } + + repeat_length = 1; + repeat_value = records.get_text((row, column)).to_owned(); + } + + if repeat_length > 1 { + cfg.set_row_span((0, column), repeat_length); + } + } + } +} + +/// A modificator for [`Table`] which looks up for duplicates in rows and +/// in case of duplicate merges the cells together using [`Span`]. +/// +/// [`Table`]: crate::Table +/// [`Span`]: crate::settings::span::Span +#[derive(Debug)] +pub struct MergeDuplicatesHorizontal; + +impl<R, D> TableOption<R, D, ColoredConfig> for MergeDuplicatesHorizontal +where + R: Records + PeekableRecords + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + if count_rows == 0 || count_cols == 0 { + return; + } + + for row in 0..count_rows { + let mut repeat_length = 0; + let mut repeat_value = String::new(); + let mut repeat_is_set = false; + let mut last_is_col_span = false; + + for column in (0..count_cols).rev() { + if last_is_col_span { + last_is_col_span = false; + continue; + } + + // we need to mitigate messing existing spans + let is_cell_visible = cfg.is_cell_visible((row, column)); + let is_col_span_cell = cfg.get_row_span((row, column)).is_some(); + + if !repeat_is_set { + if !is_cell_visible { + continue; + } + + if is_col_span_cell { + continue; + } + + repeat_length = 1; + repeat_value = records.get_text((row, column)).to_owned(); + repeat_is_set = true; + continue; + } + + if is_col_span_cell { + repeat_is_set = false; + last_is_col_span = true; + continue; + } + + if !is_cell_visible { + repeat_is_set = false; + continue; + } + + let text = records.get_text((row, column)); + let is_duplicate = text == repeat_value; + + if is_duplicate { + repeat_length += 1; + continue; + } + + if repeat_length > 1 { + cfg.set_column_span((row, column + 1), repeat_length); + } + + repeat_length = 1; + repeat_value = records.get_text((row, column)).to_owned(); + } + + if repeat_length > 1 { + cfg.set_column_span((row, 0), repeat_length); + } + } + } +} diff --git a/vendor/tabled/src/settings/mod.rs b/vendor/tabled/src/settings/mod.rs new file mode 100644 index 000000000..a89037cbd --- /dev/null +++ b/vendor/tabled/src/settings/mod.rs @@ -0,0 +1,135 @@ +//! Module contains various table configuration settings. +//! +//! There 2 types of settings; +//! +//! - [`CellOption`] which can modify only a cell. +//! - [`TableOption`] which can modify table as a whole. +//! +//! [`CellOption`] works on behave of [`Modify`] which is actually a [`TableOption`]. +//! +//! Notice that it's possble to combine settings together by the help of [`Settings`]. +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::{Settings, Style, Padding}}; +//! +//! let table_config = Settings::default() +//! .with(Padding::new(2, 2, 1, 1)) +//! .with(Style::rounded()); +//! +//! let data = [[2023;9]; 3]; +//! +//! let table = Table::new(data).with(table_config).to_string(); +//! +//! assert_eq!( +//! table, +//! "╭────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────╮\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! ├────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┤\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! ╰────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────╯" +//! ) +//! ``` + +mod cell_option; +mod settings_list; +mod table_option; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod object; + +#[cfg(feature = "std")] +mod modify; + +mod alignment; +mod extract; +mod margin; +mod padding; +mod rotate; + +#[cfg(feature = "std")] +mod color; +#[cfg(feature = "std")] +mod concat; +#[cfg(feature = "std")] +mod duplicate; + +pub mod style; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod disable; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod format; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod formatting; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod height; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod highlight; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod locator; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod measurement; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod merge; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod panel; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod peaker; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +mod shadow; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod span; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod split; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod themes; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod width; + +pub use cell_option::CellOption; +pub use settings_list::{EmptySettings, Settings}; +pub use table_option::TableOption; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use modify::{Modify, ModifyList}; + +pub use self::{ + alignment::Alignment, extract::Extract, margin::Margin, padding::Padding, rotate::Rotate, + style::Style, +}; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use self::{ + color::Color, concat::Concat, disable::Disable, duplicate::Dup, format::Format, height::Height, + highlight::Highlight, merge::Merge, panel::Panel, shadow::Shadow, span::Span, style::Border, + width::Width, +}; diff --git a/vendor/tabled/src/settings/modify.rs b/vendor/tabled/src/settings/modify.rs new file mode 100644 index 000000000..c712a255e --- /dev/null +++ b/vendor/tabled/src/settings/modify.rs @@ -0,0 +1,81 @@ +use crate::{ + grid::records::{ExactRecords, Records}, + settings::{object::Object, CellOption, Settings, TableOption}, +}; + +/// Modify structure provide an abstraction, to be able to apply +/// a set of [`CellOption`]s to the same object. +/// +/// Be aware that the settings are applied all to a cell at a time. +/// So sometimes you may need to make a several calls of [`Modify`] in order to achieve the desired affect. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Modify<O> { + obj: O, +} + +impl<O> Modify<O> { + /// Creates a new [`Modify`] without any options. + pub const fn new(obj: O) -> Self { + Self { obj } + } + + /// A function which combines together [`Modify::new`] and [`Modify::with`] calls. + pub const fn list<M>(obj: O, next: M) -> ModifyList<O, M> { + ModifyList { + obj, + modifiers: next, + } + } + + /// It's a generic function which stores a [`CellOption`]. + /// + /// IMPORTANT: + /// The function *doesn't* changes a [`Table`]. + /// [`Table`] will be changed only after passing [`Modify`] object to [`Table::with`]. + /// + /// [`Table`]: crate::Table + /// [`Table::with`]: crate::Table::with + pub fn with<M>(self, next: M) -> ModifyList<O, M> { + ModifyList { + obj: self.obj, + modifiers: next, + } + } +} + +/// This is a container of [`CellOption`]s which are applied to a set [`Object`]. +#[derive(Debug)] +pub struct ModifyList<O, S> { + obj: O, + modifiers: S, +} + +impl<O, M1> ModifyList<O, M1> { + /// With a generic function which stores a [`CellOption`]. + /// + /// IMPORTANT: + /// The function *doesn't* changes a [`Table`]. + /// [`Table`] will be changed only after passing [`Modify`] object to [`Table::with`]. + /// + /// [`Table`]: crate::Table + /// [`Table::with`]: crate::Table::with + pub fn with<M2>(self, next: M2) -> ModifyList<O, Settings<M1, M2>> { + ModifyList { + obj: self.obj, + modifiers: Settings::new(self.modifiers, next), + } + } +} + +impl<O, M, R, D, C> TableOption<R, D, C> for ModifyList<O, M> +where + O: Object<R>, + M: CellOption<R, C> + Clone, + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut C, _: &mut D) { + for entity in self.obj.cells(records) { + self.modifiers.clone().change(records, cfg, entity); + } + } +} diff --git a/vendor/tabled/src/settings/object/cell.rs b/vendor/tabled/src/settings/object/cell.rs new file mode 100644 index 000000000..0b0463c9a --- /dev/null +++ b/vendor/tabled/src/settings/object/cell.rs @@ -0,0 +1,70 @@ +use crate::{ + grid::config::{Entity, Position}, + settings::object::Object, +}; + +/// Cell denotes a particular cell on a [`Table`]. +/// +/// For example such table has 4 cells. +/// Which indexes are (0, 0), (0, 1), (1, 0), (1, 1). +/// +/// ```text +/// ┌───┬───┐ +/// │ 0 │ 1 │ +/// ├───┼───┤ +/// │ 1 │ 2 │ +/// └───┴───┘ +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct Cell(usize, usize); + +impl Cell { + /// Create new cell structure. + pub fn new(row: usize, col: usize) -> Self { + Self(row, col) + } +} + +impl From<Position> for Cell { + fn from((row, col): Position) -> Self { + Self(row, col) + } +} + +impl<I> Object<I> for Cell { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Cell(self.0, self.1))) + } +} + +impl<I> Object<I> for Position { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Cell(self.0, self.1))) + } +} + +/// An [`Iterator`] which returns an entity once. +#[derive(Debug)] +pub struct EntityOnce { + entity: Option<Entity>, +} + +impl EntityOnce { + pub(crate) const fn new(entity: Option<Entity>) -> Self { + Self { entity } + } +} + +impl Iterator for EntityOnce { + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + self.entity.take() + } +} diff --git a/vendor/tabled/src/settings/object/columns.rs b/vendor/tabled/src/settings/object/columns.rs new file mode 100644 index 000000000..bcbe2acf5 --- /dev/null +++ b/vendor/tabled/src/settings/object/columns.rs @@ -0,0 +1,209 @@ +use std::ops::{Add, RangeBounds, Sub}; + +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, Records}, + settings::object::{cell::EntityOnce, Object}, +}; + +use super::util::bounds_to_usize; + +/// Column denotes a set of cells on given columns on a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Columns<R> { + range: R, +} + +impl<R> Columns<R> { + /// Returns a new instance of [`Columns`] for a range of columns. + /// + /// If the boundaries are exceeded it may panic. + pub fn new(range: R) -> Self + where + R: RangeBounds<usize>, + { + Self { range } + } + + pub(crate) fn get_range(&self) -> &R { + &self.range + } +} + +impl Columns<()> { + /// Returns a new instance of [`Columns`] for a single column. + /// + /// If the boundaries are exceeded it may panic. + pub fn single(index: usize) -> Column { + Column(index) + } + + /// Returns a new instance of [`Columns`] for a first column. + /// + /// If the boundaries are exceeded the object will produce no cells. + pub fn first() -> FirstColumn { + FirstColumn + } + + /// Returns a new instance of [`Columns`] for a last column. + /// + /// If the boundaries are exceeded the object will produce no cells. + pub fn last() -> LastColumn { + LastColumn + } +} + +impl<I, R> Object<I> for Columns<R> +where + R: RangeBounds<usize>, + I: Records, +{ + type Iter = ColumnsIter; + + fn cells(&self, records: &I) -> Self::Iter { + let max = records.count_columns(); + let start = self.range.start_bound(); + let end = self.range.end_bound(); + let (x, y) = bounds_to_usize(start, end, max); + + ColumnsIter::new(x, y) + } +} + +/// `FirstColumn` represents the first column on a grid. +#[derive(Debug)] +pub struct FirstColumn; + +impl<I> Object<I> for FirstColumn +where + I: Records + ExactRecords, +{ + type Iter = EntityOnce; + + fn cells(&self, records: &I) -> Self::Iter { + if records.count_rows() == 0 || records.count_columns() == 0 { + return EntityOnce::new(None); + } + + EntityOnce::new(Some(Entity::Column(0))) + } +} + +impl Add<usize> for FirstColumn { + type Output = Column; + + fn add(self, rhs: usize) -> Self::Output { + Column(rhs) + } +} + +/// `LastColumn` represents the last column on a grid. +#[derive(Debug)] +pub struct LastColumn; + +impl<I> Object<I> for LastColumn +where + I: Records + ExactRecords, +{ + type Iter = EntityOnce; + + fn cells(&self, records: &I) -> Self::Iter { + if records.count_rows() == 0 || records.count_columns() == 0 { + return EntityOnce::new(None); + } + + let col = records.count_columns().saturating_sub(1); + EntityOnce::new(Some(Entity::Column(col))) + } +} + +impl Sub<usize> for LastColumn { + type Output = LastColumnOffset; + + fn sub(self, rhs: usize) -> Self::Output { + LastColumnOffset { offset: rhs } + } +} + +/// Column represents a single column on a grid. +#[derive(Debug, Clone, Copy)] +pub struct Column(usize); + +impl<I> Object<I> for Column { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Column(self.0))) + } +} + +impl From<usize> for Column { + fn from(i: usize) -> Self { + Self(i) + } +} + +impl From<Column> for usize { + fn from(val: Column) -> Self { + val.0 + } +} + +/// `LastColumnOffset` represents a single column on a grid indexed via offset from the last column. +#[derive(Debug)] +pub struct LastColumnOffset { + offset: usize, +} + +impl<I> Object<I> for LastColumnOffset +where + I: Records + ExactRecords, +{ + type Iter = EntityOnce; + + fn cells(&self, records: &I) -> Self::Iter { + if records.count_rows() == 0 || records.count_columns() == 0 { + return EntityOnce::new(None); + } + + let col = records.count_columns().saturating_sub(1); + if self.offset > col { + return EntityOnce::new(None); + } + + let col = col - self.offset; + EntityOnce::new(Some(Entity::Column(col))) + } +} + +/// An [`Iterator`] which goes goes over columns of a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct ColumnsIter { + start: usize, + end: usize, +} + +impl ColumnsIter { + const fn new(start: usize, end: usize) -> Self { + Self { start, end } + } +} + +impl Iterator for ColumnsIter { + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + if self.start >= self.end { + return None; + } + + let col = self.start; + self.start += 1; + + Some(Entity::Column(col)) + } +} diff --git a/vendor/tabled/src/settings/object/frame.rs b/vendor/tabled/src/settings/object/frame.rs new file mode 100644 index 000000000..caeb10640 --- /dev/null +++ b/vendor/tabled/src/settings/object/frame.rs @@ -0,0 +1,69 @@ +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, Records}, + settings::object::Object, +}; + +/// Frame includes cells which are on the edges of each side. +/// Therefore it's [`Object`] implementation returns a subset of cells which are present in frame. +#[derive(Debug)] +pub struct Frame; + +impl<I> Object<I> for Frame +where + I: Records + ExactRecords, +{ + type Iter = FrameIter; + + fn cells(&self, records: &I) -> Self::Iter { + FrameIter::new(records.count_rows(), records.count_columns()) + } +} + +/// An [`Iterator`] which goes goes over all cell on a frame of a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct FrameIter { + rows: usize, + cols: usize, + row: usize, + col: usize, +} + +impl FrameIter { + const fn new(count_rows: usize, count_columns: usize) -> Self { + Self { + rows: count_rows, + cols: count_columns, + row: 0, + col: 0, + } + } +} + +impl Iterator for FrameIter { + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + if self.cols == 0 || self.rows == 0 { + return None; + } + + if self.row == self.rows { + return None; + } + + let row = self.row; + let col = self.col; + + self.col += 1; + + if self.col == self.cols { + self.row += 1; + self.col = 0; + } + + Some(Entity::Cell(row, col)) + } +} diff --git a/vendor/tabled/src/settings/object/mod.rs b/vendor/tabled/src/settings/object/mod.rs new file mode 100644 index 000000000..46893c71d --- /dev/null +++ b/vendor/tabled/src/settings/object/mod.rs @@ -0,0 +1,765 @@ +//! This module contains a list of primitives that implement a [`Object`] trait. +//! They help to locate a necessary segment on a [`Table`]. +//! +//! [`Table`]: crate::Table + +mod cell; +mod columns; +mod frame; +mod rows; +mod segment; +pub(crate) mod util; + +use std::{collections::HashSet, marker::PhantomData}; + +use self::segment::SectorCellsIter; + +use crate::{ + grid::config::{Entity, EntityIterator}, + grid::records::{ExactRecords, Records}, +}; + +pub use cell::{Cell, EntityOnce}; +pub use columns::{Column, Columns, ColumnsIter, FirstColumn, LastColumn, LastColumnOffset}; +pub use frame::{Frame, FrameIter}; +pub use rows::{FirstRow, LastRow, LastRowOffset, Row, Rows, RowsIter}; +pub use segment::{SectorIter, Segment, SegmentAll}; + +/// Object helps to locate a necessary part of a [`Table`]. +/// +/// [`Table`]: crate::Table +pub trait Object<R> { + /// An [`Iterator`] which returns a list of cells. + type Iter: Iterator<Item = Entity>; + + /// Cells returns a set of coordinates of cells. + fn cells(&self, records: &R) -> Self::Iter; + + /// Combines cells. + /// It doesn't repeat cells. + fn and<O>(self, rhs: O) -> UnionCombination<Self, O, R> + where + Self: Sized, + { + UnionCombination::new(self, rhs) + } + + /// Excludes rhs cells from this cells. + fn not<O>(self, rhs: O) -> DiffCombination<Self, O, R> + where + Self: Sized, + { + DiffCombination::new(self, rhs) + } + + /// Returns cells which are present in both [`Object`]s only. + fn intersect<O>(self, rhs: O) -> IntersectionCombination<Self, O, R> + where + Self: Sized, + { + IntersectionCombination::new(self, rhs) + } + + /// Returns cells which are not present in target [`Object`]. + fn inverse(self) -> InversionCombination<Self, R> + where + Self: Sized, + { + InversionCombination::new(self) + } +} + +/// Combination struct used for chaining [`Object`]'s. +/// +/// Combines 2 sets of cells into one. +/// +/// Duplicates are removed from the output set. +#[derive(Debug)] +pub struct UnionCombination<L, R, I> { + lhs: L, + rhs: R, + _records: PhantomData<I>, +} + +impl<L, R, I> UnionCombination<L, R, I> { + fn new(lhs: L, rhs: R) -> Self { + Self { + lhs, + rhs, + _records: PhantomData, + } + } +} + +impl<I, L, R> Object<I> for UnionCombination<L, R, I> +where + L: Object<I>, + R: Object<I>, + I: Records + ExactRecords, +{ + type Iter = UnionIter<L::Iter, R::Iter>; + + fn cells(&self, records: &I) -> Self::Iter { + let lhs = self.lhs.cells(records); + let rhs = self.rhs.cells(records); + + UnionIter::new(lhs, rhs, records.count_rows(), records.count_columns()) + } +} + +/// Difference struct used for chaining [`Object`]'s. +/// +/// Returns cells from 1st set with removed ones from the 2nd set. +#[derive(Debug)] +pub struct DiffCombination<L, R, I> { + lhs: L, + rhs: R, + _records: PhantomData<I>, +} + +impl<L, R, I> DiffCombination<L, R, I> { + fn new(lhs: L, rhs: R) -> Self { + Self { + lhs, + rhs, + _records: PhantomData, + } + } +} + +impl<I, L, R> Object<I> for DiffCombination<L, R, I> +where + L: Object<I>, + R: Object<I>, + I: Records + ExactRecords, +{ + type Iter = DiffIter<L::Iter>; + + fn cells(&self, records: &I) -> Self::Iter { + let lhs = self.lhs.cells(records); + let rhs = self.rhs.cells(records); + + DiffIter::new(lhs, rhs, records.count_rows(), records.count_columns()) + } +} + +/// Intersection struct used for chaining [`Object`]'s. +/// +/// Returns cells which are present in 2 sets. +/// But not in one of them +#[derive(Debug)] +pub struct IntersectionCombination<L, R, I> { + lhs: L, + rhs: R, + _records: PhantomData<I>, +} + +impl<L, R, I> IntersectionCombination<L, R, I> { + fn new(lhs: L, rhs: R) -> Self { + Self { + lhs, + rhs, + _records: PhantomData, + } + } +} + +impl<I, L, R> Object<I> for IntersectionCombination<L, R, I> +where + L: Object<I>, + R: Object<I>, + I: Records + ExactRecords, +{ + type Iter = IntersectIter<L::Iter>; + + fn cells(&self, records: &I) -> Self::Iter { + let lhs = self.lhs.cells(records); + let rhs = self.rhs.cells(records); + + IntersectIter::new(lhs, rhs, records.count_rows(), records.count_columns()) + } +} + +/// Inversion struct used for chaining [`Object`]'s. +/// +/// Returns cells which are present in 2 sets. +/// But not in one of them +#[derive(Debug)] +pub struct InversionCombination<O, I> { + obj: O, + _records: PhantomData<I>, +} + +impl<O, I> InversionCombination<O, I> { + fn new(obj: O) -> Self { + Self { + obj, + _records: PhantomData, + } + } +} + +impl<I, O> Object<I> for InversionCombination<O, I> +where + O: Object<I>, + I: Records + ExactRecords, +{ + type Iter = InversionIter; + + fn cells(&self, records: &I) -> Self::Iter { + let obj = self.obj.cells(records); + + InversionIter::new(obj, records.count_rows(), records.count_columns()) + } +} + +/// An [`Iterator`] which goes over a combination [`Object::Iter`]. +#[derive(Debug)] +pub struct UnionIter<L, R> { + lhs: Option<L>, + rhs: R, + seen: HashSet<(usize, usize)>, + current: Option<EntityIterator>, + count_rows: usize, + count_cols: usize, +} + +impl<L, R> UnionIter<L, R> +where + L: Iterator<Item = Entity>, + R: Iterator<Item = Entity>, +{ + fn new(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self { + let size = match lhs.size_hint() { + (s1, Some(s2)) if s1 == s2 => s1, + _ => 0, + }; + + Self { + lhs: Some(lhs), + rhs, + seen: HashSet::with_capacity(size), + current: None, + count_rows, + count_cols, + } + } +} + +impl<L, R> Iterator for UnionIter<L, R> +where + L: Iterator<Item = Entity>, + R: Iterator<Item = Entity>, +{ + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + if let Some(iter) = self.current.as_mut() { + for p in iter.by_ref() { + if self.lhs.is_none() && self.seen.contains(&p) { + continue; + } + + let _ = self.seen.insert(p); + return Some(Entity::Cell(p.0, p.1)); + } + } + + if let Some(lhs) = self.lhs.as_mut() { + for entity in lhs.by_ref() { + let mut iter = entity.iter(self.count_rows, self.count_cols); + if let Some(p) = iter.by_ref().next() { + let _ = self.seen.insert(p); + self.current = Some(iter); + return Some(Entity::Cell(p.0, p.1)); + } + } + + self.lhs = None; + } + + for entity in self.rhs.by_ref() { + let mut iter = entity.iter(self.count_rows, self.count_cols); + + for p in iter.by_ref() { + if !self.seen.contains(&p) { + let _ = self.seen.insert(p); + self.current = Some(iter); + return Some(Entity::Cell(p.0, p.1)); + } + } + } + + None + } +} + +/// An [`Iterator`] which goes over only cells which are present in first [`Object::Iter`] but not second. +#[derive(Debug)] +pub struct DiffIter<L> { + lhs: L, + seen: HashSet<(usize, usize)>, + count_rows: usize, + count_cols: usize, + current: Option<EntityIterator>, +} + +impl<L> DiffIter<L> +where + L: Iterator<Item = Entity>, +{ + fn new<R>(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self + where + R: Iterator<Item = Entity>, + { + let size = match rhs.size_hint() { + (s1, Some(s2)) if s1 == s2 => s1, + _ => 0, + }; + + let mut seen = HashSet::with_capacity(size); + for entity in rhs { + seen.extend(entity.iter(count_rows, count_cols)); + } + + Self { + lhs, + seen, + count_rows, + count_cols, + current: None, + } + } +} + +impl<L> Iterator for DiffIter<L> +where + L: Iterator<Item = Entity>, +{ + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + if let Some(iter) = self.current.as_mut() { + for p in iter.by_ref() { + if !self.seen.contains(&p) { + return Some(Entity::Cell(p.0, p.1)); + } + } + } + + for entity in self.lhs.by_ref() { + let mut iter = entity.iter(self.count_rows, self.count_cols); + + for p in iter.by_ref() { + if !self.seen.contains(&p) { + self.current = Some(iter); + return Some(Entity::Cell(p.0, p.1)); + } + } + } + + None + } +} + +/// An [`Iterator`] which goes goes over cells which are present in both [`Object::Iter`]ators. +#[derive(Debug)] +pub struct IntersectIter<L> { + lhs: L, + seen: HashSet<(usize, usize)>, + count_rows: usize, + count_cols: usize, + current: Option<EntityIterator>, +} + +impl<L> IntersectIter<L> +where + L: Iterator<Item = Entity>, +{ + fn new<R>(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self + where + R: Iterator<Item = Entity>, + { + let size = match rhs.size_hint() { + (s1, Some(s2)) if s1 == s2 => s1, + _ => 0, + }; + + let mut seen = HashSet::with_capacity(size); + for entity in rhs { + seen.extend(entity.iter(count_rows, count_cols)); + } + + Self { + lhs, + seen, + count_rows, + count_cols, + current: None, + } + } +} + +impl<L> Iterator for IntersectIter<L> +where + L: Iterator<Item = Entity>, +{ + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + if let Some(iter) = self.current.as_mut() { + for p in iter.by_ref() { + if self.seen.contains(&p) { + return Some(Entity::Cell(p.0, p.1)); + } + } + } + + for entity in self.lhs.by_ref() { + let mut iter = entity.iter(self.count_rows, self.count_cols); + + for p in iter.by_ref() { + if self.seen.contains(&p) { + self.current = Some(iter); + return Some(Entity::Cell(p.0, p.1)); + } + } + } + + None + } +} + +/// An [`Iterator`] which goes goes over cells which are not present an [`Object::Iter`]ator. +#[derive(Debug)] +pub struct InversionIter { + all: SectorCellsIter, + seen: HashSet<(usize, usize)>, +} + +impl InversionIter { + fn new<O>(obj: O, count_rows: usize, count_columns: usize) -> Self + where + O: Iterator<Item = Entity>, + { + let size = match obj.size_hint() { + (s1, Some(s2)) if s1 == s2 => s1, + _ => 0, + }; + + let mut seen = HashSet::with_capacity(size); + for entity in obj { + seen.extend(entity.iter(count_rows, count_columns)); + } + + let all = SectorCellsIter::new(0, count_rows, 0, count_columns); + + Self { all, seen } + } +} + +impl Iterator for InversionIter { + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + for p in self.all.by_ref() { + if !self.seen.contains(&p) { + return Some(Entity::Cell(p.0, p.1)); + } + } + + None + } +} + +#[cfg(test)] +mod tests { + use crate::grid::records::vec_records::VecRecords; + + use super::*; + + #[test] + fn cell_test() { + assert_eq!(vec_cells((0, 0), 2, 3), [Entity::Cell(0, 0)]); + assert_eq!(vec_cells((1, 1), 2, 3), [Entity::Cell(1, 1)]); + assert_eq!(vec_cells((1, 1), 0, 0), [Entity::Cell(1, 1)]); + assert_eq!(vec_cells((1, 100), 2, 3), [Entity::Cell(1, 100)]); + assert_eq!(vec_cells((100, 1), 2, 3), [Entity::Cell(100, 1)]); + } + + #[test] + fn columns_test() { + assert_eq!( + vec_cells(Columns::new(..), 2, 3), + [Entity::Column(0), Entity::Column(1), Entity::Column(2)] + ); + assert_eq!( + vec_cells(Columns::new(1..), 2, 3), + [Entity::Column(1), Entity::Column(2)] + ); + assert_eq!(vec_cells(Columns::new(2..), 2, 3), [Entity::Column(2)]); + assert_eq!(vec_cells(Columns::new(3..), 2, 3), []); + assert_eq!(vec_cells(Columns::new(3..), 0, 0), []); + assert_eq!(vec_cells(Columns::new(0..1), 2, 3), [Entity::Column(0)]); + assert_eq!(vec_cells(Columns::new(1..2), 2, 3), [Entity::Column(1)]); + assert_eq!(vec_cells(Columns::new(2..3), 2, 3), [Entity::Column(2)]); + assert_eq!(vec_cells(Columns::new(..), 0, 0), []); + assert_eq!(vec_cells(Columns::new(..), 2, 0), []); + assert_eq!(vec_cells(Columns::new(..), 0, 3), []); + } + + #[test] + fn first_column_test() { + assert_eq!(vec_cells(Columns::first(), 5, 2), [Entity::Column(0)]); + assert_eq!(vec_cells(Columns::first(), 0, 0), []); + assert_eq!(vec_cells(Columns::first(), 10, 0), []); + assert_eq!(vec_cells(Columns::first(), 0, 10), []); + } + + #[test] + fn last_column_test() { + assert_eq!(vec_cells(Columns::last(), 5, 2), [Entity::Column(1)]); + assert_eq!(vec_cells(Columns::last(), 5, 29), [Entity::Column(28)]); + assert_eq!(vec_cells(Columns::last(), 0, 0), []); + assert_eq!(vec_cells(Columns::last(), 10, 0), []); + assert_eq!(vec_cells(Columns::last(), 0, 10), []); + } + + #[test] + fn last_column_sub_test() { + assert_eq!(vec_cells(Columns::last(), 5, 2), [Entity::Column(1)]); + assert_eq!(vec_cells(Columns::last() - 0, 5, 2), [Entity::Column(1)]); + assert_eq!(vec_cells(Columns::last() - 1, 5, 2), [Entity::Column(0)]); + assert_eq!(vec_cells(Columns::last() - 2, 5, 2), []); + assert_eq!(vec_cells(Columns::last() - 100, 5, 2), []); + } + + #[test] + fn first_column_add_test() { + assert_eq!(vec_cells(Columns::first(), 5, 2), [Entity::Column(0)]); + assert_eq!(vec_cells(Columns::first() + 0, 5, 2), [Entity::Column(0)]); + assert_eq!(vec_cells(Columns::first() + 1, 5, 2), [Entity::Column(1)]); + assert_eq!(vec_cells(Columns::first() + 2, 5, 2), [Entity::Column(2)]); + assert_eq!( + vec_cells(Columns::first() + 100, 5, 2), + [Entity::Column(100)] + ); + } + + #[test] + fn rows_test() { + assert_eq!( + vec_cells(Rows::new(..), 2, 3), + [Entity::Row(0), Entity::Row(1)] + ); + assert_eq!(vec_cells(Rows::new(1..), 2, 3), [Entity::Row(1)]); + assert_eq!(vec_cells(Rows::new(2..), 2, 3), []); + assert_eq!(vec_cells(Rows::new(2..), 0, 0), []); + assert_eq!(vec_cells(Rows::new(0..1), 2, 3), [Entity::Row(0)],); + assert_eq!(vec_cells(Rows::new(1..2), 2, 3), [Entity::Row(1)],); + assert_eq!(vec_cells(Rows::new(..), 0, 0), []); + assert_eq!(vec_cells(Rows::new(..), 0, 3), []); + assert_eq!( + vec_cells(Rows::new(..), 2, 0), + [Entity::Row(0), Entity::Row(1)] + ); + } + + #[test] + fn last_row_test() { + assert_eq!(vec_cells(Rows::last(), 5, 2), [Entity::Row(4)]); + assert_eq!(vec_cells(Rows::last(), 100, 2), [Entity::Row(99)]); + assert_eq!(vec_cells(Rows::last(), 0, 0), []); + assert_eq!(vec_cells(Rows::last(), 5, 0), []); + assert_eq!(vec_cells(Rows::last(), 0, 2), []); + } + + #[test] + fn first_row_test() { + assert_eq!(vec_cells(Rows::first(), 5, 2), [Entity::Row(0)]); + assert_eq!(vec_cells(Rows::first(), 100, 2), [Entity::Row(0)]); + assert_eq!(vec_cells(Rows::first(), 0, 0), []); + assert_eq!(vec_cells(Rows::first(), 5, 0), []); + assert_eq!(vec_cells(Rows::first(), 0, 2), []); + } + + #[test] + fn last_row_sub_test() { + assert_eq!(vec_cells(Rows::last(), 5, 2), [Entity::Row(4)]); + assert_eq!(vec_cells(Rows::last() - 0, 5, 2), [Entity::Row(4)]); + assert_eq!(vec_cells(Rows::last() - 1, 5, 2), [Entity::Row(3)]); + assert_eq!(vec_cells(Rows::last() - 2, 5, 2), [Entity::Row(2)]); + assert_eq!(vec_cells(Rows::last() - 3, 5, 2), [Entity::Row(1)]); + assert_eq!(vec_cells(Rows::last() - 4, 5, 2), [Entity::Row(0)]); + assert_eq!(vec_cells(Rows::last() - 5, 5, 2), []); + assert_eq!(vec_cells(Rows::last() - 100, 5, 2), []); + assert_eq!(vec_cells(Rows::last() - 1, 0, 0), []); + assert_eq!(vec_cells(Rows::last() - 1, 5, 0), []); + assert_eq!(vec_cells(Rows::last() - 1, 0, 2), []); + } + + #[test] + fn first_row_add_test() { + assert_eq!(vec_cells(Rows::first(), 5, 2), [Entity::Row(0)]); + assert_eq!(vec_cells(Rows::first() + 0, 5, 2), [Entity::Row(0)]); + assert_eq!(vec_cells(Rows::first() + 1, 5, 2), [Entity::Row(1)]); + assert_eq!(vec_cells(Rows::first() + 2, 5, 2), [Entity::Row(2)]); + assert_eq!(vec_cells(Rows::first() + 3, 5, 2), [Entity::Row(3)]); + assert_eq!(vec_cells(Rows::first() + 4, 5, 2), [Entity::Row(4)]); + assert_eq!(vec_cells(Rows::first() + 5, 5, 2), [Entity::Row(5)]); + assert_eq!(vec_cells(Rows::first() + 100, 5, 2), [Entity::Row(100)]); + assert_eq!(vec_cells(Rows::first() + 1, 0, 0), [Entity::Row(1)]); + assert_eq!(vec_cells(Rows::first() + 1, 5, 0), [Entity::Row(1)]); + assert_eq!(vec_cells(Rows::first() + 1, 0, 2), [Entity::Row(1)]); + } + + #[test] + fn frame_test() { + assert_eq!( + vec_cells(Frame, 2, 3), + [ + Entity::Cell(0, 0), + Entity::Cell(0, 1), + Entity::Cell(0, 2), + Entity::Cell(1, 0), + Entity::Cell(1, 1), + Entity::Cell(1, 2) + ] + ); + assert_eq!(vec_cells(Frame, 0, 0), []); + assert_eq!(vec_cells(Frame, 2, 0), []); + assert_eq!(vec_cells(Frame, 0, 2), []); + } + + #[test] + fn segment_test() { + assert_eq!( + vec_cells(Segment::new(.., ..), 2, 3), + [ + Entity::Cell(0, 0), + Entity::Cell(0, 1), + Entity::Cell(0, 2), + Entity::Cell(1, 0), + Entity::Cell(1, 1), + Entity::Cell(1, 2) + ] + ); + assert_eq!( + vec_cells(Segment::new(1.., ..), 2, 3), + [Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2)] + ); + assert_eq!(vec_cells(Segment::new(2.., ..), 2, 3), []); + + assert_eq!( + vec_cells(Segment::new(.., 1..), 2, 3), + [ + Entity::Cell(0, 1), + Entity::Cell(0, 2), + Entity::Cell(1, 1), + Entity::Cell(1, 2) + ] + ); + assert_eq!( + vec_cells(Segment::new(.., 2..), 2, 3), + [Entity::Cell(0, 2), Entity::Cell(1, 2)] + ); + assert_eq!(vec_cells(Segment::new(.., 3..), 2, 3), []); + + assert_eq!( + vec_cells(Segment::new(1.., 1..), 2, 3), + [Entity::Cell(1, 1), Entity::Cell(1, 2)] + ); + assert_eq!( + vec_cells(Segment::new(1..2, 1..2), 2, 3), + [Entity::Cell(1, 1)] + ); + + assert_eq!(vec_cells(Segment::new(5.., 5..), 2, 3), []); + } + + #[test] + fn object_and_test() { + assert_eq!( + vec_cells(Cell::new(0, 0).and(Cell::new(0, 0)), 2, 3), + [Entity::Cell(0, 0)] + ); + assert_eq!( + vec_cells(Cell::new(0, 0).and(Cell::new(1, 2)), 2, 3), + [Entity::Cell(0, 0), Entity::Cell(1, 2)] + ); + assert_eq!(vec_cells(Cell::new(0, 0).and(Cell::new(1, 2)), 0, 0), []); + } + + #[test] + fn object_not_test() { + assert_eq!(vec_cells(Rows::first().not(Cell::new(0, 0)), 0, 0), []); + assert_eq!(vec_cells(Cell::new(0, 0).not(Cell::new(0, 0)), 2, 3), []); + assert_eq!( + vec_cells(Rows::first().not(Cell::new(0, 0)), 2, 3), + [Entity::Cell(0, 1), Entity::Cell(0, 2)] + ); + assert_eq!( + vec_cells(Columns::single(1).not(Rows::single(1)), 3, 3), + [Entity::Cell(0, 1), Entity::Cell(2, 1)] + ); + assert_eq!( + vec_cells(Rows::single(1).not(Columns::single(1)), 3, 3), + [Entity::Cell(1, 0), Entity::Cell(1, 2)] + ); + } + + #[test] + fn object_intersect_test() { + assert_eq!( + vec_cells(Rows::first().intersect(Cell::new(0, 0)), 0, 0), + [] + ); + assert_eq!( + vec_cells(Segment::all().intersect(Rows::single(1)), 2, 3), + [Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2)] + ); + assert_eq!( + vec_cells(Cell::new(0, 0).intersect(Cell::new(0, 0)), 2, 3), + [Entity::Cell(0, 0)] + ); + assert_eq!( + vec_cells(Rows::first().intersect(Cell::new(0, 0)), 2, 3), + [Entity::Cell(0, 0)] + ); + // maybe we somehow shall not limit the rows/columns by the max count? + assert_eq!( + vec_cells(Rows::single(1).intersect(Columns::single(1)), 2, 1), + [] + ); + } + + #[test] + fn object_inverse_test() { + assert_eq!(vec_cells(Segment::all().inverse(), 2, 3), []); + assert_eq!( + vec_cells(Cell::new(0, 0).inverse(), 2, 3), + [ + Entity::Cell(0, 1), + Entity::Cell(0, 2), + Entity::Cell(1, 0), + Entity::Cell(1, 1), + Entity::Cell(1, 2) + ] + ); + assert_eq!( + vec_cells(Rows::first().inverse(), 2, 3), + [Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2)] + ); + assert_eq!(vec_cells(Rows::first().inverse(), 0, 0), []); + } + + fn vec_cells<O: Object<VecRecords<String>>>( + o: O, + count_rows: usize, + count_cols: usize, + ) -> Vec<Entity> { + let data = vec![vec![String::default(); count_cols]; count_rows]; + let records = VecRecords::new(data); + o.cells(&records).collect::<Vec<_>>() + } +} diff --git a/vendor/tabled/src/settings/object/rows.rs b/vendor/tabled/src/settings/object/rows.rs new file mode 100644 index 000000000..b32e79ad5 --- /dev/null +++ b/vendor/tabled/src/settings/object/rows.rs @@ -0,0 +1,213 @@ +use std::ops::{Add, RangeBounds, Sub}; + +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, Records}, + settings::object::{cell::EntityOnce, Object}, +}; + +use super::util::bounds_to_usize; + +/// Row denotes a set of cells on given rows on a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Rows<R> { + range: R, +} + +impl<R> Rows<R> { + /// Returns a new instance of [`Rows`] for a range of rows. + /// + /// If the boundaries are exceeded it may panic. + pub fn new(range: R) -> Self + where + R: RangeBounds<usize>, + { + Self { range } + } + + pub(crate) const fn get_range(&self) -> &R { + &self.range + } +} + +impl Rows<()> { + /// Returns a new instance of [`Rows`] with a single row. + /// + /// If the boundaries are exceeded it may panic. + pub const fn single(index: usize) -> Row { + Row { index } + } + + /// Returns a first row [`Object`]. + /// + /// If the table has 0 rows returns an empty set of cells. + pub const fn first() -> FirstRow { + FirstRow + } + + /// Returns a last row [`Object`]. + /// + /// If the table has 0 rows returns an empty set of cells. + pub const fn last() -> LastRow { + LastRow + } +} + +impl<I, R> Object<I> for Rows<R> +where + R: RangeBounds<usize>, + I: ExactRecords, +{ + type Iter = RowsIter; + + fn cells(&self, records: &I) -> Self::Iter { + let start = self.range.start_bound(); + let end = self.range.end_bound(); + let max = records.count_rows(); + let (x, y) = bounds_to_usize(start, end, max); + + RowsIter::new(x, y) + } +} + +/// A row which is located by an offset from the first row. +#[derive(Debug, Clone, Copy)] +pub struct Row { + index: usize, +} + +impl<I> Object<I> for Row { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Row(self.index))) + } +} + +impl From<Row> for usize { + fn from(val: Row) -> Self { + val.index + } +} + +/// This structure represents the first row of a [`Table`]. +/// It's often contains headers data. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct FirstRow; + +impl<I> Object<I> for FirstRow +where + I: Records + ExactRecords, +{ + type Iter = EntityOnce; + + fn cells(&self, records: &I) -> Self::Iter { + if records.count_columns() == 0 || records.count_rows() == 0 { + return EntityOnce::new(None); + } + + EntityOnce::new(Some(Entity::Row(0))) + } +} + +impl Add<usize> for FirstRow { + type Output = Row; + + fn add(self, rhs: usize) -> Self::Output { + Row { index: rhs } + } +} + +/// This structure represents the last row of a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct LastRow; + +impl<I> Object<I> for LastRow +where + I: Records + ExactRecords, +{ + type Iter = EntityOnce; + + fn cells(&self, records: &I) -> Self::Iter { + let count_rows = records.count_rows(); + if records.count_columns() == 0 || count_rows == 0 { + return EntityOnce::new(None); + } + + let row = if count_rows == 0 { 0 } else { count_rows - 1 }; + + EntityOnce::new(Some(Entity::Row(row))) + } +} + +impl Sub<usize> for LastRow { + type Output = LastRowOffset; + + fn sub(self, rhs: usize) -> Self::Output { + LastRowOffset { offset: rhs } + } +} + +/// A row which is located by an offset from the last row. +#[derive(Debug)] +pub struct LastRowOffset { + offset: usize, +} + +impl<I> Object<I> for LastRowOffset +where + I: Records + ExactRecords, +{ + type Iter = EntityOnce; + + fn cells(&self, records: &I) -> Self::Iter { + let count_rows = records.count_rows(); + if records.count_columns() == 0 || count_rows == 0 { + return EntityOnce::new(None); + } + + let row = if count_rows == 0 { 0 } else { count_rows - 1 }; + if self.offset > row { + return EntityOnce::new(None); + } + + let row = row - self.offset; + EntityOnce::new(Some(Entity::Row(row))) + } +} + +/// An [`Iterator`] which goes goes over all rows of a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct RowsIter { + start: usize, + end: usize, +} + +impl RowsIter { + const fn new(start: usize, end: usize) -> Self { + Self { start, end } + } +} + +impl Iterator for RowsIter { + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + if self.start >= self.end { + return None; + } + + let col = self.start; + self.start += 1; + + Some(Entity::Row(col)) + } +} diff --git a/vendor/tabled/src/settings/object/segment.rs b/vendor/tabled/src/settings/object/segment.rs new file mode 100644 index 000000000..9b59ada47 --- /dev/null +++ b/vendor/tabled/src/settings/object/segment.rs @@ -0,0 +1,151 @@ +use std::ops::{RangeBounds, RangeFull}; + +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, Records}, + settings::object::{cell::EntityOnce, Object}, +}; + +use super::util::bounds_to_usize; + +/// This structure represents a sub table of [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Segment<C, R> { + columns: C, + rows: R, +} + +impl Segment<RangeFull, RangeFull> { + /// Returns a table segment on which are present all cells. + pub fn all() -> SegmentAll { + SegmentAll + } +} + +impl<C, R> Segment<C, R> +where + C: RangeBounds<usize>, + R: RangeBounds<usize>, +{ + /// This function builds a [`Segment`]. + pub fn new(rows: R, columns: C) -> Self { + Self { columns, rows } + } +} + +impl<I, C, R> Object<I> for Segment<C, R> +where + C: RangeBounds<usize>, + R: RangeBounds<usize>, + I: Records + ExactRecords, +{ + type Iter = SectorIter; + + fn cells(&self, records: &I) -> Self::Iter { + let start = self.rows.start_bound(); + let end = self.rows.end_bound(); + let max = records.count_rows(); + let (rows_start, rows_end) = bounds_to_usize(start, end, max); + + let start = self.columns.start_bound(); + let end = self.columns.end_bound(); + let max = records.count_columns(); + let (cols_start, cols_end) = bounds_to_usize(start, end, max); + + SectorIter::new(rows_start, rows_end, cols_start, cols_end) + } +} + +/// This is a segment which contains all cells on the table. +/// +/// Can be created from [`Segment::all`]. +#[derive(Debug)] +pub struct SegmentAll; + +impl<I> Object<I> for SegmentAll { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Global)) + } +} + +/// An [`Iterator`] which goes goes over all cell in a sector in a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct SectorIter { + iter: SectorCellsIter, +} + +impl SectorIter { + const fn new(rows_start: usize, rows_end: usize, cols_start: usize, cols_end: usize) -> Self { + Self { + iter: SectorCellsIter::new(rows_start, rows_end, cols_start, cols_end), + } + } +} + +impl Iterator for SectorIter { + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + let (row, col) = self.iter.next()?; + Some(Entity::Cell(row, col)) + } +} + +#[derive(Debug)] +pub(crate) struct SectorCellsIter { + rows_end: usize, + cols_start: usize, + cols_end: usize, + row: usize, + col: usize, +} + +impl SectorCellsIter { + /// Create an iterator from 1st row to last from 1st col to last. + pub(crate) const fn new( + rows_start: usize, + rows_end: usize, + cols_start: usize, + cols_end: usize, + ) -> Self { + Self { + rows_end, + cols_start, + cols_end, + row: rows_start, + col: cols_start, + } + } +} + +impl Iterator for SectorCellsIter { + type Item = (usize, usize); + + fn next(&mut self) -> Option<Self::Item> { + if self.row >= self.rows_end { + return None; + } + + if self.col >= self.cols_end { + return None; + } + + let row = self.row; + let col = self.col; + + self.col += 1; + + if self.col == self.cols_end { + self.row += 1; + self.col = self.cols_start; + } + + Some((row, col)) + } +} diff --git a/vendor/tabled/src/settings/object/util.rs b/vendor/tabled/src/settings/object/util.rs new file mode 100644 index 000000000..94ca20e86 --- /dev/null +++ b/vendor/tabled/src/settings/object/util.rs @@ -0,0 +1,22 @@ +use std::ops::Bound; + +/// Converts a range bound to its indexes. +pub(super) fn bounds_to_usize( + left: Bound<&usize>, + right: Bound<&usize>, + count_elements: usize, +) -> (usize, usize) { + match (left, right) { + (Bound::Included(x), Bound::Included(y)) => (*x, y + 1), + (Bound::Included(x), Bound::Excluded(y)) => (*x, *y), + (Bound::Included(x), Bound::Unbounded) => (*x, count_elements), + (Bound::Unbounded, Bound::Unbounded) => (0, count_elements), + (Bound::Unbounded, Bound::Included(y)) => (0, y + 1), + (Bound::Unbounded, Bound::Excluded(y)) => (0, *y), + (Bound::Excluded(_), Bound::Unbounded) + | (Bound::Excluded(_), Bound::Included(_)) + | (Bound::Excluded(_), Bound::Excluded(_)) => { + unreachable!("A start bound can't be excluded") + } + } +} diff --git a/vendor/tabled/src/settings/padding/mod.rs b/vendor/tabled/src/settings/padding/mod.rs new file mode 100644 index 000000000..af343b3cb --- /dev/null +++ b/vendor/tabled/src/settings/padding/mod.rs @@ -0,0 +1,164 @@ +//! This module contains a [`Padding`] setting of a cell on a [`Table`]. +//! +//! # Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::{Padding, Style, Modify, object::Cell}}; +//! +//! let table = Table::new("2022".chars()) +//! .with(Style::modern()) +//! .with(Modify::new((2, 0)).with(Padding::new(1, 1, 2, 2))) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "┌──────┐\n", +//! "│ char │\n", +//! "├──────┤\n", +//! "│ 2 │\n", +//! "├──────┤\n", +//! "│ │\n", +//! "│ │\n", +//! "│ 0 │\n", +//! "│ │\n", +//! "│ │\n", +//! "├──────┤\n", +//! "│ 2 │\n", +//! "├──────┤\n", +//! "│ 2 │\n", +//! "└──────┘", +//! ), +//! ); +//! ``` +//! +//! [`Table`]: crate::Table + +use crate::{ + grid::{ + color::StaticColor, + config::{CompactConfig, CompactMultilineConfig}, + config::{Indent, Sides}, + }, + settings::TableOption, +}; + +#[cfg(feature = "std")] +use crate::grid::{color::AnsiColor, config::ColoredConfig, config::Entity}; +#[cfg(feature = "std")] +use crate::settings::CellOption; + +/// Padding is responsible for a left/right/top/bottom inner indent of a particular cell. +/// +#[cfg_attr(feature = "std", doc = "```")] +#[cfg_attr(not(feature = "std"), doc = "```ignore")] +/// # use tabled::{settings::{Style, Padding, object::Rows, Modify}, Table}; +/// # let data: Vec<&'static str> = Vec::new(); +/// let table = Table::new(&data).with(Modify::new(Rows::single(0)).with(Padding::new(0, 0, 1, 1).fill('>', '<', '^', 'V'))); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Padding<C = StaticColor> { + indent: Sides<Indent>, + colors: Option<Sides<C>>, +} + +impl Padding { + /// Construct's an Padding object. + /// + /// It uses space(' ') as a default fill character. + /// To set a custom character you can use [`Padding::fill`] function. + pub const fn new(left: usize, right: usize, top: usize, bottom: usize) -> Self { + Self { + indent: Sides::new( + Indent::spaced(left), + Indent::spaced(right), + Indent::spaced(top), + Indent::spaced(bottom), + ), + colors: None, + } + } + + /// Construct's an Padding object with all sides set to 0. + /// + /// It uses space(' ') as a default fill character. + /// To set a custom character you can use [`Padding::fill`] function. + pub const fn zero() -> Self { + Self::new(0, 0, 0, 0) + } +} + +impl<Color> Padding<Color> { + /// The function, sets a characters for the padding on an each side. + pub const fn fill(mut self, left: char, right: char, top: char, bottom: char) -> Self { + self.indent.left.fill = left; + self.indent.right.fill = right; + self.indent.top.fill = top; + self.indent.bottom.fill = bottom; + self + } + + /// The function, sets a characters for the padding on an each side. + pub fn colorize<C>(self, left: C, right: C, top: C, bottom: C) -> Padding<C> { + Padding { + indent: self.indent, + colors: Some(Sides::new(left, right, top, bottom)), + } + } +} + +#[cfg(feature = "std")] +impl<R, C> CellOption<R, ColoredConfig> for Padding<C> +where + C: Into<AnsiColor<'static>> + Clone, +{ + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let indent = self.indent; + let pad = Sides::new(indent.left, indent.right, indent.top, indent.bottom); + cfg.set_padding(entity, pad); + + if let Some(colors) = &self.colors { + let pad = Sides::new( + Some(colors.left.clone().into()), + Some(colors.right.clone().into()), + Some(colors.top.clone().into()), + Some(colors.bottom.clone().into()), + ); + cfg.set_padding_color(entity, pad); + } + } +} + +#[cfg(feature = "std")] +impl<R, D, C> TableOption<R, D, ColoredConfig> for Padding<C> +where + C: Into<AnsiColor<'static>> + Clone, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + <Self as CellOption<R, ColoredConfig>>::change(self, records, cfg, Entity::Global) + } +} + +impl<R, D, C> TableOption<R, D, CompactConfig> for Padding<C> +where + C: Into<StaticColor> + Clone, +{ + fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { + *cfg = cfg.set_padding(self.indent); + + if let Some(c) = self.colors { + let colors = Sides::new(c.left.into(), c.right.into(), c.top.into(), c.bottom.into()); + *cfg = cfg.set_padding_color(colors); + } + } +} + +impl<R, D, C> TableOption<R, D, CompactMultilineConfig> for Padding<C> +where + C: Into<StaticColor> + Clone, +{ + fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) { + self.change(records, cfg.as_mut(), dimension) + } +} diff --git a/vendor/tabled/src/settings/panel/footer.rs b/vendor/tabled/src/settings/panel/footer.rs new file mode 100644 index 000000000..8d16481cf --- /dev/null +++ b/vendor/tabled/src/settings/panel/footer.rs @@ -0,0 +1,32 @@ +use crate::{ + grid::config::ColoredConfig, + grid::records::{ExactRecords, Records, RecordsMut, Resizable}, + settings::TableOption, +}; + +use super::Panel; + +/// Footer renders a [`Panel`] at the bottom. +/// See [`Panel`]. +#[derive(Debug)] +pub struct Footer<S>(S); + +impl<S> Footer<S> { + /// Creates a new object. + pub fn new(text: S) -> Self + where + S: AsRef<str>, + { + Self(text) + } +} + +impl<S, R, D> TableOption<R, D, ColoredConfig> for Footer<S> +where + S: AsRef<str>, + R: Records + ExactRecords + Resizable + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) { + Panel::horizontal(records.count_rows(), self.0.as_ref()).change(records, cfg, dimension); + } +} diff --git a/vendor/tabled/src/settings/panel/header.rs b/vendor/tabled/src/settings/panel/header.rs new file mode 100644 index 000000000..e9d398cb5 --- /dev/null +++ b/vendor/tabled/src/settings/panel/header.rs @@ -0,0 +1,32 @@ +use crate::{ + grid::config::ColoredConfig, + grid::records::{ExactRecords, Records, RecordsMut, Resizable}, + settings::TableOption, +}; + +use super::Panel; + +/// Header inserts a [`Panel`] at the top. +/// See [`Panel`]. +#[derive(Debug)] +pub struct Header<S>(S); + +impl<S> Header<S> { + /// Creates a new object. + pub fn new(text: S) -> Self + where + S: AsRef<str>, + { + Self(text) + } +} + +impl<S, R, D> TableOption<R, D, ColoredConfig> for Header<S> +where + S: AsRef<str>, + R: Records + ExactRecords + Resizable + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) { + Panel::horizontal(0, self.0.as_ref()).change(records, cfg, dimension); + } +} diff --git a/vendor/tabled/src/settings/panel/horizontal_panel.rs b/vendor/tabled/src/settings/panel/horizontal_panel.rs new file mode 100644 index 000000000..d5871d720 --- /dev/null +++ b/vendor/tabled/src/settings/panel/horizontal_panel.rs @@ -0,0 +1,80 @@ +use crate::{ + grid::config::{ColoredConfig, SpannedConfig}, + grid::records::{ExactRecords, Records, RecordsMut, Resizable}, + settings::TableOption, +}; + +/// A horizontal/column span from 0 to a count rows. +#[derive(Debug)] +pub struct HorizontalPanel<S> { + text: S, + row: usize, +} + +impl<S> HorizontalPanel<S> { + /// Creates a new horizontal panel. + pub fn new(row: usize, text: S) -> Self { + Self { row, text } + } +} + +impl<S, R, D> TableOption<R, D, ColoredConfig> for HorizontalPanel<S> +where + S: AsRef<str>, + R: Records + ExactRecords + Resizable + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + if self.row > count_rows { + return; + } + + let is_intersect_vertical_span = (0..records.count_columns()) + .any(|col| cfg.is_cell_covered_by_row_span((self.row, col))); + if is_intersect_vertical_span { + return; + } + + move_rows_aside(records, self.row); + move_row_spans(cfg, self.row); + + let text = self.text.as_ref().to_owned(); + records.set((self.row, 0), text); + + cfg.set_column_span((self.row, 0), count_cols); + } +} + +fn move_rows_aside<R: ExactRecords + Resizable>(records: &mut R, row: usize) { + records.push_row(); + + let count_rows = records.count_rows(); + + let shift_count = count_rows - row; + for i in 1..shift_count { + let row = count_rows - i; + records.swap_row(row, row - 1); + } +} + +fn move_row_spans(cfg: &mut SpannedConfig, target_row: usize) { + for ((row, col), span) in cfg.get_column_spans() { + if row < target_row { + continue; + } + + cfg.set_column_span((row, col), 1); + cfg.set_column_span((row + 1, col), span); + } + + for ((row, col), span) in cfg.get_row_spans() { + if row < target_row { + continue; + } + + cfg.set_row_span((row, col), 1); + cfg.set_row_span((row + 1, col), span); + } +} diff --git a/vendor/tabled/src/settings/panel/mod.rs b/vendor/tabled/src/settings/panel/mod.rs new file mode 100644 index 000000000..e4e819b6c --- /dev/null +++ b/vendor/tabled/src/settings/panel/mod.rs @@ -0,0 +1,127 @@ +//! This module contains primitives to create a spread row. +//! Ultimately it is a cell with a span set to a number of columns on the [`Table`]. +//! +//! You can use a [`Span`] to set a custom span. +//! +//! # Example +//! +//! ``` +//! use tabled::{Table, settings::Panel}; +//! +//! let data = [[1, 2, 3], [4, 5, 6]]; +//! +//! let table = Table::new(data) +//! .with(Panel::vertical(1, "S\np\nl\ni\nt")) +//! .with(Panel::header("Numbers")) +//! .to_string(); +//! +//! println!("{}", table); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "+---+---+---+---+\n", +//! "| Numbers |\n", +//! "+---+---+---+---+\n", +//! "| 0 | S | 1 | 2 |\n", +//! "+---+ p +---+---+\n", +//! "| 1 | l | 2 | 3 |\n", +//! "+---+ i +---+---+\n", +//! "| 4 | t | 5 | 6 |\n", +//! "+---+---+---+---+", +//! ) +//! ) +//! ``` +//! +//! [`Table`]: crate::Table +//! [`Span`]: crate::settings::span::Span + +mod footer; +mod header; +mod horizontal_panel; +mod vertical_panel; + +pub use footer::Footer; +pub use header::Header; +pub use horizontal_panel::HorizontalPanel; +pub use vertical_panel::VerticalPanel; + +/// Panel allows to add a Row which has 1 continues Cell to a [`Table`]. +/// +/// See `examples/panel.rs`. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Panel; + +impl Panel { + /// Creates an empty vertical row at given index. + /// + /// ``` + /// use tabled::{settings::Panel, Table}; + /// + /// let data = [[1, 2, 3], [4, 5, 6]]; + /// + /// let table = Table::new(data) + /// .with(Panel::vertical(1, "Tabled Releases")) + /// .to_string(); + /// + /// println!("{}", table); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "+---+-----------------+---+---+\n", + /// "| 0 | Tabled Releases | 1 | 2 |\n", + /// "+---+ +---+---+\n", + /// "| 1 | | 2 | 3 |\n", + /// "+---+ +---+---+\n", + /// "| 4 | | 5 | 6 |\n", + /// "+---+-----------------+---+---+", + /// ) + /// ) + /// ``` + pub fn vertical<S: AsRef<str>>(column: usize, text: S) -> VerticalPanel<S> { + VerticalPanel::new(column, text) + } + + /// Creates an empty horizontal row at given index. + /// + /// ``` + /// use tabled::{Table, settings::Panel}; + /// + /// let data = [[1, 2, 3], [4, 5, 6]]; + /// + /// let table = Table::new(data) + /// .with(Panel::vertical(1, "")) + /// .to_string(); + /// + /// println!("{}", table); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "+---+--+---+---+\n", + /// "| 0 | | 1 | 2 |\n", + /// "+---+ +---+---+\n", + /// "| 1 | | 2 | 3 |\n", + /// "+---+ +---+---+\n", + /// "| 4 | | 5 | 6 |\n", + /// "+---+--+---+---+", + /// ) + /// ) + /// ``` + pub fn horizontal<S: AsRef<str>>(row: usize, text: S) -> HorizontalPanel<S> { + HorizontalPanel::new(row, text) + } + + /// Creates an horizontal row at first row. + pub fn header<S: AsRef<str>>(text: S) -> Header<S> { + Header::new(text) + } + + /// Creates an horizontal row at last row. + pub fn footer<S: AsRef<str>>(text: S) -> Footer<S> { + Footer::new(text) + } +} diff --git a/vendor/tabled/src/settings/panel/vertical_panel.rs b/vendor/tabled/src/settings/panel/vertical_panel.rs new file mode 100644 index 000000000..ddc0a562b --- /dev/null +++ b/vendor/tabled/src/settings/panel/vertical_panel.rs @@ -0,0 +1,83 @@ +use crate::{ + grid::config::{ColoredConfig, SpannedConfig}, + grid::records::{ExactRecords, Records, RecordsMut, Resizable}, + settings::TableOption, +}; + +/// A vertical/row span from 0 to a count columns. +#[derive(Debug)] +pub struct VerticalPanel<S> { + text: S, + col: usize, +} + +impl<S> VerticalPanel<S> { + /// Creates a new vertical panel. + pub fn new(col: usize, text: S) -> Self + where + S: AsRef<str>, + { + Self { text, col } + } +} + +impl<S, R, D> TableOption<R, D, ColoredConfig> for VerticalPanel<S> +where + S: AsRef<str>, + R: Records + ExactRecords + Resizable + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + if self.col > count_cols { + return; + } + + let is_intersect_horizontal_span = (0..=records.count_rows()) + .any(|row| cfg.is_cell_covered_by_column_span((row, self.col))); + + if is_intersect_horizontal_span { + return; + } + + move_columns_aside(records, self.col); + move_column_spans(cfg, self.col); + + let text = self.text.as_ref().to_owned(); + records.set((0, self.col), text); + + cfg.set_row_span((0, self.col), count_rows); + } +} + +fn move_columns_aside<R: Records + Resizable>(records: &mut R, column: usize) { + records.push_column(); + + let count_columns = records.count_columns(); + let shift_count = count_columns - column; + for i in 1..shift_count { + let col = count_columns - i; + records.swap_column(col, col - 1); + } +} + +fn move_column_spans(cfg: &mut SpannedConfig, target_column: usize) { + for ((row, col), span) in cfg.get_column_spans() { + if col < target_column { + continue; + } + + cfg.set_column_span((row, col), 1); + cfg.set_column_span((row, col + 1), span); + } + + for ((row, col), span) in cfg.get_row_spans() { + if col < target_column { + continue; + } + + cfg.set_row_span((row, col), 1); + cfg.set_row_span((row, col + 1), span); + } +} diff --git a/vendor/tabled/src/settings/peaker/mod.rs b/vendor/tabled/src/settings/peaker/mod.rs new file mode 100644 index 000000000..a49796b02 --- /dev/null +++ b/vendor/tabled/src/settings/peaker/mod.rs @@ -0,0 +1,94 @@ +//! The module contains [`Peaker`] trait and its implementations to be used in [`Height`] and [`Width`]. +//! +//! [`Width`]: crate::settings::width::Width +//! [`Height`]: crate::settings::height::Height + +/// A strategy of width function. +/// It determines the order how the function is applied. +pub trait Peaker { + /// Creates a new instance. + fn create() -> Self; + /// This function returns a column index which will be changed. + /// Or `None` if no changes are necessary. + fn peak(&mut self, min_widths: &[usize], widths: &[usize]) -> Option<usize>; +} + +/// A Peaker which goes over column 1 by 1. +#[derive(Debug, Default, Clone)] +pub struct PriorityNone { + i: usize, +} + +impl Peaker for PriorityNone { + fn create() -> Self { + Self { i: 0 } + } + + fn peak(&mut self, _: &[usize], widths: &[usize]) -> Option<usize> { + let mut i = self.i; + let mut count_empty = 0; + while widths[i] == 0 { + i += 1; + if i >= widths.len() { + i = 0; + } + + count_empty += 1; + if count_empty == widths.len() { + return None; + } + } + + let col = i; + + i += 1; + if i >= widths.len() { + i = 0; + } + + self.i = i; + + Some(col) + } +} + +/// A Peaker which goes over the biggest column first. +#[derive(Debug, Default, Clone)] +pub struct PriorityMax; + +impl Peaker for PriorityMax { + fn create() -> Self { + Self + } + + fn peak(&mut self, _: &[usize], widths: &[usize]) -> Option<usize> { + let col = (0..widths.len()).max_by_key(|&i| widths[i]).unwrap(); + if widths[col] == 0 { + None + } else { + Some(col) + } + } +} + +/// A Peaker which goes over the smallest column first. +#[derive(Debug, Default, Clone)] +pub struct PriorityMin; + +impl Peaker for PriorityMin { + fn create() -> Self { + Self + } + + fn peak(&mut self, min_widths: &[usize], widths: &[usize]) -> Option<usize> { + let col = (0..widths.len()) + .filter(|&i| min_widths.is_empty() || widths[i] > min_widths[i]) + .min_by_key(|&i| widths[i]) + .unwrap(); + if widths[col] == 0 { + None + } else { + Some(col) + } + } +} diff --git a/vendor/tabled/src/settings/rotate/mod.rs b/vendor/tabled/src/settings/rotate/mod.rs new file mode 100644 index 000000000..426417190 --- /dev/null +++ b/vendor/tabled/src/settings/rotate/mod.rs @@ -0,0 +1,155 @@ +//! This module contains a [`Rotate`] primitive which can be used in order to rotate [`Table`]. +//! +//! It's also possible to transpose the table at the point of construction. +//! See [`Builder::index`]. +//! +//! # Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::Rotate}; +//! +//! let data = [[1, 2, 3], [4, 5, 6]]; +//! +//! let table = Table::new(data).with(Rotate::Left).to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "+---+---+---+\n", +//! "| 2 | 3 | 6 |\n", +//! "+---+---+---+\n", +//! "| 1 | 2 | 5 |\n", +//! "+---+---+---+\n", +//! "| 0 | 1 | 4 |\n", +//! "+---+---+---+", +//! ) +//! ); +//! ``` +//! +//! [`Table`]: crate::Table +//! [`Builder::index`]: crate::builder::Builder::index + +// use core::cmp::max; +use core::cmp::max; + +use crate::{ + grid::records::{ExactRecords, Records, Resizable}, + settings::TableOption, +}; + +/// Rotate can be used to rotate a table by 90 degrees. +#[derive(Debug)] +pub enum Rotate { + /// Rotate [`Table`] to the left. + /// + /// [`Table`]: crate::Table + Left, + /// Rotate [`Table`] to the right. + /// + /// [`Table`]: crate::Table + Right, + /// Rotate [`Table`] to the top. + /// + /// So the top becomes the bottom. + /// + /// [`Table`]: crate::Table + Top, + /// Rotate [`Table`] to the bottom. + /// + /// So the top becomes the bottom. + /// + /// [`Table`]: crate::Table + Bottom, +} + +impl<R, D, C> TableOption<R, D, C> for Rotate +where + R: Records + ExactRecords + Resizable, +{ + fn change(self, records: &mut R, _: &mut C, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + match self { + Self::Left => { + let size = max(count_rows, count_cols); + + { + for _ in count_rows..size { + records.push_row(); + } + + for _ in count_cols..size { + records.push_column(); + } + } + + for col in 0..size { + for row in col..size { + records.swap((col, row), (row, col)); + } + } + + for row in 0..count_cols / 2 { + records.swap_row(row, count_cols - row - 1); + } + + { + for (shift, row) in (count_rows..size).enumerate() { + let row = row - shift; + records.remove_column(row); + } + + for (shift, col) in (count_cols..size).enumerate() { + let col = col - shift; + records.remove_row(col); + } + } + } + Self::Right => { + let size = max(count_rows, count_cols); + + { + for _ in count_rows..size { + records.push_row(); + } + + for _ in count_cols..size { + records.push_column(); + } + } + + for col in 0..size { + for row in col..size { + records.swap((col, row), (row, col)); + } + } + + for col in 0..count_rows / 2 { + records.swap_column(col, count_rows - col - 1); + } + + { + for (shift, row) in (count_rows..size).enumerate() { + let row = row - shift; + records.remove_column(row); + } + + for (shift, col) in (count_cols..size).enumerate() { + let col = col - shift; + records.remove_row(col); + } + } + } + Self::Bottom | Self::Top => { + for row in 0..count_rows / 2 { + for col in 0..count_cols { + let last_row = count_rows - row - 1; + records.swap((last_row, col), (row, col)); + } + } + } + } + } +} diff --git a/vendor/tabled/src/settings/settings_list.rs b/vendor/tabled/src/settings/settings_list.rs new file mode 100644 index 000000000..dc8aec179 --- /dev/null +++ b/vendor/tabled/src/settings/settings_list.rs @@ -0,0 +1,71 @@ +use crate::settings::TableOption; + +#[cfg(feature = "std")] +use crate::grid::config::Entity; +#[cfg(feature = "std")] +use crate::settings::CellOption; + +/// Settings is a combinator of [`TableOption`]s. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Settings<A = EmptySettings, B = EmptySettings>(A, B); + +impl Default for Settings<EmptySettings, EmptySettings> { + fn default() -> Self { + Self(EmptySettings, EmptySettings) + } +} + +impl Settings<(), ()> { + /// Creates an empty list. + pub const fn empty() -> Settings<EmptySettings, EmptySettings> { + Settings(EmptySettings, EmptySettings) + } +} + +impl<A, B> Settings<A, B> { + /// Creates a new combinator. + pub const fn new(settings1: A, settings2: B) -> Settings<A, B> { + Settings(settings1, settings2) + } + + /// Add an option to a combinator. + pub const fn with<C>(self, settings: C) -> Settings<Self, C> { + Settings(self, settings) + } +} + +#[cfg(feature = "std")] +impl<R, C, A, B> CellOption<R, C> for Settings<A, B> +where + A: CellOption<R, C>, + B: CellOption<R, C>, +{ + fn change(self, records: &mut R, cfg: &mut C, entity: Entity) { + self.0.change(records, cfg, entity); + self.1.change(records, cfg, entity); + } +} + +impl<R, D, C, A, B> TableOption<R, D, C> for Settings<A, B> +where + A: TableOption<R, D, C>, + B: TableOption<R, D, C>, +{ + fn change(self, records: &mut R, cfg: &mut C, dims: &mut D) { + self.0.change(records, cfg, dims); + self.1.change(records, cfg, dims); + } +} + +/// A marker structure to be able to create an empty [`Settings`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct EmptySettings; + +#[cfg(feature = "std")] +impl<R, C> CellOption<R, C> for EmptySettings { + fn change(self, _: &mut R, _: &mut C, _: Entity) {} +} + +impl<R, D, C> TableOption<R, D, C> for EmptySettings { + fn change(self, _: &mut R, _: &mut C, _: &mut D) {} +} diff --git a/vendor/tabled/src/settings/shadow/mod.rs b/vendor/tabled/src/settings/shadow/mod.rs new file mode 100644 index 000000000..6b8ff4861 --- /dev/null +++ b/vendor/tabled/src/settings/shadow/mod.rs @@ -0,0 +1,195 @@ +//! This module contains a [`Shadow`] option for a [`Table`]. +//! +//! # Example +//! +//! ``` +//! use tabled::{Table, settings::{Shadow, Style}}; +//! +//! let data = vec!["Hello", "World", "!"]; +//! +//! let table = Table::new(data) +//! .with(Style::markdown()) +//! .with(Shadow::new(1)) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "| &str | \n", +//! "|-------|▒\n", +//! "| Hello |▒\n", +//! "| World |▒\n", +//! "| ! |▒\n", +//! " ▒▒▒▒▒▒▒▒▒", +//! ) +//! ); +//! ``` +//! +//! [`Table`]: crate::Table + +use crate::{ + grid::color::AnsiColor, + grid::config::{ColoredConfig, Indent, Offset, Sides}, + settings::{color::Color, TableOption}, +}; + +/// The structure represents a shadow of a table. +/// +/// NOTICE: It uses [`Margin`] therefore it often can't be combined. +/// +/// [`Margin`]: crate::settings::Margin +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Shadow { + c: char, + size: usize, + size_offset: usize, + direction: Sides<bool>, + color: Option<Color>, +} + +impl Shadow { + /// A default fill character to be used. + pub const DEFAULT_FILL: char = '▒'; + + /// Construct's an [`Shadow`] object with default fill [`Shadow::DEFAULT_FILL`]. + /// + /// It uses space(' ') as a default fill character. + /// To set a custom character you can use [`Self::set_fill`] function. + pub fn new(size: usize) -> Self { + Self { + c: Self::DEFAULT_FILL, + size, + size_offset: 1, + direction: Sides::new(false, true, false, true), + color: None, + } + } + + /// The function, sets a characters for the [`Shadow`] to be used. + pub fn set_fill(mut self, c: char) -> Self { + self.c = c; + self + } + + /// Set an offset value (default is '1'). + pub fn set_offset(mut self, size: usize) -> Self { + self.size_offset = size; + self + } + + /// Switch shadow to top. + pub fn set_top(mut self) -> Self { + self.direction.top = true; + self.direction.bottom = false; + self + } + + /// Switch shadow to bottom. + pub fn set_bottom(mut self) -> Self { + self.direction.bottom = true; + self.direction.top = false; + self + } + + /// Switch shadow to left. + pub fn set_left(mut self) -> Self { + self.direction.left = true; + self.direction.right = false; + self + } + + /// Switch shadow to right. + pub fn set_right(mut self) -> Self { + self.direction.right = true; + self.direction.left = false; + self + } + + /// Sets a color for a shadow. + pub fn set_color(mut self, color: Color) -> Self { + self.color = Some(color); + self + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for Shadow { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + set_margin(cfg, self.size, self.c, &self.direction); + set_margin_offset(cfg, self.size_offset, &self.direction); + + if let Some(color) = &self.color { + set_margin_color(cfg, color.clone().into(), &self.direction); + } + } +} + +fn set_margin(cfg: &mut ColoredConfig, size: usize, c: char, direction: &Sides<bool>) { + let mut margin: Sides<Indent> = Sides::default(); + if direction.top { + margin.top.size = size; + margin.top.fill = c; + } + + if direction.bottom { + margin.bottom.size = size; + margin.bottom.fill = c; + } + + if direction.left { + margin.left.size = size; + margin.left.fill = c; + } + + if direction.right { + margin.right.size = size; + margin.right.fill = c; + } + + cfg.set_margin(margin); +} + +fn set_margin_offset(cfg: &mut ColoredConfig, size: usize, direction: &Sides<bool>) { + let mut margin = Sides::filled(Offset::Begin(0)); + if direction.right && direction.bottom { + margin.bottom = Offset::Begin(size); + margin.right = Offset::Begin(size); + } + + if direction.right && direction.top { + margin.top = Offset::Begin(size); + margin.right = Offset::End(size); + } + + if direction.left && direction.bottom { + margin.bottom = Offset::End(size); + margin.left = Offset::Begin(size); + } + + if direction.left && direction.top { + margin.top = Offset::End(size); + margin.left = Offset::End(size); + } + + cfg.set_margin_offset(margin); +} + +fn set_margin_color(cfg: &mut ColoredConfig, color: AnsiColor<'static>, direction: &Sides<bool>) { + let mut margin: Sides<Option<AnsiColor<'static>>> = Sides::default(); + if direction.right { + margin.right = Some(color.clone()); + } + + if direction.top { + margin.top = Some(color.clone()); + } + + if direction.left { + margin.left = Some(color.clone()); + } + + if direction.bottom { + margin.bottom = Some(color.clone()); + } + + cfg.set_margin_color(margin); +} diff --git a/vendor/tabled/src/settings/span/column.rs b/vendor/tabled/src/settings/span/column.rs new file mode 100644 index 000000000..50af64c23 --- /dev/null +++ b/vendor/tabled/src/settings/span/column.rs @@ -0,0 +1,125 @@ +use crate::{ + grid::{ + config::{ColoredConfig, Entity, Position, SpannedConfig}, + records::{ExactRecords, Records}, + }, + settings::CellOption, +}; + +/// Columns (Vertical) span. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct ColumnSpan { + size: usize, +} + +impl ColumnSpan { + /// Creates a new column (vertical) span. + pub fn new(size: usize) -> Self { + Self { size } + } + + /// Creates a new column (vertical) span with a maximux value possible. + pub fn max() -> Self { + Self::new(usize::MAX) + } +} + +impl<R> CellOption<R, ColoredConfig> for ColumnSpan +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + set_col_spans(cfg, self.size, entity, (count_rows, count_cols)); + remove_false_spans(cfg); + } +} + +fn set_col_spans(cfg: &mut SpannedConfig, span: usize, entity: Entity, shape: (usize, usize)) { + for pos in entity.iter(shape.0, shape.1) { + if !is_valid_pos(pos, shape) { + continue; + } + + let mut span = span; + if !is_column_span_valid(pos.1, span, shape.1) { + span = shape.1 - pos.1; + } + + if span_has_intersections(cfg, pos, span) { + continue; + } + + set_span_column(cfg, pos, span); + } +} + +fn set_span_column(cfg: &mut SpannedConfig, pos: (usize, usize), span: usize) { + if span == 0 { + let (row, col) = pos; + if col == 0 { + return; + } + + if let Some(closecol) = closest_visible(cfg, (row, col - 1)) { + let span = col + 1 - closecol; + cfg.set_column_span((row, closecol), span); + } + } + + cfg.set_column_span(pos, span); +} + +fn closest_visible(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> { + loop { + if cfg.is_cell_visible(pos) { + return Some(pos.1); + } + + if pos.1 == 0 { + return None; + } + + pos.1 -= 1; + } +} + +fn is_column_span_valid(col: usize, span: usize, count_cols: usize) -> bool { + span + col <= count_cols +} + +fn is_valid_pos((row, col): Position, (count_rows, count_cols): (usize, usize)) -> bool { + row < count_rows && col < count_cols +} + +fn span_has_intersections(cfg: &SpannedConfig, (row, col): Position, span: usize) -> bool { + for col in col..col + span { + if !cfg.is_cell_visible((row, col)) { + return true; + } + } + + false +} + +fn remove_false_spans(cfg: &mut SpannedConfig) { + for (pos, _) in cfg.get_column_spans() { + if cfg.is_cell_visible(pos) { + continue; + } + + cfg.set_row_span(pos, 1); + cfg.set_column_span(pos, 1); + } + + for (pos, _) in cfg.get_row_spans() { + if cfg.is_cell_visible(pos) { + continue; + } + + cfg.set_row_span(pos, 1); + cfg.set_column_span(pos, 1); + } +} diff --git a/vendor/tabled/src/settings/span/mod.rs b/vendor/tabled/src/settings/span/mod.rs new file mode 100644 index 000000000..1139a2b99 --- /dev/null +++ b/vendor/tabled/src/settings/span/mod.rs @@ -0,0 +1,68 @@ +//! This module contains a [`Span`] settings, it helps to +//! make a cell take more space then it generally takes. +//! +//! # Example +//! +//! ``` +//! use tabled::{settings::{Span, Modify}, Table}; +//! +//! let data = [[1, 2, 3], [4, 5, 6]]; +//! +//! let table = Table::new(data) +//! .with(Modify::new((2, 0)).with(Span::column(2))) +//! .with(Modify::new((0, 1)).with(Span::column(2))) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "+---+---+---+\n", +//! "| 0 | 1 |\n", +//! "+---+---+---+\n", +//! "| 1 | 2 | 3 |\n", +//! "+---+---+---+\n", +//! "| 4 | 6 |\n", +//! "+---+---+---+", +//! ) +//! ) +//! ``` + +mod column; +mod row; + +pub use column::ColumnSpan; +pub use row::RowSpan; + +/// Span represent a horizontal/column span setting for any cell on a [`Table`]. +/// +/// It will be ignored if: +/// - cell position is out of scope +/// - size is bigger then the total number of columns. +/// - size is bigger then the total number of rows. +/// +/// ```rust,no_run +/// # use tabled::{Table, settings::{Style, Span, Modify, object::Columns}}; +/// # let data: Vec<&'static str> = Vec::new(); +/// let table = Table::new(&data) +/// .with(Modify::new(Columns::single(0)).with(Span::column(2))); +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Span; + +impl Span { + /// New constructs a horizontal/column [`Span`]. + /// + /// If size is bigger then the total number of columns it will be ignored. + pub fn column(size: usize) -> ColumnSpan { + ColumnSpan::new(size) + } + + /// New constructs a vertical/row [`Span`]. + /// + /// If size is bigger then the total number of rows it will be ignored. + pub fn row(size: usize) -> RowSpan { + RowSpan::new(size) + } +} diff --git a/vendor/tabled/src/settings/span/row.rs b/vendor/tabled/src/settings/span/row.rs new file mode 100644 index 000000000..68673f7a2 --- /dev/null +++ b/vendor/tabled/src/settings/span/row.rs @@ -0,0 +1,126 @@ +use crate::{ + grid::{ + config::{ColoredConfig, Entity, Position, SpannedConfig}, + records::{ExactRecords, Records}, + }, + settings::CellOption, +}; + +/// Row (vertical) span. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct RowSpan { + size: usize, +} + +impl RowSpan { + /// Creates a new row (vertical) span. + pub const fn new(size: usize) -> Self { + Self { size } + } + + /// Creates a new row (vertical) span with a maximux value possible. + pub const fn max() -> Self { + Self::new(usize::MAX) + } +} + +impl<R> CellOption<R, ColoredConfig> for RowSpan +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + set_row_spans(cfg, self.size, entity, (count_rows, count_cols)); + remove_false_spans(cfg); + } +} + +fn set_row_spans(cfg: &mut SpannedConfig, span: usize, entity: Entity, shape: (usize, usize)) { + for pos in entity.iter(shape.0, shape.1) { + if !is_valid_pos(pos, shape) { + continue; + } + + let mut span = span; + if !is_row_span_valid(pos.0, span, shape.0) { + span = shape.0 - pos.0; + } + + if span_has_intersections(cfg, pos, span) { + continue; + } + + set_span_row(cfg, pos, span); + } +} + +fn set_span_row(cfg: &mut SpannedConfig, pos: (usize, usize), span: usize) { + if span == 0 { + let (row, col) = pos; + if row == 0 { + return; + } + + if let Some(closerow) = closest_visible_row(cfg, (row - 1, col)) { + let span = row + 1 - closerow; + cfg.set_row_span((closerow, col), span); + } + } + + cfg.set_row_span(pos, span); +} + +fn closest_visible_row(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> { + loop { + if cfg.is_cell_visible(pos) { + return Some(pos.0); + } + + if pos.0 == 0 { + // can happen if we have a above horizontal spanned cell + return None; + } + + pos.0 -= 1; + } +} + +fn is_row_span_valid(row: usize, span: usize, count_rows: usize) -> bool { + span + row <= count_rows +} + +fn is_valid_pos((row, col): Position, (count_rows, count_cols): (usize, usize)) -> bool { + row < count_rows && col < count_cols +} + +fn span_has_intersections(cfg: &SpannedConfig, (row, col): Position, span: usize) -> bool { + for row in row..row + span { + if !cfg.is_cell_visible((row, col)) { + return true; + } + } + + false +} + +fn remove_false_spans(cfg: &mut SpannedConfig) { + for (pos, _) in cfg.get_column_spans() { + if cfg.is_cell_visible(pos) { + continue; + } + + cfg.set_row_span(pos, 1); + cfg.set_column_span(pos, 1); + } + + for (pos, _) in cfg.get_row_spans() { + if cfg.is_cell_visible(pos) { + continue; + } + + cfg.set_row_span(pos, 1); + cfg.set_column_span(pos, 1); + } +} diff --git a/vendor/tabled/src/settings/split/mod.rs b/vendor/tabled/src/settings/split/mod.rs new file mode 100644 index 000000000..32f820aef --- /dev/null +++ b/vendor/tabled/src/settings/split/mod.rs @@ -0,0 +1,420 @@ +//! This module contains a [`Split`] setting which is used to +//! format the cells of a [`Table`] by a provided index, direction, behavior, and display preference. +//! +//! [`Table`]: crate::Table + +use core::ops::Range; + +use papergrid::{config::Position, records::PeekableRecords}; + +use crate::grid::records::{ExactRecords, Records, Resizable}; + +use super::TableOption; + +#[derive(Debug, Clone, Copy)] +enum Direction { + Column, + Row, +} + +#[derive(Debug, Clone, Copy)] +enum Behavior { + Concat, + Zip, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +enum Display { + Clean, + Retain, +} + +/// Returns a new [`Table`] formatted with several optional parameters. +/// +/// The required index parameter determines how many columns/rows a table will be redistributed into. +/// +/// - index +/// - direction +/// - behavior +/// - display +/// +/// # Example +/// +/// ```rust,no_run +/// use std::iter::FromIterator; +/// use tabled::{ +/// settings::split::Split, +/// Table, +/// }; +/// +/// let mut table = Table::from_iter(['a'..='z']); +/// let table = table.with(Split::column(4)).to_string(); +/// +/// assert_eq!(table, "+---+---+---+---+\n\ +/// | a | b | c | d |\n\ +/// +---+---+---+---+\n\ +/// | e | f | g | h |\n\ +/// +---+---+---+---+\n\ +/// | i | j | k | l |\n\ +/// +---+---+---+---+\n\ +/// | m | n | o | p |\n\ +/// +---+---+---+---+\n\ +/// | q | r | s | t |\n\ +/// +---+---+---+---+\n\ +/// | u | v | w | x |\n\ +/// +---+---+---+---+\n\ +/// | y | z | | |\n\ +/// +---+---+---+---+") +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug, Clone, Copy)] +pub struct Split { + direction: Direction, + behavior: Behavior, + display: Display, + index: usize, +} + +impl Split { + /// Returns a new [`Table`] split on the column at the provided index. + /// + /// The column found at that index becomes the new right-most column in the returned table. + /// Columns found beyond the index are redistributed into the table based on other defined + /// parameters. + /// + /// ```rust,no_run + /// # use tabled::settings::split::Split; + /// Split::column(4); + /// ``` + /// + /// [`Table`]: crate::Table + pub fn column(index: usize) -> Self { + Split { + direction: Direction::Column, + behavior: Behavior::Zip, + display: Display::Clean, + index, + } + } + + /// Returns a new [`Table`] split on the row at the provided index. + /// + /// The row found at that index becomes the new bottom row in the returned table. + /// Rows found beyond the index are redistributed into the table based on other defined + /// parameters. + /// + /// ```rust,no_run + /// # use tabled::settings::split::Split; + /// Split::row(4); + /// ``` + /// + /// [`Table`]: crate::Table + pub fn row(index: usize) -> Self { + Split { + direction: Direction::Row, + behavior: Behavior::Zip, + display: Display::Clean, + index, + } + } + + /// Returns a split [`Table`] with the redistributed cells pushed to the back of the new shape. + /// + /// ```text + /// +---+---+ + /// | a | b | + /// +---+---+ + /// +---+---+---+---+---+ | f | g | + /// | a | b | c | d | e | Split::column(2).concat() +---+---+ + /// +---+---+---+---+---+ => | c | d | + /// | f | g | h | i | j | +---+---+ + /// +---+---+---+---+---+ | h | i | + /// +---+---+ + /// | e | | + /// +---+---+ + /// | j | | + /// +---+---+ + /// ``` + /// + /// [`Table`]: crate::Table + pub fn concat(self) -> Self { + Self { + behavior: Behavior::Concat, + ..self + } + } + + /// Returns a split [`Table`] with the redistributed cells inserted behind + /// the first correlating column/row one after another. + /// + /// ```text + /// +---+---+ + /// | a | b | + /// +---+---+ + /// +---+---+---+---+---+ | c | d | + /// | a | b | c | d | e | Split::column(2).zip() +---+---+ + /// +---+---+---+---+---+ => | e | | + /// | f | g | h | i | j | +---+---+ + /// +---+---+---+---+---+ | f | g | + /// +---+---+ + /// | h | i | + /// +---+---+ + /// | j | | + /// +---+---+ + /// ``` + /// + /// [`Table`]: crate::Table + pub fn zip(self) -> Self { + Self { + behavior: Behavior::Zip, + ..self + } + } + + /// Returns a split [`Table`] with the empty columns/rows filtered out. + /// + /// ```text + /// + /// + /// +---+---+---+ + /// +---+---+---+---+---+ | a | b | c | + /// | a | b | c | d | e | Split::column(3).clean() +---+---+---+ + /// +---+---+---+---+---+ => | d | e | | + /// | f | g | h | | | +---+---+---+ + /// +---+---+---+---+---+ | f | g | h | + /// ^ ^ +---+---+---+ + /// these cells are filtered + /// from the resulting table + /// ``` + /// + /// ## Notes + /// + /// This is apart of the default configuration for Split. + /// + /// See [`retain`] for an alternative display option. + /// + /// [`Table`]: crate::Table + /// [`retain`]: crate::settings::split::Split::retain + pub fn clean(self) -> Self { + Self { + display: Display::Clean, + ..self + } + } + + /// Returns a split [`Table`] with all cells retained. + /// + /// ```text + /// +---+---+---+ + /// | a | b | c | + /// +---+---+---+ + /// +---+---+---+---+---+ | d | e | | + /// | a | b | c | d | e | Split::column(3).retain() +---+---+---+ + /// +---+---+---+---+---+ => | f | g | h | + /// | f | g | h | | | +---+---+---+ + /// +---+---+---+---+---+ |-----------> | | | | + /// ^ ^ | +---+---+---+ + /// |___|_____cells are kept! + /// ``` + /// + /// ## Notes + /// + /// See [`clean`] for an alternative display option. + /// + /// [`Table`]: crate::Table + /// [`clean`]: crate::settings::split::Split::clean + pub fn retain(self) -> Self { + Self { + display: Display::Retain, + ..self + } + } +} + +impl<R, D, Cfg> TableOption<R, D, Cfg> for Split +where + R: Records + ExactRecords + Resizable + PeekableRecords, +{ + fn change(self, records: &mut R, _: &mut Cfg, _: &mut D) { + // variables + let Split { + direction, + behavior, + display, + index: section_length, + } = self; + let mut filtered_sections = 0; + + // early return check + if records.count_columns() == 0 || records.count_rows() == 0 || section_length == 0 { + return; + } + + // computed variables + let (primary_length, secondary_length) = compute_length_arrangement(records, direction); + let sections_per_direction = ceil_div(primary_length, section_length); + let (outer_range, inner_range) = + compute_range_order(secondary_length, sections_per_direction, behavior); + + // work + for outer_index in outer_range { + let from_secondary_index = outer_index * sections_per_direction - filtered_sections; + for inner_index in inner_range.clone() { + let (section_index, from_secondary_index, to_secondary_index) = + compute_range_variables( + outer_index, + inner_index, + secondary_length, + from_secondary_index, + sections_per_direction, + filtered_sections, + behavior, + ); + + match (direction, behavior) { + (Direction::Column, Behavior::Concat) => records.push_row(), + (Direction::Column, Behavior::Zip) => records.insert_row(to_secondary_index), + (Direction::Row, Behavior::Concat) => records.push_column(), + (Direction::Row, Behavior::Zip) => records.insert_column(to_secondary_index), + } + + let section_is_empty = copy_section( + records, + section_length, + section_index, + primary_length, + from_secondary_index, + to_secondary_index, + direction, + ); + + if section_is_empty && display == Display::Clean { + delete(records, to_secondary_index, direction); + filtered_sections += 1; + } + } + } + + cleanup(records, section_length, primary_length, direction); + } +} + +/// Determine which direction should be considered the primary, and which the secondary based on direction +fn compute_length_arrangement<R>(records: &mut R, direction: Direction) -> (usize, usize) +where + R: Records + ExactRecords, +{ + match direction { + Direction::Column => (records.count_columns(), records.count_rows()), + Direction::Row => (records.count_rows(), records.count_columns()), + } +} + +/// reduce the table size to the length of the index in the specified direction +fn cleanup<R>(records: &mut R, section_length: usize, primary_length: usize, direction: Direction) +where + R: Resizable, +{ + for segment in (section_length..primary_length).rev() { + match direction { + Direction::Column => records.remove_column(segment), + Direction::Row => records.remove_row(segment), + } + } +} + +/// Delete target index row or column +fn delete<R>(records: &mut R, target_index: usize, direction: Direction) +where + R: Resizable, +{ + match direction { + Direction::Column => records.remove_row(target_index), + Direction::Row => records.remove_column(target_index), + } +} + +/// copy cells to new location +/// +/// returns if the copied section was entirely blank +fn copy_section<R>( + records: &mut R, + section_length: usize, + section_index: usize, + primary_length: usize, + from_secondary_index: usize, + to_secondary_index: usize, + direction: Direction, +) -> bool +where + R: ExactRecords + Resizable + PeekableRecords, +{ + let mut section_is_empty = true; + for to_primary_index in 0..section_length { + let from_primary_index = to_primary_index + section_index * section_length; + + if from_primary_index < primary_length { + let from_position = + format_position(direction, from_primary_index, from_secondary_index); + if records.get_text(from_position) != "" { + section_is_empty = false; + } + records.swap( + from_position, + format_position(direction, to_primary_index, to_secondary_index), + ); + } + } + section_is_empty +} + +/// determine section over direction or vice versa based on behavior +fn compute_range_order( + direction_length: usize, + sections_per_direction: usize, + behavior: Behavior, +) -> (Range<usize>, Range<usize>) { + match behavior { + Behavior::Concat => (1..sections_per_direction, 0..direction_length), + Behavior::Zip => (0..direction_length, 1..sections_per_direction), + } +} + +/// helper function for shimming both behaviors to work within a single nested loop +fn compute_range_variables( + outer_index: usize, + inner_index: usize, + direction_length: usize, + from_secondary_index: usize, + sections_per_direction: usize, + filtered_sections: usize, + behavior: Behavior, +) -> (usize, usize, usize) { + match behavior { + Behavior::Concat => ( + outer_index, + inner_index, + inner_index + outer_index * direction_length - filtered_sections, + ), + Behavior::Zip => ( + inner_index, + from_secondary_index, + outer_index * sections_per_direction + inner_index - filtered_sections, + ), + } +} + +/// utility for arguments of a position easily +fn format_position(direction: Direction, primary_index: usize, secondary_index: usize) -> Position { + match direction { + Direction::Column => (secondary_index, primary_index), + Direction::Row => (primary_index, secondary_index), + } +} + +/// ceil division utility because the std lib ceil_div isn't stable yet +fn ceil_div(x: usize, y: usize) -> usize { + debug_assert!(x != 0); + 1 + ((x - 1) / y) +} diff --git a/vendor/tabled/src/settings/style/border.rs b/vendor/tabled/src/settings/style/border.rs new file mode 100644 index 000000000..a8cbefb5d --- /dev/null +++ b/vendor/tabled/src/settings/style/border.rs @@ -0,0 +1,159 @@ +use crate::{ + grid::{ + config::{Border as GBorder, ColoredConfig, Entity}, + records::{ExactRecords, Records}, + }, + settings::CellOption, +}; + +/// Border represents a border of a Cell. +/// +/// ```text +/// top border +/// | +/// V +/// corner top left ------> +_______+ <---- corner top left +/// | | +/// left border ----------> | cell | <---- right border +/// | | +/// corner bottom right --> +_______+ <---- corner bottom right +/// ^ +/// | +/// bottom border +/// ``` +/// +/// ```rust,no_run +/// # use tabled::{Table, settings::{Modify, style::{Style, Border}, object::Rows}}; +/// # let data: Vec<&'static str> = Vec::new(); +/// let table = Table::new(&data) +/// .with(Style::ascii()) +/// .with(Modify::new(Rows::single(0)).with(Border::default().top('x'))); +/// ``` +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Border(GBorder<char>); + +impl Border { + /// This function constructs a cell borders with all sides set. + #[allow(clippy::too_many_arguments)] + pub const fn full( + top: char, + bottom: char, + left: char, + right: char, + top_left: char, + top_right: char, + bottom_left: char, + bottom_right: char, + ) -> Self { + Self(GBorder::full( + top, + bottom, + left, + right, + top_left, + top_right, + bottom_left, + bottom_right, + )) + } + + /// This function constructs a cell borders with all sides's char set to a given character. + /// It behaves like [`Border::full`] with the same character set to each side. + pub const fn filled(c: char) -> Self { + Self::full(c, c, c, c, c, c, c, c) + } + + /// Using this function you deconstruct the existing borders. + pub const fn empty() -> EmptyBorder { + EmptyBorder + } + + /// Set a top border character. + pub const fn top(mut self, c: char) -> Self { + self.0.top = Some(c); + self + } + + /// Set a bottom border character. + pub const fn bottom(mut self, c: char) -> Self { + self.0.bottom = Some(c); + self + } + + /// Set a left border character. + pub const fn left(mut self, c: char) -> Self { + self.0.left = Some(c); + self + } + + /// Set a right border character. + pub const fn right(mut self, c: char) -> Self { + self.0.right = Some(c); + self + } + + /// Set a top left intersection character. + pub const fn corner_top_left(mut self, c: char) -> Self { + self.0.left_top_corner = Some(c); + self + } + + /// Set a top right intersection character. + pub const fn corner_top_right(mut self, c: char) -> Self { + self.0.right_top_corner = Some(c); + self + } + + /// Set a bottom left intersection character. + pub const fn corner_bottom_left(mut self, c: char) -> Self { + self.0.left_bottom_corner = Some(c); + self + } + + /// Set a bottom right intersection character. + pub const fn corner_bottom_right(mut self, c: char) -> Self { + self.0.right_bottom_corner = Some(c); + self + } +} + +impl<R> CellOption<R, ColoredConfig> for Border +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let shape = (records.count_rows(), records.count_columns()); + + for pos in entity.iter(shape.0, shape.1) { + cfg.set_border(pos, self.0); + } + } +} + +impl From<GBorder<char>> for Border { + fn from(b: GBorder<char>) -> Border { + Border(b) + } +} + +impl From<Border> for GBorder<char> { + fn from(value: Border) -> Self { + value.0 + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct EmptyBorder; + +impl<R> CellOption<R, ColoredConfig> for EmptyBorder +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let shape = (records.count_rows(), records.count_columns()); + + for pos in entity.iter(shape.0, shape.1) { + cfg.remove_border(pos, shape); + } + } +} diff --git a/vendor/tabled/src/settings/style/border_char.rs b/vendor/tabled/src/settings/style/border_char.rs new file mode 100644 index 000000000..b1e04c579 --- /dev/null +++ b/vendor/tabled/src/settings/style/border_char.rs @@ -0,0 +1,99 @@ +use crate::{ + grid::config::{ColoredConfig, Entity, Position, SpannedConfig}, + grid::records::{ExactRecords, Records}, + settings::CellOption, +}; + +use super::Offset; + +/// [`BorderChar`] sets a char to a specific location on a horizontal line. +/// +/// # Example +/// +/// ```rust +/// use tabled::{Table, settings::{style::{Style, BorderChar, Offset}, Modify, object::{Object, Rows, Columns}}}; +/// +/// let mut table = Table::new(["Hello World"]); +/// table +/// .with(Style::markdown()) +/// .with(Modify::new(Rows::single(1)) +/// .with(BorderChar::horizontal(':', Offset::Begin(0))) +/// .with(BorderChar::horizontal(':', Offset::End(0))) +/// ) +/// .with(Modify::new((1, 0).and((1, 1))).with(BorderChar::vertical('#', Offset::Begin(0)))); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "| &str |\n", +/// "|:-----------:|\n", +/// "# Hello World #", +/// ), +/// ); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct BorderChar { + c: char, + offset: Offset, + horizontal: bool, +} + +impl BorderChar { + /// Creates a [`BorderChar`] which overrides horizontal line. + pub fn horizontal(c: char, offset: Offset) -> Self { + Self { + c, + offset, + horizontal: true, + } + } + + /// Creates a [`BorderChar`] which overrides vertical line. + pub fn vertical(c: char, offset: Offset) -> Self { + Self { + c, + offset, + horizontal: false, + } + } +} + +impl<R> CellOption<R, ColoredConfig> for BorderChar +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let cells = entity.iter(records.count_rows(), records.count_columns()); + + match self.horizontal { + true => add_char_horizontal(cfg, self.c, self.offset, cells), + false => add_char_vertical(cfg, self.c, self.offset, cells), + } + } +} + +fn add_char_vertical<I: Iterator<Item = Position>>( + cfg: &mut SpannedConfig, + c: char, + offset: Offset, + cells: I, +) { + let offset = offset.into(); + + for pos in cells { + cfg.set_vertical_char(pos, c, offset); + } +} + +fn add_char_horizontal<I: Iterator<Item = Position>>( + cfg: &mut SpannedConfig, + c: char, + offset: Offset, + cells: I, +) { + let offset = offset.into(); + + for pos in cells { + cfg.set_horizontal_char(pos, c, offset); + } +} diff --git a/vendor/tabled/src/settings/style/border_color.rs b/vendor/tabled/src/settings/style/border_color.rs new file mode 100644 index 000000000..6b3fa2b1a --- /dev/null +++ b/vendor/tabled/src/settings/style/border_color.rs @@ -0,0 +1,155 @@ +//! This module contains a configuration of a Border to set its color via [`BorderColor`]. + +use crate::{ + grid::{ + color::AnsiColor, + config::{Border, ColoredConfig, Entity}, + records::{ExactRecords, Records}, + }, + settings::{color::Color, CellOption, TableOption}, +}; + +/// BorderColored represents a colored border of a Cell. +/// +/// ```rust,no_run +/// # use tabled::{settings::{style::BorderColor, Style, Color, object::Rows, Modify}, Table}; +/// # +/// # let data: Vec<&'static str> = Vec::new(); +/// # +/// let table = Table::new(&data) +/// .with(Style::ascii()) +/// .with(Modify::new(Rows::single(0)).with(BorderColor::default().top(Color::FG_RED))); +/// ``` +#[derive(Debug, Clone, Default, Eq, PartialEq)] +pub struct BorderColor(Border<AnsiColor<'static>>); + +impl BorderColor { + /// This function constructs a cell borders with all sides set. + #[allow(clippy::too_many_arguments)] + pub fn full( + top: Color, + bottom: Color, + left: Color, + right: Color, + top_left: Color, + top_right: Color, + bottom_left: Color, + bottom_right: Color, + ) -> Self { + Self(Border::full( + top.into(), + bottom.into(), + left.into(), + right.into(), + top_left.into(), + top_right.into(), + bottom_left.into(), + bottom_right.into(), + )) + } + + /// This function constructs a cell borders with all sides's char set to a given character. + /// It behaves like [`Border::full`] with the same character set to each side. + pub fn filled(c: Color) -> Self { + let c: AnsiColor<'_> = c.into(); + + Self(Border { + top: Some(c.clone()), + bottom: Some(c.clone()), + left: Some(c.clone()), + right: Some(c.clone()), + left_bottom_corner: Some(c.clone()), + left_top_corner: Some(c.clone()), + right_bottom_corner: Some(c.clone()), + right_top_corner: Some(c), + }) + } + + /// Set a top border character. + pub fn top(mut self, c: Color) -> Self { + self.0.top = Some(c.into()); + self + } + + /// Set a bottom border character. + pub fn bottom(mut self, c: Color) -> Self { + self.0.bottom = Some(c.into()); + self + } + + /// Set a left border character. + pub fn left(mut self, c: Color) -> Self { + self.0.left = Some(c.into()); + self + } + + /// Set a right border character. + pub fn right(mut self, c: Color) -> Self { + self.0.right = Some(c.into()); + self + } + + /// Set a top left intersection character. + pub fn corner_top_left(mut self, c: Color) -> Self { + self.0.left_top_corner = Some(c.into()); + self + } + + /// Set a top right intersection character. + pub fn corner_top_right(mut self, c: Color) -> Self { + self.0.right_top_corner = Some(c.into()); + self + } + + /// Set a bottom left intersection character. + pub fn corner_bottom_left(mut self, c: Color) -> Self { + self.0.left_bottom_corner = Some(c.into()); + self + } + + /// Set a bottom right intersection character. + pub fn corner_bottom_right(mut self, c: Color) -> Self { + self.0.right_bottom_corner = Some(c.into()); + self + } +} + +impl<R> CellOption<R, ColoredConfig> for BorderColor +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let border_color = &self.0; + + for pos in entity.iter(count_rows, count_columns) { + cfg.set_border_color(pos, border_color.clone()); + } + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for BorderColor +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let border_color = &self.0; + + for row in 0..count_rows { + for col in 0..count_columns { + cfg.set_border_color((row, col), border_color.clone()); + } + } + } +} + +impl From<BorderColor> for Border<AnsiColor<'static>> { + fn from(val: BorderColor) -> Self { + val.0 + } +} diff --git a/vendor/tabled/src/settings/style/border_text.rs b/vendor/tabled/src/settings/style/border_text.rs new file mode 100644 index 000000000..163a19821 --- /dev/null +++ b/vendor/tabled/src/settings/style/border_text.rs @@ -0,0 +1,253 @@ +use crate::{ + grid::{ + color::AnsiColor, + config::{self, ColoredConfig, SpannedConfig}, + dimension::{Dimension, Estimate}, + records::{ExactRecords, Records}, + }, + settings::{ + object::{FirstRow, LastRow}, + Color, TableOption, + }, +}; + +use super::Offset; + +/// [`BorderText`] writes a custom text on a border. +/// +/// # Example +/// +/// ```rust +/// use tabled::{Table, settings::style::BorderText, settings::object::Rows}; +/// +/// let mut table = Table::new(["Hello World"]); +/// table +/// .with(BorderText::new("+-.table").horizontal(Rows::first())); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-.table------+\n\ +/// | &str |\n\ +/// +-------------+\n\ +/// | Hello World |\n\ +/// +-------------+" +/// ); +/// ``` +#[derive(Debug)] +pub struct BorderText<L> { + text: String, + offset: Offset, + color: Option<AnsiColor<'static>>, + line: L, +} + +impl BorderText<()> { + /// Creates a [`BorderText`] instance. + /// + /// Lines are numbered from 0 to the `count_rows` included + /// (`line >= 0 && line <= count_rows`). + pub fn new<S: Into<String>>(text: S) -> Self { + BorderText { + text: text.into(), + line: (), + offset: Offset::Begin(0), + color: None, + } + } +} + +impl<Line> BorderText<Line> { + /// Set a line on which we will set the text. + pub fn horizontal<L>(self, line: L) -> BorderText<L> { + BorderText { + line, + text: self.text, + offset: self.offset, + color: self.color, + } + } + + /// Set an offset from which the text will be started. + pub fn offset(self, offset: Offset) -> Self { + BorderText { + offset, + text: self.text, + line: self.line, + color: self.color, + } + } + + /// Set a color of the text. + pub fn color(self, color: Color) -> Self { + BorderText { + color: Some(color.into()), + text: self.text, + line: self.line, + offset: self.offset, + } + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for BorderText<usize> +where + R: Records + ExactRecords, + for<'a> &'a R: Records, + for<'a> D: Estimate<&'a R, ColoredConfig>, + D: Dimension, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { + dims.estimate(records, cfg); + let shape = (records.count_rows(), records.count_columns()); + let line = self.line; + set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape); + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for BorderText<FirstRow> +where + R: Records + ExactRecords, + for<'a> &'a R: Records, + for<'a> D: Estimate<&'a R, ColoredConfig>, + D: Dimension, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { + dims.estimate(records, cfg); + let shape = (records.count_rows(), records.count_columns()); + let line = 0; + set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape); + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for BorderText<LastRow> +where + R: Records + ExactRecords, + for<'a> &'a R: Records, + for<'a> D: Estimate<&'a R, ColoredConfig>, + D: Dimension, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { + dims.estimate(records, cfg); + let shape = (records.count_rows(), records.count_columns()); + let line = records.count_rows(); + set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape); + } +} + +fn set_horizontal_chars<D: Dimension>( + cfg: &mut SpannedConfig, + dims: &D, + offset: Offset, + line: usize, + text: &str, + color: &Option<AnsiColor<'static>>, + shape: (usize, usize), +) { + let (_, count_columns) = shape; + let pos = get_start_pos(cfg, dims, offset, count_columns); + let pos = match pos { + Some(pos) => pos, + None => return, + }; + + let mut chars = text.chars(); + let mut i = cfg.has_vertical(0, count_columns) as usize; + if i == 1 && pos == 0 { + let c = match chars.next() { + Some(c) => c, + None => return, + }; + + let mut b = cfg.get_border((line, 0), shape); + b.left_top_corner = b.left_top_corner.map(|_| c); + cfg.set_border((line, 0), b); + + if let Some(color) = color.as_ref() { + let mut b = cfg.get_border_color((line, 0), shape).cloned(); + b.left_top_corner = Some(color.clone()); + cfg.set_border_color((line, 0), b); + } + } + + for col in 0..count_columns { + let w = dims.get_width(col); + if i + w > pos { + for off in 0..w { + if i + off < pos { + continue; + } + + let c = match chars.next() { + Some(c) => c, + None => return, + }; + + cfg.set_horizontal_char((line, col), c, config::Offset::Begin(off)); + if let Some(color) = color.as_ref() { + cfg.set_horizontal_color( + (line, col), + color.clone(), + config::Offset::Begin(off), + ); + } + } + } + + i += w; + + if cfg.has_vertical(col + 1, count_columns) { + i += 1; + + if i > pos { + let c = match chars.next() { + Some(c) => c, + None => return, + }; + + let mut b = cfg.get_border((line, col), shape); + b.right_top_corner = b.right_top_corner.map(|_| c); + cfg.set_border((line, col), b); + + if let Some(color) = color.as_ref() { + let mut b = cfg.get_border_color((line, col), shape).cloned(); + b.right_top_corner = Some(color.clone()); + cfg.set_border_color((line, col), b); + } + } + } + } +} + +fn get_start_pos<D: Dimension>( + cfg: &SpannedConfig, + dims: &D, + offset: Offset, + count_columns: usize, +) -> Option<usize> { + let totalw = total_width(cfg, dims, count_columns); + match offset { + Offset::Begin(i) => { + if i > totalw { + None + } else { + Some(i) + } + } + Offset::End(i) => { + if i > totalw { + None + } else { + Some(totalw - i) + } + } + } +} + +fn total_width<D: Dimension>(cfg: &SpannedConfig, dims: &D, count_columns: usize) -> usize { + let mut totalw = cfg.has_vertical(0, count_columns) as usize; + for col in 0..count_columns { + totalw += dims.get_width(col); + totalw += cfg.has_vertical(col + 1, count_columns) as usize; + } + + totalw +} diff --git a/vendor/tabled/src/settings/style/builder.rs b/vendor/tabled/src/settings/style/builder.rs new file mode 100644 index 000000000..d55c400cc --- /dev/null +++ b/vendor/tabled/src/settings/style/builder.rs @@ -0,0 +1,1208 @@ +//! This module contains a compile time style builder [`Style`]. + +use core::marker::PhantomData; + +use crate::{ + grid::config::{Borders, CompactConfig, CompactMultilineConfig}, + settings::TableOption, +}; + +#[cfg(feature = "std")] +use crate::grid::config::ColoredConfig; + +use super::{HorizontalLine, Line, VerticalLine}; + +/// Style is represents a theme of a [`Table`]. +/// +/// ```text +/// corner top left top intersection corner top right +/// . | . +/// . V . +/// ╭───┬───┬───┬───┬───┬───┬────┬────┬────╮ +/// │ i │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ +/// ├───┼───┼───┼───┼───┼───┼────┼────┼────┤ <- this horizontal line is custom 'horizontals' +/// │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ other lines horizontal lines are not set they called 'horizontal' +/// │ 1 │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ +/// │ 2 │ 0 │ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │ +/// ╰───┴───┴───┴───┴───┴───┴────┴────┴────╯ +/// . ^ ^ . +/// . | | . +/// corner bottom left | bottom intersection corner bottom right +/// | +/// | +/// all this vertical lines are called 'vertical' +/// ``` +/// +/// +/// ```text +/// ┌───┬───┬───┬───┬───┐ +/// │ 0 │ 1 │ 2 │ 3 │ 4 │ +/// intersection left ->├───X───X───X───X───┤ <- all this horizontal lines are called 'horizontal' +/// │ 1 │ 2 │ 3 │ 4 │ 5 │ +/// ├───X───X───X───X───┤ <- intersection right +/// │ 2 │ 3 │ 4 │ 5 │ 6 │ +/// └───┴───┴───┴───┴───┘ +/// +/// All 'X' positions are called 'intersection'. +/// It's a place where 'vertical' and 'horizontal' lines intersect. +/// ``` +/// +/// It tries to limit an controlling a valid state of it. +/// For example, it won't allow to call method [`Style::corner_top_left`] unless [`Style::left`] and [`Style::top`] is set. +/// +/// You can turn [`Style`] into [`RawStyle`] to have more control using [`Into`] implementation. +/// +/// # Example +/// +#[cfg_attr(feature = "std", doc = "```")] +#[cfg_attr(not(feature = "std"), doc = "```ignore")] +/// use tabled::{Table, settings::Style}; +/// +/// let style = Style::ascii() +/// .bottom('*') +/// .intersection(' '); +/// +/// let data = vec!["Hello", "2021"]; +/// let table = Table::new(&data).with(style).to_string(); +/// +/// println!("{}", table); +/// ``` +/// +/// [`Table`]: crate::Table +/// [`RawStyle`]: crate::settings::style::RawStyle +/// [`Style::corner_top_left`]: Style::corner_top_left +/// [`Style::left`]: Style.left +/// [`Style::top`]: Style.function.top +#[derive(Debug, Clone)] +pub struct Style<T, B, L, R, H, V, HLines = HLineArray<0>, VLines = VLineArray<0>> { + borders: Borders<char>, + horizontals: HLines, + verticals: VLines, + _top: PhantomData<T>, + _bottom: PhantomData<B>, + _left: PhantomData<L>, + _right: PhantomData<R>, + _horizontal: PhantomData<H>, + _vertical: PhantomData<V>, +} + +type HLineArray<const N: usize> = [HorizontalLine; N]; + +type VLineArray<const N: usize> = [VerticalLine; N]; + +/// A marker struct which is used in [`Style`]. +#[derive(Debug, Clone)] +pub struct On; + +impl Style<(), (), (), (), (), (), (), ()> { + /// This style is a style with no styling options on, + /// + /// ```text + /// id destribution link + /// 0 Fedora https://getfedora.org/ + /// 2 OpenSUSE https://www.opensuse.org/ + /// 3 Endeavouros https://endeavouros.com/ + /// ``` + /// + /// Note: The cells in the example have 1-left and 1-right indent. + /// + /// This style can be used as a base style to build a custom one. + /// + /// ```rust,no_run + /// # use tabled::settings::Style; + /// let style = Style::empty() + /// .top('*') + /// .bottom('*') + /// .vertical('#') + /// .intersection_top('*'); + /// ``` + pub const fn empty() -> Style<(), (), (), (), (), ()> { + Style::new( + create_borders( + Line::empty(), + Line::empty(), + Line::empty(), + None, + None, + None, + ), + [], + [], + ) + } + + /// This style is analog of `empty` but with a vertical space(' ') line. + /// + /// ```text + /// id destribution link + /// 0 Fedora https://getfedora.org/ + /// 2 OpenSUSE https://www.opensuse.org/ + /// 3 Endeavouros https://endeavouros.com/ + /// ``` + pub const fn blank() -> Style<(), (), (), (), (), On> { + Style::new( + create_borders( + Line::empty(), + Line::empty(), + Line::empty(), + None, + None, + Some(' '), + ), + [], + [], + ) + } + + /// This is a style which relays only on ASCII charset. + /// + /// It has horizontal and vertical lines. + /// + /// ```text + /// +----+--------------+---------------------------+ + /// | id | destribution | link | + /// +----+--------------+---------------------------+ + /// | 0 | Fedora | https://getfedora.org/ | + /// +----+--------------+---------------------------+ + /// | 2 | OpenSUSE | https://www.opensuse.org/ | + /// +----+--------------+---------------------------+ + /// | 3 | Endeavouros | https://endeavouros.com/ | + /// +----+--------------+---------------------------+ + /// ``` + pub const fn ascii() -> Style<On, On, On, On, On, On> { + Style::new( + create_borders( + Line::full('-', '+', '+', '+'), + Line::full('-', '+', '+', '+'), + Line::full('-', '+', '+', '+'), + Some('|'), + Some('|'), + Some('|'), + ), + [], + [], + ) + } + + /// `psql` style looks like a table style `PostgreSQL` uses. + /// + /// It has only 1 horizontal line which splits header. + /// And no left and right vertical lines. + /// + /// ```text + /// id | destribution | link + /// ----+--------------+--------------------------- + /// 0 | Fedora | https://getfedora.org/ + /// 2 | OpenSUSE | https://www.opensuse.org/ + /// 3 | Endeavouros | https://endeavouros.com/ + /// ``` + pub const fn psql() -> Style<(), (), (), (), (), On, HLineArray<1>> { + Style::new( + create_borders( + Line::empty(), + Line::empty(), + Line::empty(), + None, + None, + Some('|'), + ), + [HorizontalLine::new(1, Line::empty()) + .main(Some('-')) + .intersection(Some('+'))], + [], + ) + } + + /// `markdown` style mimics a `Markdown` table style. + /// + /// ```text + /// | id | destribution | link | + /// |----|--------------|---------------------------| + /// | 0 | Fedora | https://getfedora.org/ | + /// | 2 | OpenSUSE | https://www.opensuse.org/ | + /// | 3 | Endeavouros | https://endeavouros.com/ | + /// ``` + pub const fn markdown() -> Style<(), (), On, On, (), On, HLineArray<1>> { + Style::new( + create_borders( + Line::empty(), + Line::empty(), + Line::empty(), + Some('|'), + Some('|'), + Some('|'), + ), + [HorizontalLine::new(1, Line::full('-', '|', '|', '|'))], + [], + ) + } + + /// This style is analog of [`Style::ascii`] which uses UTF-8 charset. + /// + /// It has vertical and horizontal split lines. + /// + /// ```text + /// ┌────┬──────────────┬───────────────────────────┐ + /// │ id │ destribution │ link │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 0 │ Fedora │ https://getfedora.org/ │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ + /// └────┴──────────────┴───────────────────────────┘ + /// ``` + pub const fn modern() -> Style<On, On, On, On, On, On> { + Style::new( + create_borders( + Line::full('─', '┬', '┌', '┐'), + Line::full('─', '┴', '└', '┘'), + Line::full('─', '┼', '├', '┤'), + Some('│'), + Some('│'), + Some('│'), + ), + [], + [], + ) + } + + /// This style looks like a [`Style::modern`] but without horozizontal lines except a header. + /// + /// Beware: It uses UTF-8 characters. + /// + /// ```text + /// ┌────┬──────────────┬───────────────────────────┐ + /// │ id │ destribution │ link │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 0 │ Fedora │ https://getfedora.org/ │ + /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ + /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ + /// └────┴──────────────┴───────────────────────────┘ + /// ``` + pub const fn sharp() -> Style<On, On, On, On, (), On, HLineArray<1>> { + Style::new( + create_borders( + Line::full('─', '┬', '┌', '┐'), + Line::full('─', '┴', '└', '┘'), + Line::empty(), + Some('│'), + Some('│'), + Some('│'), + ), + [HorizontalLine::new(1, Line::full('─', '┼', '├', '┤'))], + [], + ) + } + + /// This style looks like a [`Style::sharp`] but with rounded corners. + /// + /// Beware: It uses UTF-8 characters. + /// + /// ```text + /// ╭────┬──────────────┬───────────────────────────╮ + /// │ id │ destribution │ link │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 0 │ Fedora │ https://getfedora.org/ │ + /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ + /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ + /// ╰────┴──────────────┴───────────────────────────╯ + /// ``` + pub const fn rounded() -> Style<On, On, On, On, (), On, HLineArray<1>> { + Style::new( + create_borders( + Line::full('─', '┬', '╭', '╮'), + Line::full('─', '┴', '╰', '╯'), + Line::empty(), + Some('│'), + Some('│'), + Some('│'), + ), + [HorizontalLine::new(1, Line::full('─', '┼', '├', '┤'))], + [], + ) + } + + /// This style uses a chars which resembles '2 lines'. + /// + /// Beware: It uses UTF8 characters. + /// + /// ```text + /// ╔════╦══════════════╦═══════════════════════════╗ + /// ║ id ║ destribution ║ link ║ + /// ╠════╬══════════════╬═══════════════════════════╣ + /// ║ 0 ║ Fedora ║ https://getfedora.org/ ║ + /// ╠════╬══════════════╬═══════════════════════════╣ + /// ║ 2 ║ OpenSUSE ║ https://www.opensuse.org/ ║ + /// ╠════╬══════════════╬═══════════════════════════╣ + /// ║ 3 ║ Endeavouros ║ https://endeavouros.com/ ║ + /// ╚════╩══════════════╩═══════════════════════════╝ + /// ``` + pub const fn extended() -> Style<On, On, On, On, On, On> { + Style::new( + create_borders( + Line::full('═', '╦', '╔', '╗'), + Line::full('═', '╩', '╚', '╝'), + Line::full('═', '╬', '╠', '╣'), + Some('║'), + Some('║'), + Some('║'), + ), + [], + [], + ) + } + + /// This is a style uses only '.' and ':' chars. + /// It has a vertical and horizontal split lines. + /// + /// ```text + /// ................................................. + /// : id : destribution : link : + /// :....:..............:...........................: + /// : 0 : Fedora : https://getfedora.org/ : + /// :....:..............:...........................: + /// : 2 : OpenSUSE : https://www.opensuse.org/ : + /// :....:..............:...........................: + /// : 3 : Endeavouros : https://endeavouros.com/ : + /// :....:..............:...........................: + /// ``` + pub const fn dots() -> Style<On, On, On, On, On, On> { + Style::new( + create_borders( + Line::full('.', '.', '.', '.'), + Line::full('.', ':', ':', ':'), + Line::full('.', ':', ':', ':'), + Some(':'), + Some(':'), + Some(':'), + ), + [], + [], + ) + } + + /// This style is one of table views in `ReStructuredText`. + /// + /// ```text + /// ==== ============== =========================== + /// id destribution link + /// ==== ============== =========================== + /// 0 Fedora https://getfedora.org/ + /// 2 OpenSUSE https://www.opensuse.org/ + /// 3 Endeavouros https://endeavouros.com/ + /// ==== ============== =========================== + /// ``` + pub const fn re_structured_text() -> Style<On, On, (), (), (), On, HLineArray<1>> { + Style::new( + create_borders( + Line::new(Some('='), Some(' '), None, None), + Line::new(Some('='), Some(' '), None, None), + Line::empty(), + None, + None, + Some(' '), + ), + [HorizontalLine::new( + 1, + Line::new(Some('='), Some(' '), None, None), + )], + [], + ) + } + + /// This is a theme analog of [`Style::rounded`], but in using ascii charset and + /// with no horizontal lines. + /// + /// ```text + /// .-----------------------------------------------. + /// | id | destribution | link | + /// | 0 | Fedora | https://getfedora.org/ | + /// | 2 | OpenSUSE | https://www.opensuse.org/ | + /// | 3 | Endeavouros | https://endeavouros.com/ | + /// '-----------------------------------------------' + /// ``` + pub const fn ascii_rounded() -> Style<On, On, On, On, (), On> { + Style::new( + create_borders( + Line::full('-', '-', '.', '.'), + Line::full('-', '-', '\'', '\''), + Line::empty(), + Some('|'), + Some('|'), + Some('|'), + ), + [], + [], + ) + } +} + +impl<T, B, L, R, H, V, HLines, VLines> Style<T, B, L, R, H, V, HLines, VLines> { + /// Frame function returns a frame as a border. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::{Style, Highlight, object::Rows}}; + /// + /// let data = [["10:52:19", "Hello"], ["10:52:20", "World"]]; + /// let table = Table::new(data) + /// .with(Highlight::new(Rows::first(), Style::modern().get_frame())) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "┌──────────────────┐\n", + /// "│ 0 | 1 │\n", + /// "└──────────────────┘\n", + /// "| 10:52:19 | Hello |\n", + /// "+----------+-------+\n", + /// "| 10:52:20 | World |\n", + /// "+----------+-------+", + /// ) + /// ); + /// ``` + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub const fn get_frame(&self) -> super::Border { + let mut border = super::Border::filled(' '); + + if let Some(c) = self.borders.top { + border = border.top(c); + } + + if let Some(c) = self.borders.bottom { + border = border.bottom(c); + } + + if let Some(c) = self.borders.left { + border = border.left(c); + } + + if let Some(c) = self.borders.right { + border = border.right(c); + } + + if let Some(c) = self.borders.top_left { + border = border.corner_top_left(c); + } + + if let Some(c) = self.borders.bottom_left { + border = border.corner_bottom_left(c); + } + + if let Some(c) = self.borders.top_right { + border = border.corner_top_right(c); + } + + if let Some(c) = self.borders.bottom_right { + border = border.corner_bottom_right(c); + } + + border + } + + /// Get a [`Style`]'s default horizontal line. + /// + /// It doesn't return an overloaded line via [`Style::horizontals`]. + /// + /// # Example + /// + #[cfg_attr(feature = "std", doc = "```")] + #[cfg_attr(not(feature = "std"), doc = "```ignore")] + /// use tabled::{settings::style::{Style, HorizontalLine, Line}, Table}; + /// + /// let table = Table::new((0..3).map(|i| ("Hello", "World", i))) + /// .with(Style::ascii().remove_horizontal().horizontals([HorizontalLine::new(1, Style::modern().get_horizontal())])) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "+-------+-------+-----+\n", + /// "| &str | &str | i32 |\n", + /// "├───────┼───────┼─────┤\n", + /// "| Hello | World | 0 |\n", + /// "| Hello | World | 1 |\n", + /// "| Hello | World | 2 |\n", + /// "+-------+-------+-----+", + /// ) + /// ) + /// ``` + pub const fn get_horizontal(&self) -> Line { + Line::new( + self.borders.horizontal, + self.borders.intersection, + self.borders.left_intersection, + self.borders.right_intersection, + ) + } + + /// Get a [`Style`]'s default horizontal line. + /// + /// It doesn't return an overloaded line via [`Style::verticals`]. + /// + /// # Example + /// + #[cfg_attr(feature = "std", doc = "```")] + #[cfg_attr(not(feature = "std"), doc = "```ignore")] + /// use tabled::{settings::style::{Style, VerticalLine, Line}, Table}; + /// + /// let table = Table::new((0..3).map(|i| ("Hello", "World", i))) + /// .with(Style::ascii().remove_horizontal().verticals([VerticalLine::new(1, Style::modern().get_vertical())])) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "+-------┬-------+-----+\n", + /// "| &str │ &str | i32 |\n", + /// "| Hello │ World | 0 |\n", + /// "| Hello │ World | 1 |\n", + /// "| Hello │ World | 2 |\n", + /// "+-------┴-------+-----+", + /// ) + /// ) + /// ``` + pub const fn get_vertical(&self) -> Line { + Line::new( + self.borders.vertical, + self.borders.intersection, + self.borders.top_intersection, + self.borders.bottom_intersection, + ) + } + + /// Sets a top border. + /// + /// Any corners and intersections which were set will be overridden. + pub fn top(mut self, c: char) -> Style<On, B, L, R, H, V, HLines, VLines> + where + for<'a> &'a mut VLines: IntoIterator<Item = &'a mut VerticalLine>, + { + self.borders.top = Some(c); + + if self.borders.has_left() { + self.borders.top_left = Some(c); + } + + if self.borders.has_right() { + self.borders.top_right = Some(c); + } + + if self.borders.has_vertical() { + self.borders.top_intersection = Some(c); + } + + for vl in &mut self.verticals { + vl.line.connector1 = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a bottom border. + /// + /// Any corners and intersections which were set will be overridden. + pub fn bottom(mut self, c: char) -> Style<T, On, L, R, H, V, HLines, VLines> + where + for<'a> &'a mut VLines: IntoIterator<Item = &'a mut VerticalLine>, + { + self.borders.bottom = Some(c); + + if self.borders.has_left() { + self.borders.bottom_left = Some(c); + } + + if self.borders.has_right() { + self.borders.bottom_right = Some(c); + } + + if self.borders.has_vertical() { + self.borders.bottom_intersection = Some(c); + } + + for vl in &mut self.verticals { + vl.line.connector2 = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a left border. + /// + /// Any corners and intersections which were set will be overridden. + pub fn left(mut self, c: char) -> Style<T, B, On, R, H, V, HLines, VLines> + where + for<'a> &'a mut HLines: IntoIterator<Item = &'a mut HorizontalLine>, + { + self.borders.left = Some(c); + + if self.borders.has_top() { + self.borders.top_left = Some(c); + } + + if self.borders.has_bottom() { + self.borders.bottom_left = Some(c); + } + + if self.borders.has_horizontal() { + self.borders.left_intersection = Some(c); + } + + for hl in &mut self.horizontals { + hl.line.connector1 = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a right border. + /// + /// Any corners and intersections which were set will be overridden. + pub fn right(mut self, c: char) -> Style<T, B, L, On, H, V, HLines, VLines> + where + for<'a> &'a mut HLines: IntoIterator<Item = &'a mut HorizontalLine>, + { + self.borders.right = Some(c); + + if self.borders.has_top() { + self.borders.top_right = Some(c); + } + + if self.borders.has_bottom() { + self.borders.bottom_right = Some(c); + } + + if self.borders.has_horizontal() { + self.borders.right_intersection = Some(c); + } + + for hl in &mut self.horizontals { + hl.line.connector2 = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a horizontal split line. + /// + /// Any corners and intersections which were set will be overridden. + pub fn horizontal(mut self, c: char) -> Style<T, B, L, R, On, V, HLines, VLines> + where + for<'a> &'a mut VLines: IntoIterator<Item = &'a mut VerticalLine>, + { + self.borders.horizontal = Some(c); + + if self.borders.has_vertical() { + self.borders.intersection = Some(c); + } + + if self.borders.has_left() { + self.borders.left_intersection = Some(c); + } + + if self.borders.has_right() { + self.borders.right_intersection = Some(c); + } + + for vl in &mut self.verticals { + vl.line.intersection = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a vertical split line. + /// + /// Any corners and intersections which were set will be overridden. + pub fn vertical(mut self, c: char) -> Style<T, B, L, R, H, On, HLines, VLines> + where + for<'a> &'a mut HLines: IntoIterator<Item = &'a mut HorizontalLine>, + { + self.borders.vertical = Some(c); + + if self.borders.has_horizontal() { + self.borders.intersection = Some(c); + } + + if self.borders.has_top() { + self.borders.top_intersection = Some(c); + } + + if self.borders.has_bottom() { + self.borders.bottom_intersection = Some(c); + } + + for hl in &mut self.horizontals { + hl.line.intersection = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Set border horizontal lines. + /// + /// # Example + /// + #[cfg_attr(feature = "derive", doc = "```")] + #[cfg_attr(not(feature = "derive"), doc = "```ignore")] + /// use tabled::{settings::style::{Style, HorizontalLine, Line}, Table}; + /// + /// let table = Table::new((0..3).map(|i| ("Hello", i))) + /// .with(Style::rounded().horizontals((1..4).map(|i| HorizontalLine::new(i, Line::filled('#'))))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "╭───────┬─────╮\n", + /// "│ &str │ i32 │\n", + /// "###############\n", + /// "│ Hello │ 0 │\n", + /// "###############\n", + /// "│ Hello │ 1 │\n", + /// "###############\n", + /// "│ Hello │ 2 │\n", + /// "╰───────┴─────╯", + /// ) + /// ) + /// ``` + pub fn horizontals<NewLines>(self, lines: NewLines) -> Style<T, B, L, R, H, V, NewLines, VLines> + where + NewLines: IntoIterator<Item = HorizontalLine> + Clone, + { + Style::new(self.borders, lines, self.verticals) + } + + /// Set border vertical lines. + /// + /// # Example + /// + #[cfg_attr(feature = "derive", doc = "```")] + #[cfg_attr(not(feature = "derive"), doc = "```ignore")] + /// use tabled::{Table, settings::style::{Style, VerticalLine, Line}}; + /// + /// let table = Table::new((0..3).map(|i| ("Hello", i))) + /// .with(Style::rounded().verticals((0..3).map(|i| VerticalLine::new(i, Line::filled('#'))))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "#───────#─────#\n", + /// "# &str # i32 #\n", + /// "├───────┼─────┤\n", + /// "# Hello # 0 #\n", + /// "# Hello # 1 #\n", + /// "# Hello # 2 #\n", + /// "#───────#─────#", + /// ) + /// ) + /// ``` + pub fn verticals<NewLines>(self, lines: NewLines) -> Style<T, B, L, R, H, V, HLines, NewLines> + where + NewLines: IntoIterator<Item = VerticalLine> + Clone, + { + Style::new(self.borders, self.horizontals, lines) + } + + /// Removes all horizontal lines set by [`Style::horizontals`] + pub fn remove_horizontals(self) -> Style<T, B, L, R, H, V, HLineArray<0>, VLines> { + Style::new(self.borders, [], self.verticals) + } + + /// Removes all verticals lines set by [`Style::verticals`] + pub fn remove_verticals(self) -> Style<T, B, L, R, H, V, HLines, VLineArray<0>> { + Style::new(self.borders, self.horizontals, []) + } +} + +impl<B, R, H, V, HLines, VLines> Style<On, B, On, R, H, V, HLines, VLines> { + /// Sets a top left corner. + pub fn corner_top_left(mut self, c: char) -> Self { + self.borders.top_left = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<B, L, H, V, HLines, VLines> Style<On, B, L, On, H, V, HLines, VLines> { + /// Sets a top right corner. + pub fn corner_top_right(mut self, c: char) -> Self { + self.borders.top_right = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<T, L, H, V, HLines, VLines> Style<T, On, L, On, H, V, HLines, VLines> { + /// Sets a bottom right corner. + pub fn corner_bottom_right(mut self, c: char) -> Self { + self.borders.bottom_right = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<T, R, H, V, HLines, VLines> Style<T, On, On, R, H, V, HLines, VLines> { + /// Sets a bottom left corner. + pub fn corner_bottom_left(mut self, c: char) -> Self { + self.borders.bottom_left = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<T, B, R, V, HLines, VLines> Style<T, B, On, R, On, V, HLines, VLines> { + /// Sets a left intersection char. + pub fn intersection_left(mut self, c: char) -> Self { + self.borders.left_intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<T, B, L, V, HLines, VLines> Style<T, B, L, On, On, V, HLines, VLines> { + /// Sets a right intersection char. + pub fn intersection_right(mut self, c: char) -> Self { + self.borders.right_intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<B, L, R, H, HLines, VLines> Style<On, B, L, R, H, On, HLines, VLines> { + /// Sets a top intersection char. + pub fn intersection_top(mut self, c: char) -> Self { + self.borders.top_intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<T, L, R, H, HLines, VLines> Style<T, On, L, R, H, On, HLines, VLines> { + /// Sets a bottom intersection char. + pub fn intersection_bottom(mut self, c: char) -> Self { + self.borders.bottom_intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<T, B, L, R, HLines, VLines> Style<T, B, L, R, On, On, HLines, VLines> { + /// Sets an inner intersection char. + /// A char between horizontal and vertical split lines. + pub fn intersection(mut self, c: char) -> Self { + self.borders.intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<B, L, R, H, V, HLines, VLines> Style<On, B, L, R, H, V, HLines, VLines> { + /// Removes top border. + pub fn remove_top( + mut self, + ) -> Style<(), B, L, R, H, V, HLines, VerticalLineIter<VLines::IntoIter>> + where + VLines: IntoIterator<Item = VerticalLine> + Clone, + { + self.borders.top = None; + self.borders.top_intersection = None; + self.borders.top_left = None; + self.borders.top_right = None; + + let iter = VerticalLineIter::new(self.verticals.into_iter(), false, true, false); + Style::new(self.borders, self.horizontals, iter) + } +} + +impl<T, L, R, H, V, HLines, VLines> Style<T, On, L, R, H, V, HLines, VLines> { + /// Removes bottom border. + pub fn remove_bottom( + mut self, + ) -> Style<T, (), L, R, H, V, HLines, VerticalLineIter<VLines::IntoIter>> + where + VLines: IntoIterator<Item = VerticalLine> + Clone, + { + self.borders.bottom = None; + self.borders.bottom_intersection = None; + self.borders.bottom_left = None; + self.borders.bottom_right = None; + + let iter = VerticalLineIter::new(self.verticals.into_iter(), false, false, true); + Style::new(self.borders, self.horizontals, iter) + } +} + +impl<T, B, R, H, V, HLines, VLines> Style<T, B, On, R, H, V, HLines, VLines> { + /// Removes left border. + pub fn remove_left( + mut self, + ) -> Style<T, B, (), R, H, V, HorizontalLineIter<HLines::IntoIter>, VLines> + where + HLines: IntoIterator<Item = HorizontalLine> + Clone, + { + self.borders.left = None; + self.borders.left_intersection = None; + self.borders.top_left = None; + self.borders.bottom_left = None; + + let iter = HorizontalLineIter::new(self.horizontals.into_iter(), false, true, false); + Style::new(self.borders, iter, self.verticals) + } +} + +impl<T, B, L, H, V, HLines, VLines> Style<T, B, L, On, H, V, HLines, VLines> { + /// Removes right border. + pub fn remove_right( + mut self, + ) -> Style<T, B, L, (), H, V, HorizontalLineIter<HLines::IntoIter>, VLines> + where + HLines: IntoIterator<Item = HorizontalLine> + Clone, + { + self.borders.right = None; + self.borders.right_intersection = None; + self.borders.top_right = None; + self.borders.bottom_right = None; + + let iter = HorizontalLineIter::new(self.horizontals.into_iter(), false, false, true); + Style::new(self.borders, iter, self.verticals) + } +} + +impl<T, B, L, R, V, HLines, VLines> Style<T, B, L, R, On, V, HLines, VLines> { + /// Removes horizontal split lines. + /// + /// Not including custom split lines. + pub fn remove_horizontal( + mut self, + ) -> Style<T, B, L, R, (), V, HLines, VerticalLineIter<VLines::IntoIter>> + where + VLines: IntoIterator<Item = VerticalLine> + Clone, + { + self.borders.horizontal = None; + self.borders.left_intersection = None; + self.borders.right_intersection = None; + self.borders.intersection = None; + + let iter = VerticalLineIter::new(self.verticals.into_iter(), true, false, false); + Style::new(self.borders, self.horizontals, iter) + } +} + +impl<T, B, L, R, H, HLines, VLines> Style<T, B, L, R, H, On, HLines, VLines> { + /// Removes vertical split lines. + pub fn remove_vertical( + mut self, + ) -> Style<T, B, L, R, H, (), HorizontalLineIter<HLines::IntoIter>, VLines> + where + HLines: IntoIterator<Item = HorizontalLine> + Clone, + { + self.borders.vertical = None; + self.borders.top_intersection = None; + self.borders.bottom_intersection = None; + self.borders.intersection = None; + + let iter = HorizontalLineIter::new(self.horizontals.into_iter(), true, false, false); + Style::new(self.borders, iter, self.verticals) + } +} + +impl<T, B, L, R, H, V, HLines, VLines> Style<T, B, L, R, H, V, HLines, VLines> { + const fn new(borders: Borders<char>, horizontals: HLines, verticals: VLines) -> Self { + Self { + borders, + horizontals, + verticals, + _top: PhantomData, + _bottom: PhantomData, + _left: PhantomData, + _right: PhantomData, + _horizontal: PhantomData, + _vertical: PhantomData, + } + } + + /// Return borders of a table. + pub const fn get_borders(&self) -> &Borders<char> { + &self.borders + } + + /// Return custom horizontals which were set. + pub const fn get_horizontals(&self) -> &HLines { + &self.horizontals + } + + /// Return custom verticals which were set. + pub const fn get_verticals(&self) -> &VLines { + &self.verticals + } +} + +#[cfg(feature = "std")] +impl<T, B, L, R, H, V, HLines, VLines, I, D> TableOption<I, D, ColoredConfig> + for Style<T, B, L, R, H, V, HLines, VLines> +where + HLines: IntoIterator<Item = HorizontalLine> + Clone, + VLines: IntoIterator<Item = VerticalLine> + Clone, +{ + fn change(self, records: &mut I, cfg: &mut ColoredConfig, dimension: &mut D) { + cfg.clear_theme(); + + cfg.set_borders(self.borders); + + for hl in self.horizontals { + hl.change(records, cfg, dimension); + } + + for vl in self.verticals { + vl.change(records, cfg, dimension); + } + } +} + +impl<T, B, L, R, H, V, HLines, VLines, I, D> TableOption<I, D, CompactConfig> + for Style<T, B, L, R, H, V, HLines, VLines> +where + HLines: IntoIterator<Item = HorizontalLine> + Clone, +{ + fn change(self, records: &mut I, cfg: &mut CompactConfig, dimension: &mut D) { + *cfg = cfg.set_borders(self.borders); + + let first_line = self.horizontals.into_iter().next(); + if let Some(line) = first_line { + line.change(records, cfg, dimension); + } + } +} + +impl<T, B, L, R, H, V, HLines, VLines, I, D> TableOption<I, D, CompactMultilineConfig> + for Style<T, B, L, R, H, V, HLines, VLines> +where + HLines: IntoIterator<Item = HorizontalLine> + Clone, +{ + fn change(self, records: &mut I, cfg: &mut CompactMultilineConfig, dimension: &mut D) { + self.change(records, cfg.as_mut(), dimension) + } +} + +/// An iterator which limits [`Line`] influence on iterations over lines for in [`Style`]. +#[derive(Debug, Clone)] +pub struct HorizontalLineIter<I> { + iter: I, + intersection: bool, + left: bool, + right: bool, +} + +impl<I> HorizontalLineIter<I> { + fn new(iter: I, intersection: bool, left: bool, right: bool) -> Self { + Self { + iter, + intersection, + left, + right, + } + } +} + +impl<I> Iterator for HorizontalLineIter<I> +where + I: Iterator<Item = HorizontalLine>, +{ + type Item = HorizontalLine; + + fn next(&mut self) -> Option<Self::Item> { + let mut hl = self.iter.next()?; + + if self.intersection { + hl.line.intersection = None; + } + + if self.left { + hl.line.connector1 = None; + } + + if self.right { + hl.line.connector2 = None; + } + + Some(hl) + } +} + +/// An iterator which limits [`Line`] influence on iterations over lines for in [`Style`]. +#[derive(Debug, Clone)] +pub struct VerticalLineIter<I> { + iter: I, + intersection: bool, + top: bool, + bottom: bool, +} + +impl<I> VerticalLineIter<I> { + fn new(iter: I, intersection: bool, top: bool, bottom: bool) -> Self { + Self { + iter, + intersection, + top, + bottom, + } + } +} + +impl<I> Iterator for VerticalLineIter<I> +where + I: Iterator<Item = VerticalLine>, +{ + type Item = VerticalLine; + + fn next(&mut self) -> Option<Self::Item> { + let mut hl = self.iter.next()?; + + if self.intersection { + hl.line.intersection = None; + } + + if self.top { + hl.line.connector1 = None; + } + + if self.bottom { + hl.line.connector2 = None; + } + + Some(hl) + } +} + +const fn create_borders( + top: Line, + bottom: Line, + horizontal: Line, + left: Option<char>, + right: Option<char>, + vertical: Option<char>, +) -> Borders<char> { + Borders { + top: top.main, + bottom: bottom.main, + top_left: top.connector1, + top_right: top.connector2, + bottom_left: bottom.connector1, + bottom_right: bottom.connector2, + top_intersection: top.intersection, + bottom_intersection: bottom.intersection, + left_intersection: horizontal.connector1, + right_intersection: horizontal.connector2, + horizontal: horizontal.main, + intersection: horizontal.intersection, + left, + right, + vertical, + } +} diff --git a/vendor/tabled/src/settings/style/horizontal_line.rs b/vendor/tabled/src/settings/style/horizontal_line.rs new file mode 100644 index 000000000..bbcdc3fb4 --- /dev/null +++ b/vendor/tabled/src/settings/style/horizontal_line.rs @@ -0,0 +1,69 @@ +use crate::{ + grid::config::{CompactConfig, CompactMultilineConfig}, + settings::TableOption, +}; + +#[cfg(feature = "std")] +use crate::grid::config::{ColoredConfig, HorizontalLine as GridLine}; + +use super::Line; + +/// A horizontal split line which can be used to set a border. +#[cfg_attr(not(feature = "std"), allow(dead_code))] +#[derive(Debug, Clone)] +pub struct HorizontalLine { + pub(super) index: usize, + pub(super) line: Line, +} + +impl HorizontalLine { + /// Creates a new horizontal split line. + pub const fn new(index: usize, line: Line) -> Self { + Self { index, line } + } + + /// Sets a horizontal character. + pub const fn main(mut self, c: Option<char>) -> Self { + self.line.main = c; + self + } + + /// Sets a vertical intersection character. + pub const fn intersection(mut self, c: Option<char>) -> Self { + self.line.intersection = c; + self + } + + /// Sets a left character. + pub const fn left(mut self, c: Option<char>) -> Self { + self.line.connector1 = c; + self + } + + /// Sets a right character. + pub const fn right(mut self, c: Option<char>) -> Self { + self.line.connector2 = c; + self + } +} + +#[cfg(feature = "std")] +impl<R, D> TableOption<R, D, ColoredConfig> for HorizontalLine { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + cfg.insert_horizontal_line(self.index, GridLine::from(self.line)) + } +} + +impl<R, D> TableOption<R, D, CompactConfig> for HorizontalLine { + fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { + if self.index == 1 { + *cfg = cfg.set_first_horizontal_line(papergrid::config::Line::from(self.line)); + } + } +} + +impl<R, D> TableOption<R, D, CompactMultilineConfig> for HorizontalLine { + fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) { + self.change(records, cfg.as_mut(), dimension) + } +} diff --git a/vendor/tabled/src/settings/style/line.rs b/vendor/tabled/src/settings/style/line.rs new file mode 100644 index 000000000..10b3895c4 --- /dev/null +++ b/vendor/tabled/src/settings/style/line.rs @@ -0,0 +1,91 @@ +#[cfg(feature = "std")] +use crate::grid::config::{HorizontalLine, VerticalLine}; + +/// The structure represent a vertical or horizontal line. +#[derive(Debug, Default, Clone, Copy)] +pub struct Line { + pub(crate) main: Option<char>, + pub(crate) intersection: Option<char>, + pub(crate) connector1: Option<char>, + pub(crate) connector2: Option<char>, +} + +impl Line { + /// Creates a new [`Line`] object. + pub const fn new( + main: Option<char>, + intersection: Option<char>, + connector1: Option<char>, + connector2: Option<char>, + ) -> Self { + Self { + main, + intersection, + connector1, + connector2, + } + } + + /// Creates a new [`Line`] object with all chars set. + pub const fn full(main: char, intersection: char, connector1: char, connector2: char) -> Self { + Self::new( + Some(main), + Some(intersection), + Some(connector1), + Some(connector2), + ) + } + + /// Creates a new [`Line`] object with all chars set to the provided one. + pub const fn filled(c: char) -> Self { + Self::full(c, c, c, c) + } + + /// Creates a new [`Line`] object with all chars not set. + pub const fn empty() -> Self { + Self::new(None, None, None, None) + } + + /// Checks if the line has nothing set. + pub const fn is_empty(&self) -> bool { + self.main.is_none() + && self.intersection.is_none() + && self.connector1.is_none() + && self.connector2.is_none() + } +} + +#[cfg(feature = "std")] +impl From<Line> for HorizontalLine { + fn from(l: Line) -> Self { + Self { + main: l.main, + intersection: l.intersection, + left: l.connector1, + right: l.connector2, + } + } +} + +#[cfg(feature = "std")] +impl From<Line> for VerticalLine { + fn from(l: Line) -> Self { + Self { + main: l.main, + intersection: l.intersection, + top: l.connector1, + bottom: l.connector2, + } + } +} + +impl From<Line> for papergrid::config::Line<char> { + fn from(l: Line) -> Self { + Self { + main: l.main.unwrap_or(' '), + intersection: l.intersection, + connect1: l.connector1, + connect2: l.connector2, + } + } +} diff --git a/vendor/tabled/src/settings/style/mod.rs b/vendor/tabled/src/settings/style/mod.rs new file mode 100644 index 000000000..b8d7f688d --- /dev/null +++ b/vendor/tabled/src/settings/style/mod.rs @@ -0,0 +1,127 @@ +//! This module contains a list of primitives which can be applied to change [`Table`] style. +//! +//! ## [`Style`] +//! +//! It is responsible for a table border style. +//! An individual cell border can be set by [`Border`]. +//! +//! ### Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::Style}; +//! +//! let data = vec!["Hello", "2022"]; +//! let mut table = Table::new(&data); +//! table.with(Style::psql()); +//! +//! assert_eq!( +//! table.to_string(), +//! concat!( +//! " &str \n", +//! "-------\n", +//! " Hello \n", +//! " 2022 ", +//! ) +//! ) +//! ``` +//! +//! ## [`BorderText`] +//! +//! It's used to override a border with a custom text. +//! +//! ### Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::style::{BorderText, Style}}; +//! +//! let data = vec!["Hello", "2022"]; +//! let table = Table::new(&data) +//! .with(Style::psql()) +//! .with(BorderText::new("Santa").horizontal(1)) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! " &str \n", +//! "Santa--\n", +//! " Hello \n", +//! " 2022 ", +//! ) +//! ) +//! ``` +//! +//! ## [`Border`] +//! +//! [`Border`] can be used to modify cell's borders. +//! +//! It's possible to set a collored border when `color` feature is on. +//! +//! ### Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::{Modify, Style}}; +//! +//! let data = vec!["Hello", "2022"]; +//! let table = Table::new(&data) +//! .with(Style::psql()) +//! .with(Modify::new((0, 0)).with(Style::modern().get_frame())) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "┌───────┐\n", +//! "│ &str │\n", +//! "└───────┘\n", +//! " Hello \n", +//! " 2022 ", +//! ) +//! ) +//! ``` +//! +//! ## [`RawStyle`] +//! +//! A different representation of [`Style`]. +//! With no checks in place. +//! +//! It also contains a list of types to support colors. +//! +//! [`Table`]: crate::Table +//! [`BorderText`]: crate::settings::style::BorderText +//! [`RawStyle`]: crate::settings::style::RawStyle + +#[cfg(feature = "std")] +mod border; +#[cfg(feature = "std")] +mod border_char; +#[cfg(feature = "std")] +mod border_color; +#[cfg(feature = "std")] +mod border_text; +#[cfg(feature = "std")] +mod offset; +#[cfg(feature = "std")] +mod raw_style; +#[cfg(feature = "std")] +mod span_border_correction; + +mod builder; +mod horizontal_line; +mod line; +mod vertical_line; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use self::{ + border::Border, border_char::BorderChar, border_color::BorderColor, border_text::BorderText, + offset::Offset, raw_style::RawStyle, span_border_correction::BorderSpanCorrection, +}; + +pub use builder::{HorizontalLineIter, On, Style, VerticalLineIter}; +pub use horizontal_line::HorizontalLine; +pub use line::Line; +pub use vertical_line::VerticalLine; diff --git a/vendor/tabled/src/settings/style/offset.rs b/vendor/tabled/src/settings/style/offset.rs new file mode 100644 index 000000000..3f8a4f85e --- /dev/null +++ b/vendor/tabled/src/settings/style/offset.rs @@ -0,0 +1,19 @@ +use crate::grid::config; + +/// The structure represents an offset in a text. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Offset { + /// An offset from the start. + Begin(usize), + /// An offset from the end. + End(usize), +} + +impl From<Offset> for config::Offset { + fn from(o: Offset) -> Self { + match o { + Offset::Begin(i) => config::Offset::Begin(i), + Offset::End(i) => config::Offset::End(i), + } + } +} diff --git a/vendor/tabled/src/settings/style/raw_style.rs b/vendor/tabled/src/settings/style/raw_style.rs new file mode 100644 index 000000000..eedb48e79 --- /dev/null +++ b/vendor/tabled/src/settings/style/raw_style.rs @@ -0,0 +1,438 @@ +//! This module contains [`RawStyle`] structure, which is analogues to [`Style`] but not generic, +//! so sometimes it can be used more conviently. + +use std::collections::HashMap; + +use crate::{ + grid::{color::AnsiColor, config, config::Borders, config::ColoredConfig, records::Records}, + settings::{Color, TableOption}, +}; + +use super::{Border, HorizontalLine, Line, Style, VerticalLine}; + +/// A raw style data, which can be produced safely from [`Style`]. +/// +/// It can be useful in order to not have a generics and be able to use it as a variable more conveniently. +#[derive(Default, Debug, Clone)] +pub struct RawStyle { + borders: Borders<char>, + colors: Borders<AnsiColor<'static>>, + horizontals: HashMap<usize, Line>, + verticals: HashMap<usize, Line>, +} + +impl RawStyle { + /// Set a top border character. + pub fn set_top(&mut self, s: Option<char>) -> &mut Self { + self.borders.top = s; + self + } + + /// Set a top border color. + pub fn set_color_top(&mut self, color: Color) -> &mut Self { + self.colors.top = Some(color.into()); + self + } + + /// Set a bottom border character. + pub fn set_bottom(&mut self, s: Option<char>) -> &mut Self { + self.borders.bottom = s; + self + } + + /// Set a bottom border color. + pub fn set_color_bottom(&mut self, color: Color) -> &mut Self { + self.colors.bottom = Some(color.into()); + self + } + + /// Set a left border character. + pub fn set_left(&mut self, s: Option<char>) -> &mut Self { + self.borders.left = s; + self + } + + /// Set a left border color. + pub fn set_color_left(&mut self, color: Color) -> &mut Self { + self.colors.left = Some(color.into()); + self + } + + /// Set a right border character. + pub fn set_right(&mut self, s: Option<char>) -> &mut Self { + self.borders.right = s; + self + } + + /// Set a right border color. + pub fn set_color_right(&mut self, color: Color) -> &mut Self { + self.colors.right = Some(color.into()); + self + } + + /// Set a top intersection character. + pub fn set_intersection_top(&mut self, s: Option<char>) -> &mut Self { + self.borders.top_intersection = s; + self + } + + /// Set a top intersection color. + pub fn set_color_intersection_top(&mut self, color: Color) -> &mut Self { + self.colors.top_intersection = Some(color.into()); + self + } + + /// Set a bottom intersection character. + pub fn set_intersection_bottom(&mut self, s: Option<char>) -> &mut Self { + self.borders.bottom_intersection = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_intersection_bottom(&mut self, color: Color) -> &mut Self { + self.colors.bottom_intersection = Some(color.into()); + self + } + + /// Set a left split character. + pub fn set_intersection_left(&mut self, s: Option<char>) -> &mut Self { + self.borders.left_intersection = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_intersection_left(&mut self, color: Color) -> &mut Self { + self.colors.left_intersection = Some(color.into()); + self + } + + /// Set a right split character. + pub fn set_intersection_right(&mut self, s: Option<char>) -> &mut Self { + self.borders.right_intersection = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_intersection_right(&mut self, color: Color) -> &mut Self { + self.colors.right_intersection = Some(color.into()); + self + } + + /// Set an internal character. + pub fn set_intersection(&mut self, s: Option<char>) -> &mut Self { + self.borders.intersection = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_intersection(&mut self, color: Color) -> &mut Self { + self.colors.intersection = Some(color.into()); + self + } + + /// Set a vertical character. + pub fn set_vertical(&mut self, s: Option<char>) -> &mut Self { + self.borders.vertical = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_vertical(&mut self, color: Color) -> &mut Self { + self.colors.vertical = Some(color.into()); + self + } + + /// Set a horizontal character. + pub fn set_horizontal(&mut self, s: Option<char>) -> &mut Self { + self.borders.horizontal = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_horizontal(&mut self, color: Color) -> &mut Self { + self.colors.horizontal = Some(color.into()); + self + } + + /// Set a character for a top left corner. + pub fn set_corner_top_left(&mut self, s: Option<char>) -> &mut Self { + self.borders.top_left = s; + self + } + /// Set a bottom intersection color. + pub fn set_color_corner_top_left(&mut self, color: Color) -> &mut Self { + self.colors.top_left = Some(color.into()); + self + } + + /// Set a character for a top right corner. + pub fn set_corner_top_right(&mut self, s: Option<char>) -> &mut Self { + self.borders.top_right = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_corner_top_right(&mut self, color: Color) -> &mut Self { + self.colors.top_right = Some(color.into()); + self + } + + /// Set a character for a bottom left corner. + pub fn set_corner_bottom_left(&mut self, s: Option<char>) -> &mut Self { + self.borders.bottom_left = s; + self + } + /// Set a bottom intersection color. + pub fn set_color_corner_bottom_left(&mut self, color: Color) -> &mut Self { + self.colors.bottom_left = Some(color.into()); + self + } + + /// Set a character for a bottom right corner. + pub fn set_corner_bottom_right(&mut self, s: Option<char>) -> &mut Self { + self.borders.bottom_right = s; + self + } + /// Set a bottom intersection color. + pub fn set_color_corner_bottom_right(&mut self, color: Color) -> &mut Self { + self.colors.bottom_right = Some(color.into()); + self + } + + /// Set horizontal border lines. + /// + /// # Example + /// + /// ``` + /// use std::collections::HashMap; + /// use tabled::{Table, settings::style::{Style, Line, RawStyle}}; + /// + /// let mut style = RawStyle::from(Style::re_structured_text()); + /// + /// let mut lines = HashMap::new(); + /// lines.insert(1, Style::extended().get_horizontal()); + /// style.set_horizontals(lines); + /// + /// let table = Table::new((0..3).map(|i| ("Hello", i))) + /// .with(style) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// " ======= ===== \n", + /// " &str i32 \n", + /// "╠═══════╬═════╣\n", + /// " Hello 0 \n", + /// " Hello 1 \n", + /// " Hello 2 \n", + /// " ======= ===== ", + /// ), + /// ) + /// ``` + pub fn set_horizontals(&mut self, lines: HashMap<usize, Line>) -> &mut Self { + self.horizontals = lines; + self + } + + /// Insert a horizontal line to a specific row location. + pub fn insert_horizontal(&mut self, row: usize, line: Line) -> &mut Self { + let _ = self.horizontals.insert(row, line); + self + } + + /// Insert a horizontal line to a specific row location. + pub fn get_horizontal(&self, row: usize) -> Option<Line> { + self.horizontals.get(&row).cloned() + } + + /// Set vertical border lines. + /// + /// # Example + /// + /// ``` + /// use std::collections::HashMap; + /// use tabled::{Table, settings::style::{Style, Line, RawStyle}}; + /// + /// let mut style = RawStyle::from(Style::re_structured_text()); + /// + /// let mut lines = HashMap::new(); + /// lines.insert(1, Style::extended().get_horizontal()); + /// style.set_verticals(lines); + /// + /// let table = Table::new((0..3).map(|i| ("Hello", i))) + /// .with(style) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "=======╠=====\n", + /// " &str ═ i32 \n", + /// "======= =====\n", + /// " Hello ═ 0 \n", + /// " Hello ═ 1 \n", + /// " Hello ═ 2 \n", + /// "=======╣=====", + /// ), + /// ) + /// ``` + pub fn set_verticals(&mut self, lines: HashMap<usize, Line>) -> &mut Self { + self.verticals = lines; + self + } + + /// Insert a vertical line into specific column location. + pub fn insert_vertical(&mut self, column: usize, line: Line) -> &mut Self { + let _ = self.verticals.insert(column, line); + self + } + + /// Get a left char. + pub fn get_left(&self) -> Option<char> { + self.borders.left + } + + /// Get a left intersection char. + pub fn get_left_intersection(&self) -> Option<char> { + self.borders.left_intersection + } + + /// Get a right char. + pub fn get_right(&self) -> Option<char> { + self.borders.right + } + + /// Get a right intersection char. + pub fn get_right_intersection(&self) -> Option<char> { + self.borders.right_intersection + } + + /// Get a top char. + pub fn get_top(&self) -> Option<char> { + self.borders.top + } + + /// Get a top left char. + pub fn get_top_left(&self) -> Option<char> { + self.borders.top_left + } + + /// Get a top right char. + pub fn get_top_right(&self) -> Option<char> { + self.borders.top_right + } + + /// Get a top intersection char. + pub fn get_top_intersection(&self) -> Option<char> { + self.borders.top_intersection + } + + /// Get a bottom intersection char. + pub fn get_bottom(&self) -> Option<char> { + self.borders.bottom + } + + /// Get a bottom intersection char. + pub fn get_bottom_left(&self) -> Option<char> { + self.borders.bottom_left + } + + /// Get a bottom intersection char. + pub fn get_bottom_right(&self) -> Option<char> { + self.borders.bottom_right + } + + /// Get a bottom intersection char. + pub fn get_bottom_intersection(&self) -> Option<char> { + self.borders.bottom_intersection + } + + /// Returns an outer border of the style. + pub fn get_frame(&self) -> Border { + Border::from(crate::grid::config::Border { + top: self.borders.top, + bottom: self.borders.bottom, + left: self.borders.left, + right: self.borders.right, + left_top_corner: self.borders.top_left, + right_top_corner: self.borders.top_right, + left_bottom_corner: self.borders.bottom_left, + right_bottom_corner: self.borders.bottom_right, + }) + } + + /// Returns an general borders configuration of the style. + pub fn get_borders(&self) -> Borders<char> { + self.borders + } +} + +impl From<Borders<char>> for RawStyle { + fn from(borders: Borders<char>) -> Self { + Self { + borders, + horizontals: HashMap::new(), + verticals: HashMap::new(), + colors: Borders::default(), + } + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for RawStyle +where + R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) { + (&self).change(records, cfg, dimension) + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for &RawStyle { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + cfg.clear_theme(); + + cfg.set_borders(self.borders); + + for (&row, line) in &self.horizontals { + cfg.insert_horizontal_line(row, config::HorizontalLine::from(*line)); + } + + for (&col, line) in &self.verticals { + cfg.insert_vertical_line(col, config::VerticalLine::from(*line)); + } + + if !self.colors.is_empty() { + cfg.set_borders_color(self.colors.clone()); + } + } +} + +impl<T, B, L, R, H, V, HLines, VLines> From<Style<T, B, L, R, H, V, HLines, VLines>> for RawStyle +where + HLines: IntoIterator<Item = HorizontalLine> + Clone, + VLines: IntoIterator<Item = VerticalLine> + Clone, +{ + fn from(style: Style<T, B, L, R, H, V, HLines, VLines>) -> Self { + let horizontals = style + .get_horizontals() + .clone() + .into_iter() + .map(|hr| (hr.index, hr.line)) + .collect(); + + let verticals = style + .get_verticals() + .clone() + .into_iter() + .map(|hr| (hr.index, hr.line)) + .collect(); + + Self { + borders: *style.get_borders(), + horizontals, + verticals, + colors: Borders::default(), + } + } +} diff --git a/vendor/tabled/src/settings/style/span_border_correction.rs b/vendor/tabled/src/settings/style/span_border_correction.rs new file mode 100644 index 000000000..665a12788 --- /dev/null +++ b/vendor/tabled/src/settings/style/span_border_correction.rs @@ -0,0 +1,216 @@ +//! This module contains [`BorderSpanCorrection`] structure, which can be useful when [`Span`] is used, and +//! you want to fix the intersections symbols which are left intact by default. +//! +//! [`Span`]: crate::settings::span::Span + +use crate::{ + grid::{ + config::{ColoredConfig, Position, SpannedConfig}, + records::{ExactRecords, Records}, + }, + settings::TableOption, +}; + +/// A correctness function of style for [`Table`] which has [`Span`]s. +/// +/// Try to fix the style when table contains spans. +/// +/// By default [`Style`] doesn't implies any logic to better render split lines when +/// [`Span`] is used. +/// +/// So this function can be used to set the split lines in regard of spans used. +/// +/// # Example +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{ +/// Modify, style::{Style, BorderSpanCorrection}, +/// Format, Span, object::Cell +/// } +/// }; +/// +/// let data = vec![ +/// ("09", "June", "2022"), +/// ("10", "July", "2022"), +/// ]; +/// +/// let mut table = Table::new(data); +/// table.with(Modify::new((0, 0)).with("date").with(Span::column(3))); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "+----+------+------+\n", +/// "| date |\n", +/// "+----+------+------+\n", +/// "| 09 | June | 2022 |\n", +/// "+----+------+------+\n", +/// "| 10 | July | 2022 |\n", +/// "+----+------+------+", +/// ) +/// ); +/// +/// table.with(BorderSpanCorrection); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "+------------------+\n", +/// "| date |\n", +/// "+----+------+------+\n", +/// "| 09 | June | 2022 |\n", +/// "+----+------+------+\n", +/// "| 10 | July | 2022 |\n", +/// "+----+------+------+", +/// ) +/// ); +/// ``` +/// See [`BorderSpanCorrection`]. +/// +/// [`Table`]: crate::Table +/// [`Span`]: crate::settings::span::Span +/// [`Style`]: crate::settings::Style +/// [`Style::correct_spans`]: crate::settings::style::BorderSpanCorrection +#[derive(Debug)] +pub struct BorderSpanCorrection; + +impl<R, D> TableOption<R, D, ColoredConfig> for BorderSpanCorrection +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let shape = (records.count_rows(), records.count_columns()); + correct_span_styles(cfg, shape); + } +} + +fn correct_span_styles(cfg: &mut SpannedConfig, shape: (usize, usize)) { + for ((row, c), span) in cfg.get_column_spans() { + for col in c..c + span { + if col == 0 { + continue; + } + + let is_first = col == c; + let has_up = row > 0 && has_left(cfg, (row - 1, col), shape); + let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col), shape); + + let borders = cfg.get_borders(); + + let mut border = cfg.get_border((row, col), shape); + + let has_top_border = border.left_top_corner.is_some() && border.top.is_some(); + if has_top_border { + if has_up && is_first { + border.left_top_corner = borders.intersection; + } else if has_up { + border.left_top_corner = borders.bottom_intersection; + } else if is_first { + border.left_top_corner = borders.top_intersection; + } else { + border.left_top_corner = border.top; + } + } + + let has_bottom_border = border.left_bottom_corner.is_some() && border.bottom.is_some(); + if has_bottom_border { + if has_down && is_first { + border.left_bottom_corner = borders.intersection; + } else if has_down { + border.left_bottom_corner = borders.top_intersection; + } else if is_first { + border.left_bottom_corner = borders.bottom_intersection; + } else { + border.left_bottom_corner = border.bottom; + } + } + + cfg.set_border((row, col), border); + } + } + + for ((r, col), span) in cfg.get_row_spans() { + for row in r + 1..r + span { + let mut border = cfg.get_border((row, col), shape); + let borders = cfg.get_borders(); + + let has_left_border = border.left_top_corner.is_some(); + if has_left_border { + let has_left = col > 0 && has_top(cfg, (row, col - 1), shape); + if has_left { + border.left_top_corner = borders.right_intersection; + } else { + border.left_top_corner = borders.vertical; + } + } + + let has_right_border = border.right_top_corner.is_some(); + if has_right_border { + let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1), shape); + if has_right { + border.right_top_corner = borders.left_intersection; + } else { + border.right_top_corner = borders.vertical; + } + } + + cfg.set_border((row, col), border); + } + } + + let cells = iter_totaly_spanned_cells(cfg, shape).collect::<Vec<_>>(); + for (row, col) in cells { + if row == 0 { + continue; + } + + let mut border = cfg.get_border((row, col), shape); + let borders = cfg.get_borders(); + + let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1), shape); + let has_up = has_left(cfg, (row - 1, col), shape); + if has_up && !has_right { + border.right_top_corner = borders.right_intersection; + } + + let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col), shape); + if has_down { + border.left_bottom_corner = borders.top_intersection; + } + + cfg.set_border((row, col), border); + } +} + +fn has_left(cfg: &SpannedConfig, pos: Position, shape: (usize, usize)) -> bool { + if cfg.is_cell_covered_by_both_spans(pos) || cfg.is_cell_covered_by_column_span(pos) { + return false; + } + + let border = cfg.get_border(pos, shape); + border.left.is_some() || border.left_top_corner.is_some() || border.left_bottom_corner.is_some() +} + +fn has_top(cfg: &SpannedConfig, pos: Position, shape: (usize, usize)) -> bool { + if cfg.is_cell_covered_by_both_spans(pos) || cfg.is_cell_covered_by_row_span(pos) { + return false; + } + + let border = cfg.get_border(pos, shape); + border.top.is_some() || border.left_top_corner.is_some() || border.right_top_corner.is_some() +} + +fn iter_totaly_spanned_cells( + cfg: &SpannedConfig, + shape: (usize, usize), +) -> impl Iterator<Item = Position> + '_ { + // todo: can be optimized + let (count_rows, count_cols) = shape; + (0..count_rows).flat_map(move |row| { + (0..count_cols) + .map(move |col| (row, col)) + .filter(move |&p| cfg.is_cell_covered_by_both_spans(p)) + }) +} diff --git a/vendor/tabled/src/settings/style/vertical_line.rs b/vendor/tabled/src/settings/style/vertical_line.rs new file mode 100644 index 000000000..adbef2c82 --- /dev/null +++ b/vendor/tabled/src/settings/style/vertical_line.rs @@ -0,0 +1,50 @@ +#[cfg(feature = "std")] +use crate::grid::config::{ColoredConfig, VerticalLine as VLine}; + +use super::Line; + +/// A horizontal split line which can be used to set a border. +#[cfg_attr(not(feature = "std"), allow(dead_code))] +#[derive(Debug, Clone)] +pub struct VerticalLine { + pub(crate) index: usize, + pub(crate) line: Line, +} + +impl VerticalLine { + /// Creates a new horizontal split line. + pub const fn new(index: usize, line: Line) -> Self { + Self { index, line } + } + + /// Sets a horizontal character. + pub const fn main(mut self, c: Option<char>) -> Self { + self.line.main = c; + self + } + + /// Sets a vertical intersection character. + pub const fn intersection(mut self, c: Option<char>) -> Self { + self.line.intersection = c; + self + } + + /// Sets a top character. + pub const fn top(mut self, c: Option<char>) -> Self { + self.line.connector1 = c; + self + } + + /// Sets a bottom character. + pub const fn bottom(mut self, c: Option<char>) -> Self { + self.line.connector2 = c; + self + } +} + +#[cfg(feature = "std")] +impl<R, D> crate::settings::TableOption<R, D, ColoredConfig> for VerticalLine { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + cfg.insert_vertical_line(self.index, VLine::from(self.line)); + } +} diff --git a/vendor/tabled/src/settings/table_option.rs b/vendor/tabled/src/settings/table_option.rs new file mode 100644 index 000000000..e20aa4480 --- /dev/null +++ b/vendor/tabled/src/settings/table_option.rs @@ -0,0 +1,30 @@ +/// A trait which is responsible for configuration of a [`Table`]. +/// +/// [`Table`]: crate::Table +pub trait TableOption<R, D, C> { + /// The function allows modification of records and a grid configuration. + fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D); +} + +impl<T, R, D, C> TableOption<R, D, C> for &[T] +where + for<'a> &'a T: TableOption<R, D, C>, +{ + fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D) { + for opt in self { + opt.change(records, cfg, dimension) + } + } +} + +#[cfg(feature = "std")] +impl<T, R, D, C> TableOption<R, D, C> for Vec<T> +where + T: TableOption<R, D, C>, +{ + fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D) { + for opt in self { + opt.change(records, cfg, dimension) + } + } +} diff --git a/vendor/tabled/src/settings/themes/colorization.rs b/vendor/tabled/src/settings/themes/colorization.rs new file mode 100644 index 000000000..41c721330 --- /dev/null +++ b/vendor/tabled/src/settings/themes/colorization.rs @@ -0,0 +1,388 @@ +use papergrid::{ + color::AnsiColor, + config::{Entity, Sides}, +}; + +use crate::{ + grid::{ + config::ColoredConfig, + records::{ExactRecords, Records}, + }, + settings::{object::Object, Color, TableOption}, +}; + +/// [`Colorization`] sets a color for the whole table data (so it's not include the borders). +/// +/// You can colorize borders in a different round using [`BorderColor`] or [`RawStyle`] +/// +/// # Examples +/// +/// ``` +/// use std::iter::FromIterator; +/// +/// use tabled::builder::Builder; +/// use tabled::settings::{style::BorderColor, themes::Colorization, Color, Style}; +/// +/// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; +/// +/// let color1 = Color::FG_BLACK | Color::BG_WHITE; +/// let color2 = Color::BG_BLACK | Color::FG_WHITE; +/// let color3 = Color::FG_RED | Color::BG_RED; +/// +/// let mut table = Builder::from_iter(data).build(); +/// table +/// .with(Colorization::chess(color1, color2)) +/// .with(Style::modern()) +/// .with(BorderColor::filled(color3)); +/// +/// println!("{table}"); +/// ``` +/// +/// [`RawStyle`]: crate::settings::style::RawStyle +/// [`BorderColor`]: crate::settings::style::BorderColor +#[derive(Debug, Clone)] +pub struct Colorization { + pattern: ColorizationPattern, + colors: Vec<Color>, +} + +#[derive(Debug, Clone)] +enum ColorizationPattern { + Column, + Row, + ByRow, + ByColumn, + Chess, +} + +impl Colorization { + /// Creates a [`Colorization`] with a chess pattern. + /// + /// ``` + /// use std::iter::FromIterator; + /// + /// use tabled::builder::Builder; + /// use tabled::settings::{themes::Colorization, Color, Style}; + /// + /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; + /// + /// let color1 = Color::FG_BLACK | Color::BG_WHITE; + /// let color2 = Color::BG_BLACK | Color::FG_WHITE; + /// + /// let mut table = Builder::from_iter(data).build(); + /// table + /// .with(Colorization::chess(color1, color2)) + /// .with(Style::empty()); + /// + /// println!("{table}"); + /// ``` + pub fn chess(white: Color, black: Color) -> Self { + Self::new(vec![white, black], ColorizationPattern::Chess) + } + + /// Creates a [`Colorization`] with a target [`Object`]. + /// + /// ``` + /// use std::iter::FromIterator; + /// + /// use tabled::builder::Builder; + /// use tabled::settings::object::Rows; + /// use tabled::settings::{themes::Colorization, Color, Style}; + /// + /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; + /// + /// let color1 = Color::FG_BLACK | Color::BG_WHITE; + /// let color2 = Color::BG_BLACK | Color::FG_WHITE; + /// + /// let mut table = Builder::from_iter(data).build(); + /// table + /// .with(Colorization::exact([color1, color2], Rows::first())) + /// .with(Style::empty()); + /// + /// println!("{table}"); + /// ``` + pub fn exact<I, O>(colors: I, target: O) -> ExactColorization<O> + where + I: IntoIterator, + I::Item: Into<Color>, + { + let colors = colors.into_iter().map(Into::into).collect(); + ExactColorization::new(colors, target) + } + + /// Creates a [`Colorization`] with a pattern which changes row by row. + /// + /// ``` + /// use std::iter::FromIterator; + /// + /// use tabled::builder::Builder; + /// use tabled::settings::object::Rows; + /// use tabled::settings::{themes::Colorization, Color, Style}; + /// + /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; + /// + /// let color1 = Color::FG_BLACK | Color::BG_WHITE; + /// let color2 = Color::BG_BLACK | Color::FG_WHITE; + /// + /// let mut table = Builder::from_iter(data).build(); + /// table + /// .with(Colorization::rows([color1, color2])) + /// .with(Style::empty()); + /// + /// println!("{table}"); + /// ``` + pub fn rows<I>(colors: I) -> Self + where + I: IntoIterator, + I::Item: Into<Color>, + { + Self::new(colors, ColorizationPattern::Row) + } + + /// Creates a [`Colorization`] with a pattern which changes column by column. + /// + /// ``` + /// use std::iter::FromIterator; + /// + /// use tabled::builder::Builder; + /// use tabled::settings::object::Rows; + /// use tabled::settings::{themes::Colorization, Color, Style}; + /// + /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; + /// + /// let color1 = Color::FG_BLACK | Color::BG_WHITE; + /// let color2 = Color::BG_BLACK | Color::FG_WHITE; + /// + /// let mut table = Builder::from_iter(data).build(); + /// table + /// .with(Colorization::columns([color1, color2])) + /// .with(Style::empty()); + /// + /// println!("{table}"); + /// ``` + pub fn columns<I>(colors: I) -> Self + where + I: IntoIterator, + I::Item: Into<Color>, + { + Self::new(colors, ColorizationPattern::Column) + } + + /// Creates a [`Colorization`] with a pattern which peaks cells one by one iterating over rows. + /// + /// ``` + /// use std::iter::FromIterator; + /// + /// use tabled::builder::Builder; + /// use tabled::settings::object::Rows; + /// use tabled::settings::{themes::Colorization, Color, Style}; + /// + /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; + /// + /// let color1 = Color::FG_BLACK | Color::BG_WHITE; + /// let color2 = Color::BG_BLACK | Color::FG_WHITE; + /// + /// let mut table = Builder::from_iter(data).build(); + /// table + /// .with(Colorization::by_row([color1, color2])) + /// .with(Style::empty()); + /// + /// println!("{table}"); + /// ``` + pub fn by_row<I>(colors: I) -> Self + where + I: IntoIterator, + I::Item: Into<Color>, + { + Self::new(colors, ColorizationPattern::ByRow) + } + + /// Creates a [`Colorization`] with a pattern which peaks cells one by one iterating over columns. + /// + /// ``` + /// use std::iter::FromIterator; + /// + /// use tabled::builder::Builder; + /// use tabled::settings::object::Rows; + /// use tabled::settings::{themes::Colorization, Color, Style}; + /// + /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; + /// + /// let color1 = Color::FG_BLACK | Color::BG_WHITE; + /// let color2 = Color::BG_BLACK | Color::FG_WHITE; + /// + /// let mut table = Builder::from_iter(data).build(); + /// table + /// .with(Colorization::by_column([color1, color2])) + /// .with(Style::empty()); + /// + /// println!("{table}"); + /// ``` + pub fn by_column<I>(colors: I) -> Self + where + I: IntoIterator, + I::Item: Into<Color>, + { + Self::new(colors, ColorizationPattern::ByColumn) + } + + fn new<I>(colors: I, pattern: ColorizationPattern) -> Self + where + I: IntoIterator, + I::Item: Into<Color>, + { + let colors = colors.into_iter().map(Into::into).collect(); + Self { colors, pattern } + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for Colorization +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + if self.colors.is_empty() { + return; + } + + let count_columns = records.count_columns(); + let count_rows = records.count_rows(); + + match self.pattern { + ColorizationPattern::Column => colorize_columns(&self.colors, count_columns, cfg), + ColorizationPattern::Row => colorize_rows(&self.colors, count_rows, cfg), + ColorizationPattern::ByRow => { + colorize_by_row(&self.colors, count_rows, count_columns, cfg) + } + ColorizationPattern::ByColumn => { + colorize_by_column(&self.colors, count_rows, count_columns, cfg) + } + ColorizationPattern::Chess => { + colorize_diogonals(&self.colors, count_rows, count_columns, cfg) + } + } + } +} + +fn colorize_columns(colors: &[Color], count_columns: usize, cfg: &mut ColoredConfig) { + for (col, color) in (0..count_columns).zip(colors.iter().cycle()) { + colorize_entity(color, Entity::Column(col), cfg); + } +} + +fn colorize_rows(colors: &[Color], count_rows: usize, cfg: &mut ColoredConfig) { + for (row, color) in (0..count_rows).zip(colors.iter().cycle()) { + colorize_entity(color, Entity::Row(row), cfg); + } +} + +fn colorize_by_row( + colors: &[Color], + count_rows: usize, + count_columns: usize, + cfg: &mut ColoredConfig, +) { + let mut color_peek = colors.iter().cycle(); + for row in 0..count_rows { + for col in 0..count_columns { + let color = color_peek.next().unwrap(); + colorize_entity(color, Entity::Cell(row, col), cfg); + } + } +} + +fn colorize_by_column( + colors: &[Color], + count_rows: usize, + count_columns: usize, + cfg: &mut ColoredConfig, +) { + let mut color_peek = colors.iter().cycle(); + for col in 0..count_columns { + for row in 0..count_rows { + let color = color_peek.next().unwrap(); + colorize_entity(color, Entity::Cell(row, col), cfg); + } + } +} + +fn colorize_diogonals( + colors: &[Color], + count_rows: usize, + count_columns: usize, + cfg: &mut ColoredConfig, +) { + let mut color_peek = colors.iter().cycle(); + for mut row in 0..count_rows { + let color = color_peek.next().unwrap(); + for col in 0..count_columns { + colorize_entity(color, Entity::Cell(row, col), cfg); + + row += 1; + if row == count_rows { + break; + } + } + } + + let _ = color_peek.next().unwrap(); + + for mut col in 1..count_columns { + let color = color_peek.next().unwrap(); + for row in 0..count_rows { + colorize_entity(color, Entity::Cell(row, col), cfg); + + col += 1; + if col == count_columns { + break; + } + } + } +} + +fn colorize_entity(color: &Color, pos: Entity, cfg: &mut ColoredConfig) { + let ansi_color = AnsiColor::from(color.clone()); + let _ = cfg.set_color(pos, ansi_color.clone()); + cfg.set_justification_color(pos, Some(ansi_color.clone())); + cfg.set_padding_color( + pos, + Sides::new( + Some(ansi_color.clone()), + Some(ansi_color.clone()), + Some(ansi_color.clone()), + Some(ansi_color), + ), + ); +} + +/// A colorization of a target [`Object`]. +/// +/// Can be created by [`Colorization::exact`]. +#[derive(Debug, Clone)] +pub struct ExactColorization<O> { + colors: Vec<Color>, + target: O, +} + +impl<O> ExactColorization<O> { + fn new(colors: Vec<Color>, target: O) -> Self { + Self { colors, target } + } +} + +impl<R, D, O> TableOption<R, D, ColoredConfig> for ExactColorization<O> +where + O: Object<R>, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + if self.colors.is_empty() { + return; + } + + let mut color_peek = self.colors.iter().cycle(); + for pos in self.target.cells(records) { + let color = color_peek.next().unwrap(); + colorize_entity(color, pos, cfg); + } + } +} diff --git a/vendor/tabled/src/settings/themes/column_names.rs b/vendor/tabled/src/settings/themes/column_names.rs new file mode 100644 index 000000000..02848166d --- /dev/null +++ b/vendor/tabled/src/settings/themes/column_names.rs @@ -0,0 +1,355 @@ +use crate::{ + grid::{ + config::{AlignmentHorizontal, ColoredConfig}, + dimension::{CompleteDimensionVecRecords, Dimension, Estimate}, + records::{ + vec_records::{CellInfo, VecRecords}, + ExactRecords, PeekableRecords, Records, Resizable, + }, + util::string::string_width, + }, + settings::{ + style::{BorderText, Offset}, + Color, TableOption, + }, +}; + +/// [`ColumnNames`] sets strings on horizontal lines for the columns. +/// +/// Notice that using a [`Default`] would reuse a names from the first row. +/// +/// # Examples +/// +/// ``` +/// use std::iter::FromIterator; +/// use tabled::{Table, settings::themes::ColumnNames}; +/// +/// let data = vec![ +/// vec!["Hello", "World"], +/// vec!["Hello", "World"], +/// ]; +/// +/// let mut table = Table::from_iter(data); +/// table.with(ColumnNames::new(["head1", "head2"]).set_offset(3).set_line(2)); +/// +/// assert_eq!( +/// table.to_string(), +/// "+--------+--------+\n\ +/// | Hello | World |\n\ +/// +--------+--------+\n\ +/// | Hello | World |\n\ +/// +---head1+---head2+" +/// ); +/// ``` +/// +/// [`Default`] usage. +/// +/// ``` +/// use std::iter::FromIterator; +/// use tabled::{Table, settings::themes::ColumnNames}; +/// +/// let data = vec![ +/// vec!["Hello", "World"], +/// vec!["Hello", "World"], +/// ]; +/// +/// let mut table = Table::from_iter(data); +/// table.with(ColumnNames::default()); +/// +/// assert_eq!( +/// table.to_string(), +/// "+Hello--+World--+\n\ +/// | Hello | World |\n\ +/// +-------+-------+" +/// ); +/// ``` +#[derive(Debug, Clone)] +pub struct ColumnNames { + names: Option<Vec<String>>, + colors: Vec<Option<Color>>, + offset: usize, + line: usize, + alignment: AlignmentHorizontal, +} + +impl Default for ColumnNames { + fn default() -> Self { + Self { + names: Default::default(), + colors: Default::default(), + offset: Default::default(), + line: Default::default(), + alignment: AlignmentHorizontal::Left, + } + } +} + +impl ColumnNames { + /// Creates a [`ColumnNames`] with a given names. + /// + /// Using a [`Default`] would reuse a names from the first row. + /// + /// # Example + /// + /// ``` + /// use std::iter::FromIterator; + /// use tabled::{Table, settings::themes::ColumnNames}; + /// + /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); + /// table.with(ColumnNames::new(["head1", "head2"])); + /// + /// assert_eq!( + /// table.to_string(), + /// "+head1--+head2--+\n\ + /// | Hello | World |\n\ + /// +-------+-------+" + /// ); + /// ``` + pub fn new<I>(names: I) -> Self + where + I: IntoIterator, + I::Item: Into<String>, + { + let names = names.into_iter().map(Into::into).collect::<Vec<_>>(); + Self { + names: Some(names), + colors: Vec::new(), + offset: 0, + line: 0, + alignment: AlignmentHorizontal::Left, + } + } + + /// Set color for the column names. + /// + /// By default there's no colors. + /// + /// # Example + /// + /// ``` + /// use std::iter::FromIterator; + /// use tabled::Table; + /// use tabled::settings::{Color, themes::ColumnNames}; + /// + /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); + /// table.with(ColumnNames::new(["head1", "head2"]).set_colors([Color::FG_RED])); + /// + /// assert_eq!( + /// table.to_string(), + /// "+\u{1b}[31mh\u{1b}[39m\u{1b}[31me\u{1b}[39m\u{1b}[31ma\u{1b}[39m\u{1b}[31md\u{1b}[39m\u{1b}[31m1\u{1b}[39m--+head2--+\n\ + /// | Hello | World |\n\ + /// +-------+-------+" + /// ); + /// ``` + pub fn set_colors<I>(self, colors: I) -> Self + where + I: IntoIterator, + I::Item: Into<Option<Color>>, + { + let colors = colors.into_iter().map(Into::into).collect::<Vec<_>>(); + Self { + names: self.names, + offset: self.offset, + line: self.line, + alignment: self.alignment, + colors, + } + } + + /// Set a left offset after which the names will be used. + /// + /// By default there's no offset. + /// + /// # Example + /// + /// ``` + /// use std::iter::FromIterator; + /// use tabled::{Table, settings::themes::ColumnNames}; + /// + /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); + /// table.with(ColumnNames::new(["head1", "head2"]).set_offset(1)); + /// + /// assert_eq!( + /// table.to_string(), + /// "+-head1-+-head2-+\n\ + /// | Hello | World |\n\ + /// +-------+-------+" + /// ); + /// ``` + pub fn set_offset(self, i: usize) -> Self { + Self { + names: self.names, + colors: self.colors, + line: self.line, + alignment: self.alignment, + offset: i, + } + } + + /// Set a horizontal line the names will be applied to. + /// + /// The default value is 0 (the top horizontal line). + /// + /// # Example + /// + /// ``` + /// use std::iter::FromIterator; + /// use tabled::{Table, settings::themes::ColumnNames}; + /// + /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); + /// table.with(ColumnNames::new(["head1", "head2"]).set_line(1)); + /// + /// assert_eq!( + /// table.to_string(), + /// "+-------+-------+\n\ + /// | Hello | World |\n\ + /// +head1--+head2--+" + /// ); + /// ``` + pub fn set_line(self, i: usize) -> Self { + Self { + names: self.names, + colors: self.colors, + offset: self.offset, + alignment: self.alignment, + line: i, + } + } + + /// Set an alignment for the names. + /// + /// By default it's left aligned. + /// + /// # Example + /// + /// ``` + /// use std::iter::FromIterator; + /// use tabled::{ + /// Table, + /// settings::themes::ColumnNames, + /// grid::config::AlignmentHorizontal, + /// }; + /// + /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); + /// table.with(ColumnNames::new(["head1", "head2"]).set_alignment(AlignmentHorizontal::Right)); + /// + /// assert_eq!( + /// table.to_string(), + /// "+--head1+--head2+\n\ + /// | Hello | World |\n\ + /// +-------+-------+" + /// ); + /// ``` + pub fn set_alignment(self, alignment: AlignmentHorizontal) -> Self { + Self { + names: self.names, + colors: self.colors, + offset: self.offset, + line: self.line, + alignment, + } + } +} + +impl TableOption<VecRecords<CellInfo<String>>, CompleteDimensionVecRecords<'static>, ColoredConfig> + for ColumnNames +{ + fn change( + self, + records: &mut VecRecords<CellInfo<String>>, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + let names = match self.names { + Some(names) => names, + None => { + if records.count_rows() == 0 || records.count_columns() == 0 { + return; + } + + let names = (0..records.count_columns()) + .map(|column| records.get_text((0, column))) + .map(ToString::to_string) + .collect::<Vec<_>>(); + + records.remove_row(0); + + names + } + }; + + let names = names.iter().map(|name| name.lines().next().unwrap_or("")); + + dims.estimate(&*records, cfg); + + let mut widths = (0..records.count_columns()) + .map(|column| dims.get_width(column)) + .collect::<Vec<_>>(); + + let names = names.take(widths.len()); + + let offset = self.offset; + widths + .iter_mut() + .zip(names.clone()) + .for_each(|(width, text)| { + let name_width = string_width(text) + offset; + *width = std::cmp::max(name_width, *width); + }); + + let _ = dims.set_widths(widths.clone()); + + let mut total_width = 0; + for (i, (width, name)) in widths.iter().zip(names).enumerate() { + let color = get_color(&self.colors, i); + let offset = total_width + 1; + let btext = get_border_text( + name, + offset, + *width, + self.alignment, + self.offset, + self.line, + color, + ); + btext.change(records, cfg, dims); + + total_width += width + 1; + } + } +} + +fn get_border_text( + text: &str, + offset: usize, + available: usize, + alignment: AlignmentHorizontal, + alignment_offset: usize, + line: usize, + color: Option<&Color>, +) -> BorderText<usize> { + let name = text.to_string(); + let left_indent = get_indent(text, alignment, alignment_offset, available); + let left_offset = Offset::Begin(offset + left_indent); + let mut btext = BorderText::new(name).horizontal(line).offset(left_offset); + if let Some(color) = color { + btext = btext.color(color.clone()); + } + + btext +} + +fn get_color(colors: &[Option<Color>], i: usize) -> Option<&Color> { + colors.get(i).and_then(|color| match color { + Some(color) => Some(color), + None => None, + }) +} + +fn get_indent(text: &str, align: AlignmentHorizontal, offset: usize, available: usize) -> usize { + match align { + AlignmentHorizontal::Left => offset, + AlignmentHorizontal::Right => available - string_width(text) - offset, + AlignmentHorizontal::Center => (available - string_width(text)) / 2, + } +} diff --git a/vendor/tabled/src/settings/themes/mod.rs b/vendor/tabled/src/settings/themes/mod.rs new file mode 100644 index 000000000..75cf458cc --- /dev/null +++ b/vendor/tabled/src/settings/themes/mod.rs @@ -0,0 +1,9 @@ +//! The module contains a varieity of configurations of table, which often +//! changes not a single setting. +//! As such they are making relatively big changes to the configuration. + +mod colorization; +mod column_names; + +pub use colorization::{Colorization, ExactColorization}; +pub use column_names::ColumnNames; diff --git a/vendor/tabled/src/settings/width/justify.rs b/vendor/tabled/src/settings/width/justify.rs new file mode 100644 index 000000000..03b2afe0d --- /dev/null +++ b/vendor/tabled/src/settings/width/justify.rs @@ -0,0 +1,96 @@ +//! This module contains [`Justify`] structure, used to set an exact width to each column. + +use crate::{ + grid::config::ColoredConfig, + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + settings::{ + measurement::{Max, Measurement, Min}, + CellOption, TableOption, Width, + }, +}; + +/// Justify sets all columns widths to the set value. +/// +/// Be aware that it doesn't consider padding. +/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. +/// +/// ## Examples +/// +/// ``` +/// use tabled::{Table, settings::{Width, Style, object::Segment, Padding, Modify}}; +/// +/// let data = ["Hello", "World", "!"]; +/// +/// let table = Table::new(&data) +/// .with(Style::markdown()) +/// .with(Modify::new(Segment::all()).with(Padding::zero())) +/// .with(Width::justify(3)); +/// ``` +/// +/// [`Max`] usage to justify by a max column width. +/// +/// ``` +/// use tabled::{Table, settings::{width::Justify, Style}}; +/// +/// let data = ["Hello", "World", "!"]; +/// +/// let table = Table::new(&data) +/// .with(Style::markdown()) +/// .with(Justify::max()); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Justify<W> { + width: W, +} + +impl<W> Justify<W> +where + W: Measurement<Width>, +{ + /// Creates a new [`Justify`] instance. + /// + /// Be aware that [`Padding`] is not considered when comparing the width. + /// + /// [`Padding`]: crate::settings::Padding + pub fn new(width: W) -> Self { + Self { width } + } +} + +impl Justify<Max> { + /// Creates a new Justify instance with a Max width used as a value. + pub fn max() -> Self { + Self { width: Max } + } +} + +impl Justify<Min> { + /// Creates a new Justify instance with a Min width used as a value. + pub fn min() -> Self { + Self { width: Min } + } +} + +impl<R, D, W> TableOption<R, D, ColoredConfig> for Justify<W> +where + W: Measurement<Width>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let width = self.width.measure(&*records, cfg); + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + for row in 0..count_rows { + for col in 0..count_columns { + let pos = (row, col).into(); + CellOption::change(Width::increase(width), records, cfg, pos); + CellOption::change(Width::truncate(width), records, cfg, pos); + } + } + } +} diff --git a/vendor/tabled/src/settings/width/min_width.rs b/vendor/tabled/src/settings/width/min_width.rs new file mode 100644 index 000000000..afb9ae302 --- /dev/null +++ b/vendor/tabled/src/settings/width/min_width.rs @@ -0,0 +1,205 @@ +//! This module contains [`MinWidth`] structure, used to increase width of a [`Table`]s or a cell on a [`Table`]. +//! +//! [`Table`]: crate::Table + +use std::marker::PhantomData; + +use crate::{ + grid::config::ColoredConfig, + grid::config::Entity, + grid::dimension::CompleteDimensionVecRecords, + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + grid::util::string::{get_lines, string_width_multiline}, + settings::{ + measurement::Measurement, + peaker::{Peaker, PriorityNone}, + CellOption, TableOption, Width, + }, +}; + +use super::util::get_table_widths_with_total; + +/// [`MinWidth`] changes a content in case if it's length is lower then the boundary. +/// +/// It can be applied to a whole table. +/// +/// It does nothing in case if the content's length is bigger then the boundary. +/// +/// Be aware that further changes of the table may cause the width being not set. +/// For example applying [`Padding`] after applying [`MinWidth`] will make the former have no affect. +/// (You should use [`Padding`] first). +/// +/// Be aware that it doesn't consider padding. +/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. +/// +/// ## Examples +/// +/// Cell change +/// +/// ``` +/// use tabled::{Table, settings::{object::Segment, Width, Style, Modify}}; +/// +/// let data = ["Hello", "World", "!"]; +/// +/// let table = Table::new(&data) +/// .with(Style::markdown()) +/// .with(Modify::new(Segment::all()).with(Width::increase(10))); +/// ``` +/// Table change +/// +/// ``` +/// use tabled::{Table, settings::Width}; +/// +/// let table = Table::new(&["Hello World!"]).with(Width::increase(5)); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct MinWidth<W = usize, P = PriorityNone> { + width: W, + fill: char, + _priority: PhantomData<P>, +} + +impl<W> MinWidth<W> +where + W: Measurement<Width>, +{ + /// Creates a new instance of [`MinWidth`]. + pub fn new(width: W) -> Self { + Self { + width, + fill: ' ', + _priority: PhantomData, + } + } +} + +impl<W, P> MinWidth<W, P> { + /// Set's a fill character which will be used to fill the space + /// when increasing the length of the string to the set boundary. + /// + /// Used only if chaning cells. + pub fn fill_with(mut self, c: char) -> Self { + self.fill = c; + self + } + + /// Priority defines the logic by which a increase of width will be applied when is done for the whole table. + /// + /// - [`PriorityNone`] which inc the columns one after another. + /// - [`PriorityMax`] inc the biggest columns first. + /// - [`PriorityMin`] inc the lowest columns first. + /// + /// [`PriorityMax`]: crate::settings::peaker::PriorityMax + /// [`PriorityMin`]: crate::settings::peaker::PriorityMin + pub fn priority<PP: Peaker>(self) -> MinWidth<W, PP> { + MinWidth { + fill: self.fill, + width: self.width, + _priority: PhantomData, + } + } +} + +impl<W, R> CellOption<R, ColoredConfig> for MinWidth<W> +where + W: Measurement<Width>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let width = self.width.measure(&*records, cfg); + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + for pos in entity.iter(count_rows, count_columns) { + let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns; + if !is_valid_pos { + continue; + } + + let cell = records.get_text(pos); + let cell_width = string_width_multiline(cell); + if cell_width >= width { + continue; + } + + let content = increase_width(cell, width, self.fill); + records.set(pos, content); + } + } +} + +impl<W, P, R> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> for MinWidth<W, P> +where + W: Measurement<Width>, + P: Peaker, + R: Records + ExactRecords + PeekableRecords, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + if records.count_rows() == 0 || records.count_columns() == 0 { + return; + } + + let nessary_width = self.width.measure(&*records, cfg); + + let (widths, total_width) = get_table_widths_with_total(&*records, cfg); + if total_width >= nessary_width { + return; + } + + let widths = get_increase_list(widths, nessary_width, total_width, P::create()); + let _ = dims.set_widths(widths); + } +} + +fn get_increase_list<F>( + mut widths: Vec<usize>, + need: usize, + mut current: usize, + mut peaker: F, +) -> Vec<usize> +where + F: Peaker, +{ + while need != current { + let col = match peaker.peak(&[], &widths) { + Some(col) => col, + None => break, + }; + + widths[col] += 1; + current += 1; + } + + widths +} + +fn increase_width(s: &str, width: usize, fill_with: char) -> String { + use crate::grid::util::string::string_width; + use std::{borrow::Cow, iter::repeat}; + + get_lines(s) + .map(|line| { + let length = string_width(&line); + + if length < width { + let mut line = line.into_owned(); + let remain = width - length; + line.extend(repeat(fill_with).take(remain)); + Cow::Owned(line) + } else { + line + } + }) + .collect::<Vec<_>>() + .join("\n") +} diff --git a/vendor/tabled/src/settings/width/mod.rs b/vendor/tabled/src/settings/width/mod.rs new file mode 100644 index 000000000..c1202f70f --- /dev/null +++ b/vendor/tabled/src/settings/width/mod.rs @@ -0,0 +1,163 @@ +//! This module contains object which can be used to limit a cell to a given width: +//! +//! - [`Truncate`] cuts a cell content to limit width. +//! - [`Wrap`] split the content via new lines in order to fit max width. +//! - [`Justify`] sets columns width to the same value. +//! +//! To set a a table width, a combination of [`Width::truncate`] or [`Width::wrap`] and [`Width::increase`] can be used. +//! +//! ## Example +//! +//! ``` +//! use tabled::{Table, settings::Width}; +//! +//! let table = Table::new(&["Hello World!"]) +//! .with(Width::wrap(7)) +//! .with(Width::increase(7)) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "+-----+\n", +//! "| &st |\n", +//! "| r |\n", +//! "+-----+\n", +//! "| Hel |\n", +//! "| lo |\n", +//! "| Wor |\n", +//! "| ld! |\n", +//! "+-----+", +//! ) +//! ); +//! ``` + +mod justify; +mod min_width; +mod truncate; +mod util; +mod width_list; +mod wrap; + +use crate::settings::measurement::Measurement; + +pub use self::{ + justify::Justify, + min_width::MinWidth, + truncate::{SuffixLimit, Truncate}, + width_list::WidthList, + wrap::Wrap, +}; + +/// Width allows you to set a min and max width of an object on a [`Table`] +/// using different strategies. +/// +/// It also allows you to set a min and max width for a whole table. +/// +/// You can apply a min and max strategy at the same time with the same value, +/// the value will be a total table width. +/// +/// It is an abstract factory. +/// +/// Beware that borders are not removed when you set a size value to very small. +/// For example if you set size to 0 the table still be rendered but with all content removed. +/// +/// Also be aware that it doesn't changes [`Padding`] settings nor it considers them. +/// +/// The function is color aware if a `color` feature is on. +/// +/// ## Examples +/// +/// ### Cell change +/// +/// ``` +/// use tabled::{Table, settings::{object::Segment, Width, Style, Modify}}; +/// +/// let data = ["Hello", "World", "!"]; +/// +/// let table = Table::new(&data) +/// .with(Style::markdown()) +/// .with(Modify::new(Segment::all()).with(Width::truncate(3).suffix("..."))); +/// ``` +/// +/// ### Table change +/// +/// ``` +/// use tabled::{Table, settings::Width}; +/// +/// let table = Table::new(&["Hello World!"]).with(Width::wrap(5)); +/// ``` +/// +/// ### Total width +/// +/// ``` +/// use tabled::{Table, settings::Width}; +/// +/// let table = Table::new(&["Hello World!"]) +/// .with(Width::wrap(5)) +/// .with(Width::increase(5)); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Width; + +impl Width { + /// Returns a [`Wrap`] structure. + pub fn wrap<W: Measurement<Width>>(width: W) -> Wrap<W> { + Wrap::new(width) + } + + /// Returns a [`Truncate`] structure. + pub fn truncate<W: Measurement<Width>>(width: W) -> Truncate<'static, W> { + Truncate::new(width) + } + + /// Returns a [`MinWidth`] structure. + pub fn increase<W: Measurement<Width>>(width: W) -> MinWidth<W> { + MinWidth::new(width) + } + + /// Returns a [`Justify`] structure. + pub fn justify<W: Measurement<Width>>(width: W) -> Justify<W> { + Justify::new(width) + } + + /// Create [`WidthList`] to set a table width to a constant list of column widths. + /// + /// Notice if you provide a list with `.len()` smaller than `Table::count_columns` then it will have no affect. + /// + /// Also notice that you must provide values bigger than or equal to a real content width, otherwise it may panic. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::Width}; + /// + /// let data = vec![ + /// ("Some\ndata", "here", "and here"), + /// ("Some\ndata on a next", "line", "right here"), + /// ]; + /// + /// let table = Table::new(data) + /// .with(Width::list([20, 10, 12])) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+--------------------+----------+------------+\n\ + /// | &str | &str | &str |\n\ + /// +--------------------+----------+------------+\n\ + /// | Some | here | and here |\n\ + /// | data | | |\n\ + /// +--------------------+----------+------------+\n\ + /// | Some | line | right here |\n\ + /// | data on a next | | |\n\ + /// +--------------------+----------+------------+" + /// ) + /// ``` + pub fn list<I: IntoIterator<Item = usize>>(rows: I) -> WidthList { + WidthList::new(rows.into_iter().collect()) + } +} diff --git a/vendor/tabled/src/settings/width/truncate.rs b/vendor/tabled/src/settings/width/truncate.rs new file mode 100644 index 000000000..c7336f037 --- /dev/null +++ b/vendor/tabled/src/settings/width/truncate.rs @@ -0,0 +1,505 @@ +//! This module contains [`Truncate`] structure, used to decrease width of a [`Table`]s or a cell on a [`Table`] by truncating the width. +//! +//! [`Table`]: crate::Table + +use std::{borrow::Cow, iter, marker::PhantomData}; + +use crate::{ + grid::{ + config::{ColoredConfig, SpannedConfig}, + dimension::CompleteDimensionVecRecords, + records::{EmptyRecords, ExactRecords, PeekableRecords, Records, RecordsMut}, + util::string::{string_width, string_width_multiline}, + }, + settings::{ + measurement::Measurement, + peaker::{Peaker, PriorityNone}, + CellOption, TableOption, Width, + }, +}; + +use super::util::{cut_str, get_table_widths, get_table_widths_with_total}; + +/// Truncate cut the string to a given width if its length exceeds it. +/// Otherwise keeps the content of a cell untouched. +/// +/// The function is color aware if a `color` feature is on. +/// +/// Be aware that it doesn't consider padding. +/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. +/// +/// ## Example +/// +/// ``` +/// use tabled::{Table, settings::{object::Segment, Width, Modify}}; +/// +/// let table = Table::new(&["Hello World!"]) +/// .with(Modify::new(Segment::all()).with(Width::truncate(3))); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Truncate<'a, W = usize, P = PriorityNone> { + width: W, + suffix: Option<TruncateSuffix<'a>>, + multiline: bool, + _priority: PhantomData<P>, +} +#[cfg(feature = "color")] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +struct TruncateSuffix<'a> { + text: Cow<'a, str>, + limit: SuffixLimit, + try_color: bool, +} + +#[cfg(not(feature = "color"))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +struct TruncateSuffix<'a> { + text: Cow<'a, str>, + limit: SuffixLimit, +} + +impl Default for TruncateSuffix<'_> { + fn default() -> Self { + Self { + text: Cow::default(), + limit: SuffixLimit::Cut, + #[cfg(feature = "color")] + try_color: false, + } + } +} + +/// A suffix limit settings. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum SuffixLimit { + /// Cut the suffix. + Cut, + /// Don't show the suffix. + Ignore, + /// Use a string with n chars instead. + Replace(char), +} + +impl<W> Truncate<'static, W> +where + W: Measurement<Width>, +{ + /// Creates a [`Truncate`] object + pub fn new(width: W) -> Truncate<'static, W> { + Self { + width, + multiline: false, + suffix: None, + _priority: PhantomData, + } + } +} + +impl<'a, W, P> Truncate<'a, W, P> { + /// Sets a suffix which will be appended to a resultant string. + /// + /// The suffix is used in 3 circumstances: + /// 1. If original string is *bigger* than the suffix. + /// We cut more of the original string and append the suffix. + /// 2. If suffix is bigger than the original string. + /// We cut the suffix to fit in the width by default. + /// But you can peak the behaviour by using [`Truncate::suffix_limit`] + pub fn suffix<S: Into<Cow<'a, str>>>(self, suffix: S) -> Truncate<'a, W, P> { + let mut suff = self.suffix.unwrap_or_default(); + suff.text = suffix.into(); + + Truncate { + width: self.width, + multiline: self.multiline, + suffix: Some(suff), + _priority: PhantomData, + } + } + + /// Sets a suffix limit, which is used when the suffix is too big to be used. + pub fn suffix_limit(self, limit: SuffixLimit) -> Truncate<'a, W, P> { + let mut suff = self.suffix.unwrap_or_default(); + suff.limit = limit; + + Truncate { + width: self.width, + multiline: self.multiline, + suffix: Some(suff), + _priority: PhantomData, + } + } + + /// Use trancate logic per line, not as a string as a whole. + pub fn multiline(self) -> Truncate<'a, W, P> { + Truncate { + width: self.width, + multiline: true, + suffix: self.suffix, + _priority: self._priority, + } + } + + #[cfg(feature = "color")] + /// Sets a optional logic to try to colorize a suffix. + pub fn suffix_try_color(self, color: bool) -> Truncate<'a, W, P> { + let mut suff = self.suffix.unwrap_or_default(); + suff.try_color = color; + + Truncate { + width: self.width, + multiline: self.multiline, + suffix: Some(suff), + _priority: PhantomData, + } + } +} + +impl<'a, W, P> Truncate<'a, W, P> { + /// Priority defines the logic by which a truncate will be applied when is done for the whole table. + /// + /// - [`PriorityNone`] which cuts the columns one after another. + /// - [`PriorityMax`] cuts the biggest columns first. + /// - [`PriorityMin`] cuts the lowest columns first. + /// + /// [`PriorityMax`]: crate::settings::peaker::PriorityMax + /// [`PriorityMin`]: crate::settings::peaker::PriorityMin + pub fn priority<PP: Peaker>(self) -> Truncate<'a, W, PP> { + Truncate { + width: self.width, + multiline: self.multiline, + suffix: self.suffix, + _priority: PhantomData, + } + } +} + +impl Truncate<'_, (), ()> { + /// Truncate a given string + pub fn truncate_text(text: &str, width: usize) -> Cow<'_, str> { + truncate_text(text, width, "", false) + } +} + +impl<W, P, R> CellOption<R, ColoredConfig> for Truncate<'_, W, P> +where + W: Measurement<Width>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: papergrid::config::Entity) { + let available = self.width.measure(&*records, cfg); + + let mut width = available; + let mut suffix = Cow::Borrowed(""); + + if let Some(x) = self.suffix.as_ref() { + let (cutted_suffix, rest_width) = make_suffix(x, width); + suffix = cutted_suffix; + width = rest_width; + }; + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let colorize = need_suffix_color_preservation(&self.suffix); + + for pos in entity.iter(count_rows, count_columns) { + let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns; + if !is_valid_pos { + continue; + } + + let text = records.get_text(pos); + + let cell_width = string_width_multiline(text); + if available >= cell_width { + continue; + } + + let text = + truncate_multiline(text, &suffix, width, available, colorize, self.multiline); + + records.set(pos, text.into_owned()); + } + } +} + +fn truncate_multiline<'a>( + text: &'a str, + suffix: &'a str, + width: usize, + twidth: usize, + suffix_color: bool, + multiline: bool, +) -> Cow<'a, str> { + if multiline { + let mut buf = String::new(); + for (i, line) in crate::grid::util::string::get_lines(text).enumerate() { + if i != 0 { + buf.push('\n'); + } + + let line = make_text_truncated(&line, suffix, width, twidth, suffix_color); + buf.push_str(&line); + } + + Cow::Owned(buf) + } else { + make_text_truncated(text, suffix, width, twidth, suffix_color) + } +} + +fn make_text_truncated<'a>( + text: &'a str, + suffix: &'a str, + width: usize, + twidth: usize, + suffix_color: bool, +) -> Cow<'a, str> { + if width == 0 { + if twidth == 0 { + Cow::Borrowed("") + } else { + Cow::Borrowed(suffix) + } + } else { + truncate_text(text, width, suffix, suffix_color) + } +} + +fn need_suffix_color_preservation(_suffix: &Option<TruncateSuffix<'_>>) -> bool { + #[cfg(not(feature = "color"))] + { + false + } + #[cfg(feature = "color")] + { + _suffix.as_ref().map_or(false, |s| s.try_color) + } +} + +fn make_suffix<'a>(suffix: &'a TruncateSuffix<'_>, width: usize) -> (Cow<'a, str>, usize) { + let suffix_length = string_width(&suffix.text); + if width > suffix_length { + return (Cow::Borrowed(suffix.text.as_ref()), width - suffix_length); + } + + match suffix.limit { + SuffixLimit::Ignore => (Cow::Borrowed(""), width), + SuffixLimit::Cut => { + let suffix = cut_str(&suffix.text, width); + (suffix, 0) + } + SuffixLimit::Replace(c) => { + let suffix = Cow::Owned(iter::repeat(c).take(width).collect()); + (suffix, 0) + } + } +} + +impl<W, P, R> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> + for Truncate<'_, W, P> +where + W: Measurement<Width>, + P: Peaker, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + if records.count_rows() == 0 || records.count_columns() == 0 { + return; + } + + let width = self.width.measure(&*records, cfg); + let (widths, total) = get_table_widths_with_total(&*records, cfg); + if total <= width { + return; + } + + let suffix = self.suffix.as_ref().map(|s| TruncateSuffix { + text: Cow::Borrowed(&s.text), + limit: s.limit, + #[cfg(feature = "color")] + try_color: s.try_color, + }); + + let priority = P::create(); + let multiline = self.multiline; + let widths = truncate_total_width( + records, cfg, widths, total, width, priority, suffix, multiline, + ); + + let _ = dims.set_widths(widths); + } +} + +#[allow(clippy::too_many_arguments)] +fn truncate_total_width<P, R>( + records: &mut R, + cfg: &mut ColoredConfig, + mut widths: Vec<usize>, + total: usize, + width: usize, + priority: P, + suffix: Option<TruncateSuffix<'_>>, + multiline: bool, +) -> Vec<usize> +where + for<'a> &'a R: Records, + P: Peaker, + R: Records + PeekableRecords + ExactRecords + RecordsMut<String>, +{ + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let min_widths = get_table_widths(EmptyRecords::new(count_rows, count_columns), cfg); + + decrease_widths(&mut widths, &min_widths, total, width, priority); + + let points = get_decrease_cell_list(cfg, &widths, &min_widths, (count_rows, count_columns)); + + for ((row, col), width) in points { + let mut truncate = Truncate::new(width); + truncate.suffix = suffix.clone(); + truncate.multiline = multiline; + CellOption::change(truncate, records, cfg, (row, col).into()); + } + + widths +} + +fn truncate_text<'a>( + text: &'a str, + width: usize, + suffix: &str, + _suffix_color: bool, +) -> Cow<'a, str> { + let content = cut_str(text, width); + if suffix.is_empty() { + return content; + } + + #[cfg(feature = "color")] + { + if _suffix_color { + if let Some(block) = ansi_str::get_blocks(text).last() { + if block.has_ansi() { + let style = block.style(); + Cow::Owned(format!( + "{}{}{}{}", + content, + style.start(), + suffix, + style.end() + )) + } else { + let mut content = content.into_owned(); + content.push_str(suffix); + Cow::Owned(content) + } + } else { + let mut content = content.into_owned(); + content.push_str(suffix); + Cow::Owned(content) + } + } else { + let mut content = content.into_owned(); + content.push_str(suffix); + Cow::Owned(content) + } + } + + #[cfg(not(feature = "color"))] + { + let mut content = content.into_owned(); + content.push_str(suffix); + Cow::Owned(content) + } +} + +fn get_decrease_cell_list( + cfg: &SpannedConfig, + widths: &[usize], + min_widths: &[usize], + shape: (usize, usize), +) -> Vec<((usize, usize), usize)> { + let mut points = Vec::new(); + (0..shape.1).for_each(|col| { + (0..shape.0) + .filter(|&row| cfg.is_cell_visible((row, col))) + .for_each(|row| { + let (width, width_min) = match cfg.get_column_span((row, col)) { + Some(span) => { + let width = (col..col + span).map(|i| widths[i]).sum::<usize>(); + let min_width = (col..col + span).map(|i| min_widths[i]).sum::<usize>(); + let count_borders = count_borders(cfg, col, col + span, shape.1); + (width + count_borders, min_width + count_borders) + } + None => (widths[col], min_widths[col]), + }; + + if width >= width_min { + let padding = cfg.get_padding((row, col).into()); + let width = width.saturating_sub(padding.left.size + padding.right.size); + + points.push(((row, col), width)); + } + }); + }); + + points +} + +fn decrease_widths<F>( + widths: &mut [usize], + min_widths: &[usize], + total_width: usize, + mut width: usize, + mut peeaker: F, +) where + F: Peaker, +{ + let mut empty_list = 0; + for col in 0..widths.len() { + if widths[col] == 0 || widths[col] <= min_widths[col] { + empty_list += 1; + } + } + + while width != total_width { + if empty_list == widths.len() { + break; + } + + let col = match peeaker.peak(min_widths, widths) { + Some(col) => col, + None => break, + }; + + if widths[col] == 0 || widths[col] <= min_widths[col] { + continue; + } + + widths[col] -= 1; + + if widths[col] == 0 || widths[col] <= min_widths[col] { + empty_list += 1; + } + + width += 1; + } +} + +fn count_borders(cfg: &SpannedConfig, start: usize, end: usize, count_columns: usize) -> usize { + (start..end) + .skip(1) + .filter(|&i| cfg.has_vertical(i, count_columns)) + .count() +} diff --git a/vendor/tabled/src/settings/width/util.rs b/vendor/tabled/src/settings/width/util.rs new file mode 100644 index 000000000..92cc48c41 --- /dev/null +++ b/vendor/tabled/src/settings/width/util.rs @@ -0,0 +1,265 @@ +use std::borrow::Cow; + +use crate::{ + grid::config::SpannedConfig, grid::dimension::SpannedGridDimension, grid::records::Records, +}; + +pub(crate) fn get_table_widths<R: Records>(records: R, cfg: &SpannedConfig) -> Vec<usize> { + SpannedGridDimension::width(records, cfg) +} + +pub(crate) fn get_table_widths_with_total<R: Records>( + records: R, + cfg: &SpannedConfig, +) -> (Vec<usize>, usize) { + let widths = SpannedGridDimension::width(records, cfg); + let total_width = get_table_total_width(&widths, cfg); + (widths, total_width) +} + +fn get_table_total_width(list: &[usize], cfg: &SpannedConfig) -> usize { + let margin = cfg.get_margin(); + list.iter().sum::<usize>() + + cfg.count_vertical(list.len()) + + margin.left.size + + margin.right.size +} + +/// The function cuts the string to a specific width. +/// +/// BE AWARE: width is expected to be in bytes. +pub(crate) fn cut_str(s: &str, width: usize) -> Cow<'_, str> { + #[cfg(feature = "color")] + { + const REPLACEMENT: char = '\u{FFFD}'; + + let stripped = ansi_str::AnsiStr::ansi_strip(s); + let (length, count_unknowns, _) = split_at_pos(&stripped, width); + + let mut buf = ansi_str::AnsiStr::ansi_cut(s, ..length); + if count_unknowns > 0 { + let mut b = buf.into_owned(); + b.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); + buf = Cow::Owned(b); + } + + buf + } + + #[cfg(not(feature = "color"))] + { + cut_str_basic(s, width) + } +} + +/// The function cuts the string to a specific width. +/// +/// BE AWARE: width is expected to be in bytes. +#[cfg(not(feature = "color"))] +pub(crate) 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) +} + +/// The function splits a string in the position and +/// returns a exact number of bytes before the position and in case of a split in an unicode grapheme +/// a width of a character which was tried to be splited in. +/// +/// BE AWARE: pos is expected to be in bytes. +pub(crate) 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_default(); + + // 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) +} + +/// Strip OSC codes from `s`. If `s` is a single OSC8 hyperlink, with no other text, then return +/// (s_with_all_hyperlinks_removed, Some(url)). If `s` does not meet this description, then return +/// (s_with_all_hyperlinks_removed, None). Any ANSI color sequences in `s` will be retained. See +/// <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda> +/// +/// The function is based on Dan Davison <https://github.com/dandavison> delta <https://github.com/dandavison/delta> ansi library. +#[cfg(feature = "color")] +pub(crate) fn strip_osc(text: &str) -> (String, Option<String>) { + #[derive(Debug)] + enum ExtractOsc8HyperlinkState { + ExpectOsc8Url, + ExpectFirstText, + ExpectMoreTextOrTerminator, + SeenOneHyperlink, + WillNotReturnUrl, + } + + use ExtractOsc8HyperlinkState::*; + + let mut url = None; + let mut state = ExpectOsc8Url; + let mut buf = String::with_capacity(text.len()); + + for el in ansitok::parse_ansi(text) { + match el.kind() { + ansitok::ElementKind::Osc => match state { + ExpectOsc8Url => { + url = Some(&text[el.start()..el.end()]); + state = ExpectFirstText; + } + ExpectMoreTextOrTerminator => state = SeenOneHyperlink, + _ => state = WillNotReturnUrl, + }, + ansitok::ElementKind::Sgr => buf.push_str(&text[el.start()..el.end()]), + ansitok::ElementKind::Csi => buf.push_str(&text[el.start()..el.end()]), + ansitok::ElementKind::Esc => {} + ansitok::ElementKind::Text => { + buf.push_str(&text[el.start()..el.end()]); + match state { + ExpectFirstText => state = ExpectMoreTextOrTerminator, + ExpectMoreTextOrTerminator => {} + _ => state = WillNotReturnUrl, + } + } + } + } + + match state { + WillNotReturnUrl => (buf, None), + _ => { + let url = url.and_then(|s| { + s.strip_prefix("\x1b]8;;") + .and_then(|s| s.strip_suffix('\x1b')) + }); + if let Some(url) = url { + (buf, Some(url.to_string())) + } else { + (buf, None) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::grid::util::string::string_width; + + #[cfg(feature = "color")] + use owo_colors::{colors::Yellow, OwoColorize}; + + #[test] + fn strip_test() { + assert_eq!(cut_str("123456", 0), ""); + assert_eq!(cut_str("123456", 3), "123"); + assert_eq!(cut_str("123456", 10), "123456"); + + assert_eq!(cut_str("a week ago", 4), "a we"); + + assert_eq!(cut_str("😳😳😳😳😳", 0), ""); + assert_eq!(cut_str("😳😳😳😳😳", 3), "😳�"); + assert_eq!(cut_str("😳😳😳😳😳", 4), "😳😳"); + assert_eq!(cut_str("😳😳😳😳😳", 20), "😳😳😳😳😳"); + + assert_eq!(cut_str("🏳️🏳️", 0), ""); + assert_eq!(cut_str("🏳️🏳️", 1), "🏳"); + assert_eq!(cut_str("🏳️🏳️", 2), "🏳\u{fe0f}🏳"); + assert_eq!(string_width("🏳️🏳️"), string_width("🏳\u{fe0f}🏳")); + + assert_eq!(cut_str("🎓", 1), "�"); + assert_eq!(cut_str("🎓", 2), "🎓"); + + assert_eq!(cut_str("🥿", 1), "�"); + assert_eq!(cut_str("🥿", 2), "🥿"); + + assert_eq!(cut_str("🩰", 1), "�"); + assert_eq!(cut_str("🩰", 2), "🩰"); + + assert_eq!(cut_str("👍🏿", 1), "�"); + assert_eq!(cut_str("👍🏿", 2), "👍"); + assert_eq!(cut_str("👍🏿", 3), "👍�"); + assert_eq!(cut_str("👍🏿", 4), "👍🏿"); + + assert_eq!(cut_str("🇻🇬", 1), "🇻"); + assert_eq!(cut_str("🇻🇬", 2), "🇻🇬"); + assert_eq!(cut_str("🇻🇬", 3), "🇻🇬"); + assert_eq!(cut_str("🇻🇬", 4), "🇻🇬"); + } + + #[cfg(feature = "color")] + #[test] + fn strip_color_test() { + let numbers = "123456".red().on_bright_black().to_string(); + + assert_eq!(cut_str(&numbers, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m"); + assert_eq!( + cut_str(&numbers, 3), + "\u{1b}[31;100m123\u{1b}[39m\u{1b}[49m" + ); + assert_eq!(cut_str(&numbers, 10), "\u{1b}[31;100m123456\u{1b}[0m"); + + let emojies = "😳😳😳😳😳".red().on_bright_black().to_string(); + + assert_eq!(cut_str(&emojies, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m"); + assert_eq!( + cut_str(&emojies, 3), + "\u{1b}[31;100m😳\u{1b}[39m\u{1b}[49m�" + ); + assert_eq!( + cut_str(&emojies, 4), + "\u{1b}[31;100m😳😳\u{1b}[39m\u{1b}[49m" + ); + assert_eq!(cut_str(&emojies, 20), "\u{1b}[31;100m😳😳😳😳😳\u{1b}[0m"); + + let emojies = "🏳️🏳️".red().on_bright_black().to_string(); + + assert_eq!(cut_str(&emojies, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m"); + assert_eq!(cut_str(&emojies, 1), "\u{1b}[31;100m🏳\u{1b}[39m\u{1b}[49m"); + assert_eq!( + cut_str(&emojies, 2), + "\u{1b}[31;100m🏳\u{fe0f}🏳\u{1b}[39m\u{1b}[49m" + ); + assert_eq!( + string_width(&emojies), + string_width("\u{1b}[31;100m🏳\u{fe0f}🏳\u{1b}[39m\u{1b}[49m") + ); + } + + #[test] + #[cfg(feature = "color")] + fn test_color_strip() { + let s = "Collored string" + .fg::<Yellow>() + .on_truecolor(12, 200, 100) + .blink() + .to_string(); + assert_eq!( + cut_str(&s, 1), + "\u{1b}[5m\u{1b}[48;2;12;200;100m\u{1b}[33mC\u{1b}[25m\u{1b}[39m\u{1b}[49m" + ) + } +} diff --git a/vendor/tabled/src/settings/width/width_list.rs b/vendor/tabled/src/settings/width/width_list.rs new file mode 100644 index 000000000..7547b97f3 --- /dev/null +++ b/vendor/tabled/src/settings/width/width_list.rs @@ -0,0 +1,50 @@ +use std::iter::FromIterator; + +use crate::{ + grid::dimension::CompleteDimensionVecRecords, grid::records::Records, settings::TableOption, +}; + +/// A structure used to set [`Table`] width via a list of columns widths. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct WidthList { + list: Vec<usize>, +} + +impl WidthList { + /// Creates a new object. + pub fn new(list: Vec<usize>) -> Self { + Self { list } + } +} + +impl From<Vec<usize>> for WidthList { + fn from(list: Vec<usize>) -> Self { + Self::new(list) + } +} + +impl FromIterator<usize> for WidthList { + fn from_iter<T: IntoIterator<Item = usize>>(iter: T) -> Self { + Self::new(iter.into_iter().collect()) + } +} + +impl<R, C> TableOption<R, CompleteDimensionVecRecords<'static>, C> for WidthList +where + R: Records, +{ + fn change( + self, + records: &mut R, + _: &mut C, + dimension: &mut CompleteDimensionVecRecords<'static>, + ) { + if self.list.len() < records.count_columns() { + return; + } + + let _ = dimension.set_widths(self.list); + } +} diff --git a/vendor/tabled/src/settings/width/wrap.rs b/vendor/tabled/src/settings/width/wrap.rs new file mode 100644 index 000000000..96a370408 --- /dev/null +++ b/vendor/tabled/src/settings/width/wrap.rs @@ -0,0 +1,1468 @@ +//! This module contains [`Wrap`] structure, used to decrease width of a [`Table`]s or a cell on a [`Table`] by wrapping it's content +//! to a new line. +//! +//! [`Table`]: crate::Table + +use std::marker::PhantomData; + +use crate::{ + grid::config::ColoredConfig, + grid::dimension::CompleteDimensionVecRecords, + grid::records::{EmptyRecords, ExactRecords, PeekableRecords, Records, RecordsMut}, + grid::{config::Entity, config::SpannedConfig, util::string::string_width_multiline}, + settings::{ + measurement::Measurement, + peaker::{Peaker, PriorityNone}, + width::Width, + CellOption, TableOption, + }, +}; + +use super::util::{get_table_widths, get_table_widths_with_total, split_at_pos}; + +/// Wrap wraps a string to a new line in case it exceeds the provided max boundary. +/// Otherwise keeps the content of a cell untouched. +/// +/// The function is color aware if a `color` feature is on. +/// +/// Be aware that it doesn't consider padding. +/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. +/// +/// ## Example +/// +/// ``` +/// use tabled::{Table, settings::{object::Segment, width::Width, Modify}}; +/// +/// let table = Table::new(&["Hello World!"]) +/// .with(Modify::new(Segment::all()).with(Width::wrap(3))); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +#[derive(Debug, Clone)] +pub struct Wrap<W = usize, P = PriorityNone> { + width: W, + keep_words: bool, + _priority: PhantomData<P>, +} + +impl<W> Wrap<W> { + /// Creates a [`Wrap`] object + pub fn new(width: W) -> Self + where + W: Measurement<Width>, + { + Wrap { + width, + keep_words: false, + _priority: PhantomData, + } + } +} + +impl<W, P> Wrap<W, P> { + /// Priority defines the logic by which a truncate will be applied when is done for the whole table. + /// + /// - [`PriorityNone`] which cuts the columns one after another. + /// - [`PriorityMax`] cuts the biggest columns first. + /// - [`PriorityMin`] cuts the lowest columns first. + /// + /// Be aware that it doesn't consider padding. + /// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. + /// + /// [`Padding`]: crate::settings::Padding + /// [`PriorityMax`]: crate::settings::peaker::PriorityMax + /// [`PriorityMin`]: crate::settings::peaker::PriorityMin + pub fn priority<PP>(self) -> Wrap<W, PP> { + Wrap { + width: self.width, + keep_words: self.keep_words, + _priority: PhantomData, + } + } + + /// Set the keep words option. + /// + /// If a wrapping point will be in a word, [`Wrap`] will + /// preserve a word (if possible) and wrap the string before it. + pub fn keep_words(mut self) -> Self { + self.keep_words = true; + self + } +} + +impl Wrap<(), ()> { + /// Wrap a given string + pub fn wrap_text(text: &str, width: usize, keeping_words: bool) -> String { + wrap_text(text, width, keeping_words) + } +} + +impl<W, P, R> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> for Wrap<W, P> +where + W: Measurement<Width>, + P: Peaker, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + if records.count_rows() == 0 || records.count_columns() == 0 { + return; + } + + let width = self.width.measure(&*records, cfg); + let (widths, total) = get_table_widths_with_total(&*records, cfg); + if width >= total { + return; + } + + let priority = P::create(); + let keep_words = self.keep_words; + let widths = wrap_total_width(records, cfg, widths, total, width, keep_words, priority); + + let _ = dims.set_widths(widths); + } +} + +impl<W, R> CellOption<R, ColoredConfig> for Wrap<W> +where + W: Measurement<Width>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let width = self.width.measure(&*records, cfg); + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + for pos in entity.iter(count_rows, count_columns) { + let is_valid_pos = pos.0 < records.count_rows() && pos.1 < records.count_columns(); + if !is_valid_pos { + continue; + } + + let text = records.get_text(pos); + let cell_width = string_width_multiline(text); + if cell_width <= width { + continue; + } + + let wrapped = wrap_text(text, width, self.keep_words); + records.set(pos, wrapped); + } + } +} + +fn wrap_total_width<R, P>( + records: &mut R, + cfg: &mut ColoredConfig, + mut widths: Vec<usize>, + total_width: usize, + width: usize, + keep_words: bool, + priority: P, +) -> Vec<usize> +where + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + P: Peaker, + for<'a> &'a R: Records, +{ + let shape = (records.count_rows(), records.count_columns()); + let min_widths = get_table_widths(EmptyRecords::from(shape), cfg); + + decrease_widths(&mut widths, &min_widths, total_width, width, priority); + + let points = get_decrease_cell_list(cfg, &widths, &min_widths, shape); + + for ((row, col), width) in points { + let mut wrap = Wrap::new(width); + wrap.keep_words = keep_words; + <Wrap as CellOption<_, _>>::change(wrap, records, cfg, (row, col).into()); + } + + widths +} + +#[cfg(not(feature = "color"))] +pub(crate) fn wrap_text(text: &str, width: usize, keep_words: bool) -> String { + if width == 0 { + return String::new(); + } + + if keep_words { + split_keeping_words(text, width, "\n") + } else { + chunks(text, width).join("\n") + } +} + +#[cfg(feature = "color")] +pub(crate) fn wrap_text(text: &str, width: usize, keep_words: bool) -> String { + use super::util::strip_osc; + + if width == 0 { + return String::new(); + } + + let (text, url): (String, Option<String>) = strip_osc(text); + let (prefix, suffix) = build_link_prefix_suffix(url); + + if keep_words { + split_keeping_words(&text, width, &prefix, &suffix) + } else { + chunks(&text, width, &prefix, &suffix).join("\n") + } +} + +#[cfg(feature = "color")] +fn build_link_prefix_suffix(url: Option<String>) -> (String, String) { + match url { + Some(url) => { + // https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda + let osc8 = "\x1b]8;;"; + let st = "\x1b\\"; + + (format!("{osc8}{url}{st}"), format!("{osc8}{st}")) + } + None => ("".to_string(), "".to_string()), + } +} + +#[cfg(not(feature = "color"))] +fn chunks(s: &str, width: usize) -> Vec<String> { + if width == 0 { + return Vec::new(); + } + + const REPLACEMENT: char = '\u{FFFD}'; + + let mut buf = String::with_capacity(width); + let mut list = Vec::new(); + let mut i = 0; + for c in s.chars() { + let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or_default(); + if i + c_width > width { + let count_unknowns = width - i; + buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); + i += count_unknowns; + } else { + buf.push(c); + i += c_width; + } + + if i == width { + list.push(buf); + buf = String::with_capacity(width); + i = 0; + } + } + + if !buf.is_empty() { + list.push(buf); + } + + list +} + +#[cfg(feature = "color")] +fn chunks(s: &str, width: usize, prefix: &str, suffix: &str) -> Vec<String> { + use std::fmt::Write; + + if width == 0 { + return Vec::new(); + } + + let mut list = Vec::new(); + let mut line = String::with_capacity(width); + let mut line_width = 0; + + for b in ansi_str::get_blocks(s) { + let text_style = b.style(); + let mut text_slice = b.text(); + if text_slice.is_empty() { + continue; + } + + let available_space = width - line_width; + if available_space == 0 { + list.push(line); + line = String::with_capacity(width); + line_width = 0; + } + + line.push_str(prefix); + let _ = write!(&mut line, "{}", text_style.start()); + + while !text_slice.is_empty() { + let available_space = width - line_width; + + let part_width = unicode_width::UnicodeWidthStr::width(text_slice); + if part_width <= available_space { + line.push_str(text_slice); + line_width += part_width; + + if available_space == 0 { + let _ = write!(&mut line, "{}", text_style.end()); + line.push_str(suffix); + list.push(line); + line = String::with_capacity(width); + line.push_str(prefix); + line_width = 0; + let _ = write!(&mut line, "{}", text_style.start()); + } + + break; + } + + let (lhs, rhs, (unknowns, split_char)) = split_string_at(text_slice, available_space); + + text_slice = &rhs[split_char..]; + + line.push_str(lhs); + line_width += unicode_width::UnicodeWidthStr::width(lhs); + + const REPLACEMENT: char = '\u{FFFD}'; + line.extend(std::iter::repeat(REPLACEMENT).take(unknowns)); + line_width += unknowns; + + if line_width == width { + let _ = write!(&mut line, "{}", text_style.end()); + line.push_str(suffix); + list.push(line); + line = String::with_capacity(width); + line.push_str(prefix); + line_width = 0; + let _ = write!(&mut line, "{}", text_style.start()); + } + } + + if line_width > 0 { + let _ = write!(&mut line, "{}", text_style.end()); + } + } + + if line_width > 0 { + line.push_str(suffix); + list.push(line); + } + + list +} + +#[cfg(not(feature = "color"))] +fn split_keeping_words(s: &str, width: usize, sep: &str) -> String { + const REPLACEMENT: char = '\u{FFFD}'; + + let mut lines = Vec::new(); + let mut line = String::with_capacity(width); + let mut line_width = 0; + + let mut is_first_word = true; + + for word in s.split(' ') { + if !is_first_word { + let line_has_space = line_width < width; + if line_has_space { + line.push(' '); + line_width += 1; + is_first_word = false; + } + } + + if is_first_word { + is_first_word = false; + } + + let word_width = unicode_width::UnicodeWidthStr::width(word); + + let line_has_space = line_width + word_width <= width; + if line_has_space { + line.push_str(word); + line_width += word_width; + continue; + } + + if word_width <= width { + // the word can be fit to 'width' so we put it on new line + + line.extend(std::iter::repeat(' ').take(width - line_width)); + lines.push(line); + + line = String::with_capacity(width); + line_width = 0; + + line.push_str(word); + line_width += word_width; + is_first_word = false; + } else { + // the word is too long any way so we split it + + let mut word_part = word; + while !word_part.is_empty() { + let available_space = width - line_width; + let (lhs, rhs, (unknowns, split_char)) = + split_string_at(word_part, available_space); + + word_part = &rhs[split_char..]; + line_width += unicode_width::UnicodeWidthStr::width(lhs) + unknowns; + is_first_word = false; + + line.push_str(lhs); + line.extend(std::iter::repeat(REPLACEMENT).take(unknowns)); + + if line_width == width { + lines.push(line); + line = String::with_capacity(width); + line_width = 0; + is_first_word = true; + } + } + } + } + + if line_width > 0 { + line.extend(std::iter::repeat(' ').take(width - line_width)); + lines.push(line); + } + + lines.join(sep) +} + +#[cfg(feature = "color")] +fn split_keeping_words(text: &str, width: usize, prefix: &str, suffix: &str) -> String { + if text.is_empty() || width == 0 { + return String::new(); + } + + let stripped_text = ansi_str::AnsiStr::ansi_strip(text); + let mut word_width = 0; + let mut word_chars = 0; + let mut blocks = parsing::Blocks::new(ansi_str::get_blocks(text)); + let mut buf = parsing::MultilineBuffer::new(width); + buf.set_prefix(prefix); + buf.set_suffix(suffix); + + for c in stripped_text.chars() { + match c { + ' ' => { + parsing::handle_word(&mut buf, &mut blocks, word_chars, word_width, 1); + word_chars = 0; + word_width = 0; + } + '\n' => { + parsing::handle_word(&mut buf, &mut blocks, word_chars, word_width, 1); + word_chars = 0; + word_width = 0; + } + _ => { + word_width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); + word_chars += 1; + } + } + } + + if word_chars > 0 { + parsing::handle_word(&mut buf, &mut blocks, word_chars, word_width, 0); + buf.finish_line(&blocks); + } + + buf.into_string() +} + +#[cfg(feature = "color")] +mod parsing { + use ansi_str::{AnsiBlock, AnsiBlockIter, Style}; + use std::fmt::Write; + + pub(super) struct Blocks<'a> { + iter: AnsiBlockIter<'a>, + current: Option<RelativeBlock<'a>>, + } + + impl<'a> Blocks<'a> { + pub(super) fn new(iter: AnsiBlockIter<'a>) -> Self { + Self { + iter, + current: None, + } + } + + pub(super) fn next_block(&mut self) -> Option<RelativeBlock<'a>> { + self.current + .take() + .or_else(|| self.iter.next().map(RelativeBlock::new)) + } + } + + pub(super) struct RelativeBlock<'a> { + block: AnsiBlock<'a>, + pos: usize, + } + + impl<'a> RelativeBlock<'a> { + pub(super) fn new(block: AnsiBlock<'a>) -> Self { + Self { block, pos: 0 } + } + + pub(super) fn get_text(&self) -> &str { + &self.block.text()[self.pos..] + } + + pub(super) fn get_origin(&self) -> &str { + self.block.text() + } + + pub(super) fn get_style(&self) -> &Style { + self.block.style() + } + } + + pub(super) struct MultilineBuffer<'a> { + buf: String, + width_last: usize, + width: usize, + prefix: &'a str, + suffix: &'a str, + } + + impl<'a> MultilineBuffer<'a> { + pub(super) fn new(width: usize) -> Self { + Self { + buf: String::new(), + width_last: 0, + prefix: "", + suffix: "", + width, + } + } + + pub(super) fn into_string(self) -> String { + self.buf + } + + pub(super) fn set_suffix(&mut self, suffix: &'a str) { + self.suffix = suffix; + } + + pub(super) fn set_prefix(&mut self, prefix: &'a str) { + self.prefix = prefix; + } + + pub(super) fn max_width(&self) -> usize { + self.width + } + + pub(super) fn available_width(&self) -> usize { + self.width - self.width_last + } + + pub(super) fn fill(&mut self, c: char) -> usize { + debug_assert_eq!(unicode_width::UnicodeWidthChar::width(c), Some(1)); + + let rest_width = self.available_width(); + for _ in 0..rest_width { + self.buf.push(c); + } + + rest_width + } + + pub(super) fn set_next_line(&mut self, blocks: &Blocks<'_>) { + if let Some(block) = &blocks.current { + let _ = self + .buf + .write_fmt(format_args!("{}", block.get_style().end())); + } + + self.buf.push_str(self.suffix); + + let _ = self.fill(' '); + self.buf.push('\n'); + self.width_last = 0; + + self.buf.push_str(self.prefix); + + if let Some(block) = &blocks.current { + let _ = self + .buf + .write_fmt(format_args!("{}", block.get_style().start())); + } + } + + pub(super) fn finish_line(&mut self, blocks: &Blocks<'_>) { + if let Some(block) = &blocks.current { + let _ = self + .buf + .write_fmt(format_args!("{}", block.get_style().end())); + } + + self.buf.push_str(self.suffix); + + let _ = self.fill(' '); + self.width_last = 0; + } + + pub(super) fn read_chars(&mut self, block: &RelativeBlock<'_>, n: usize) -> (usize, usize) { + let mut count_chars = 0; + let mut count_bytes = 0; + for c in block.get_text().chars() { + if count_chars == n { + break; + } + + count_chars += 1; + count_bytes += c.len_utf8(); + + let cwidth = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); + + let available_space = self.width - self.width_last; + if available_space == 0 { + let _ = self + .buf + .write_fmt(format_args!("{}", block.get_style().end())); + self.buf.push_str(self.suffix); + self.buf.push('\n'); + self.buf.push_str(self.prefix); + let _ = self + .buf + .write_fmt(format_args!("{}", block.get_style().start())); + self.width_last = 0; + } + + let is_enough_space = self.width_last + cwidth <= self.width; + if !is_enough_space { + // thereatically a cwidth can be 2 but buf_width is 1 + // but it handled here too; + + const REPLACEMENT: char = '\u{FFFD}'; + let _ = self.fill(REPLACEMENT); + self.width_last = self.width; + } else { + self.buf.push(c); + self.width_last += cwidth; + } + } + + (count_chars, count_bytes) + } + + pub(super) fn read_chars_unchecked( + &mut self, + block: &RelativeBlock<'_>, + n: usize, + ) -> (usize, usize) { + let mut count_chars = 0; + let mut count_bytes = 0; + for c in block.get_text().chars() { + if count_chars == n { + break; + } + + count_chars += 1; + count_bytes += c.len_utf8(); + + let cwidth = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); + self.width_last += cwidth; + + self.buf.push(c); + } + + debug_assert!(self.width_last <= self.width); + + (count_chars, count_bytes) + } + } + + pub(super) fn read_chars(buf: &mut MultilineBuffer<'_>, blocks: &mut Blocks<'_>, n: usize) { + let mut n = n; + while n > 0 { + let is_new_block = blocks.current.is_none(); + let mut block = blocks.next_block().expect("Must never happen"); + if is_new_block { + buf.buf.push_str(buf.prefix); + let _ = buf + .buf + .write_fmt(format_args!("{}", block.get_style().start())); + } + + let (read_count, read_bytes) = buf.read_chars(&block, n); + + if block.pos + read_bytes == block.get_origin().len() { + let _ = buf + .buf + .write_fmt(format_args!("{}", block.get_style().end())); + } else { + block.pos += read_bytes; + blocks.current = Some(block); + } + + n -= read_count; + } + } + + pub(super) fn read_chars_unchecked( + buf: &mut MultilineBuffer<'_>, + blocks: &mut Blocks<'_>, + n: usize, + ) { + let mut n = n; + while n > 0 { + let is_new_block = blocks.current.is_none(); + let mut block = blocks.next_block().expect("Must never happen"); + + if is_new_block { + buf.buf.push_str(buf.prefix); + let _ = buf + .buf + .write_fmt(format_args!("{}", block.get_style().start())); + } + + let (read_count, read_bytes) = buf.read_chars_unchecked(&block, n); + + if block.pos + read_bytes == block.get_origin().len() { + let _ = buf + .buf + .write_fmt(format_args!("{}", block.get_style().end())); + } else { + block.pos += read_bytes; + blocks.current = Some(block); + } + + n -= read_count; + } + } + + pub(super) fn handle_word( + buf: &mut MultilineBuffer<'_>, + blocks: &mut Blocks<'_>, + word_chars: usize, + word_width: usize, + additional_read: usize, + ) { + if word_chars > 0 { + let has_line_space = word_width <= buf.available_width(); + let is_word_too_big = word_width > buf.max_width(); + + if is_word_too_big { + read_chars(buf, blocks, word_chars + additional_read); + } else if has_line_space { + read_chars_unchecked(buf, blocks, word_chars); + if additional_read > 0 { + read_chars(buf, blocks, additional_read); + } + } else { + buf.set_next_line(&*blocks); + read_chars_unchecked(buf, blocks, word_chars); + if additional_read > 0 { + read_chars(buf, blocks, additional_read); + } + } + + return; + } + + let has_current_line_space = additional_read <= buf.available_width(); + if has_current_line_space { + read_chars_unchecked(buf, blocks, additional_read); + } else { + buf.set_next_line(&*blocks); + read_chars_unchecked(buf, blocks, additional_read); + } + } +} + +fn split_string_at(text: &str, at: usize) -> (&str, &str, (usize, usize)) { + let (length, count_unknowns, split_char_size) = split_at_pos(text, at); + let (lhs, rhs) = text.split_at(length); + + (lhs, rhs, (count_unknowns, split_char_size)) +} + +fn decrease_widths<F>( + widths: &mut [usize], + min_widths: &[usize], + total_width: usize, + mut width: usize, + mut peeaker: F, +) where + F: Peaker, +{ + let mut empty_list = 0; + for col in 0..widths.len() { + if widths[col] == 0 || widths[col] <= min_widths[col] { + empty_list += 1; + } + } + + while width != total_width { + if empty_list == widths.len() { + break; + } + + let col = match peeaker.peak(min_widths, widths) { + Some(col) => col, + None => break, + }; + + if widths[col] == 0 || widths[col] <= min_widths[col] { + continue; + } + + widths[col] -= 1; + + if widths[col] == 0 || widths[col] <= min_widths[col] { + empty_list += 1; + } + + width += 1; + } +} + +fn get_decrease_cell_list( + cfg: &SpannedConfig, + widths: &[usize], + min_widths: &[usize], + shape: (usize, usize), +) -> Vec<((usize, usize), usize)> { + let mut points = Vec::new(); + (0..shape.1).for_each(|col| { + (0..shape.0) + .filter(|&row| cfg.is_cell_visible((row, col))) + .for_each(|row| { + let (width, width_min) = match cfg.get_column_span((row, col)) { + Some(span) => { + let width = (col..col + span).map(|i| widths[i]).sum::<usize>(); + let min_width = (col..col + span).map(|i| min_widths[i]).sum::<usize>(); + let count_borders = count_borders(cfg, col, col + span, shape.1); + (width + count_borders, min_width + count_borders) + } + None => (widths[col], min_widths[col]), + }; + + if width >= width_min { + let padding = cfg.get_padding((row, col).into()); + let width = width.saturating_sub(padding.left.size + padding.right.size); + + points.push(((row, col), width)); + } + }); + }); + + points +} + +fn count_borders(cfg: &SpannedConfig, start: usize, end: usize, count_columns: usize) -> usize { + (start..end) + .skip(1) + .filter(|&i| cfg.has_vertical(i, count_columns)) + .count() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn split_test() { + #[cfg(not(feature = "color"))] + let split = |text, width| chunks(text, width).join("\n"); + + #[cfg(feature = "color")] + let split = |text, width| chunks(text, width, "", "").join("\n"); + + assert_eq!(split("123456", 0), ""); + + assert_eq!(split("123456", 1), "1\n2\n3\n4\n5\n6"); + assert_eq!(split("123456", 2), "12\n34\n56"); + assert_eq!(split("12345", 2), "12\n34\n5"); + assert_eq!(split("123456", 6), "123456"); + assert_eq!(split("123456", 10), "123456"); + + assert_eq!(split("😳😳😳😳😳", 1), "�\n�\n�\n�\n�"); + assert_eq!(split("😳😳😳😳😳", 2), "😳\n😳\n😳\n😳\n😳"); + assert_eq!(split("😳😳😳😳😳", 3), "😳�\n😳�\n😳"); + assert_eq!(split("😳😳😳😳😳", 6), "😳😳😳\n😳😳"); + assert_eq!(split("😳😳😳😳😳", 20), "😳😳😳😳😳"); + + assert_eq!(split("😳123😳", 1), "�\n1\n2\n3\n�"); + assert_eq!(split("😳12😳3", 1), "�\n1\n2\n�\n3"); + } + + #[test] + fn chunks_test() { + #[allow(clippy::redundant_closure)] + #[cfg(not(feature = "color"))] + let chunks = |text, width| chunks(text, width); + + #[cfg(feature = "color")] + let chunks = |text, width| chunks(text, width, "", ""); + + assert_eq!(chunks("123456", 0), [""; 0]); + + assert_eq!(chunks("123456", 1), ["1", "2", "3", "4", "5", "6"]); + assert_eq!(chunks("123456", 2), ["12", "34", "56"]); + assert_eq!(chunks("12345", 2), ["12", "34", "5"]); + + assert_eq!(chunks("😳😳😳😳😳", 1), ["�", "�", "�", "�", "�"]); + assert_eq!(chunks("😳😳😳😳😳", 2), ["😳", "😳", "😳", "😳", "😳"]); + assert_eq!(chunks("😳😳😳😳😳", 3), ["😳�", "😳�", "😳"]); + } + + #[cfg(not(feature = "color"))] + #[test] + fn split_by_line_keeping_words_test() { + let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); + + assert_eq!(split_keeping_words("123456", 1), "1\n2\n3\n4\n5\n6"); + assert_eq!(split_keeping_words("123456", 2), "12\n34\n56"); + assert_eq!(split_keeping_words("12345", 2), "12\n34\n5 "); + + assert_eq!(split_keeping_words("😳😳😳😳😳", 1), "�\n�\n�\n�\n�"); + + assert_eq!(split_keeping_words("111 234 1", 4), "111 \n234 \n1 "); + } + + #[cfg(feature = "color")] + #[test] + fn split_by_line_keeping_words_test() { + #[cfg(feature = "color")] + let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); + + assert_eq!(split_keeping_words("123456", 1), "1\n2\n3\n4\n5\n6"); + assert_eq!(split_keeping_words("123456", 2), "12\n34\n56"); + assert_eq!(split_keeping_words("12345", 2), "12\n34\n5 "); + + assert_eq!(split_keeping_words("😳😳😳😳😳", 1), "�\n�\n�\n�\n�"); + + assert_eq!(split_keeping_words("111 234 1", 4), "111 \n234 \n1 "); + } + + #[cfg(feature = "color")] + #[test] + fn split_by_line_keeping_words_color_test() { + #[cfg(feature = "color")] + let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); + + #[cfg(not(feature = "color"))] + let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); + + let text = "\u{1b}[36mJapanese “vacancy” button\u{1b}[0m"; + + assert_eq!(split_keeping_words(text, 2), "\u{1b}[36mJa\u{1b}[39m\n\u{1b}[36mpa\u{1b}[39m\n\u{1b}[36mne\u{1b}[39m\n\u{1b}[36mse\u{1b}[39m\n\u{1b}[36m “\u{1b}[39m\n\u{1b}[36mva\u{1b}[39m\n\u{1b}[36mca\u{1b}[39m\n\u{1b}[36mnc\u{1b}[39m\n\u{1b}[36my”\u{1b}[39m\n\u{1b}[36m b\u{1b}[39m\n\u{1b}[36mut\u{1b}[39m\n\u{1b}[36mto\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m "); + assert_eq!(split_keeping_words(text, 1), "\u{1b}[36mJ\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mp\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m\n\u{1b}[36me\u{1b}[39m\n\u{1b}[36ms\u{1b}[39m\n\u{1b}[36me\u{1b}[39m\n\u{1b}[36m \u{1b}[39m\n\u{1b}[36m“\u{1b}[39m\n\u{1b}[36mv\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mc\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m\n\u{1b}[36mc\u{1b}[39m\n\u{1b}[36my\u{1b}[39m\n\u{1b}[36m”\u{1b}[39m\n\u{1b}[36m \u{1b}[39m\n\u{1b}[36mb\u{1b}[39m\n\u{1b}[36mu\u{1b}[39m\n\u{1b}[36mt\u{1b}[39m\n\u{1b}[36mt\u{1b}[39m\n\u{1b}[36mo\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m"); + } + + #[cfg(feature = "color")] + #[test] + fn split_by_line_keeping_words_color_2_test() { + use ansi_str::AnsiStr; + + #[cfg(feature = "color")] + let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); + + #[cfg(not(feature = "color"))] + let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); + + let text = "\u{1b}[37mTigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia\u{1b}[0m"; + + assert_eq!( + split_keeping_words(text, 2) + .ansi_split("\n") + .collect::<Vec<_>>(), + [ + "\u{1b}[37mTi\u{1b}[39m", + "\u{1b}[37mgr\u{1b}[39m", + "\u{1b}[37me \u{1b}[39m", + "\u{1b}[37mEc\u{1b}[39m", + "\u{1b}[37mua\u{1b}[39m", + "\u{1b}[37mdo\u{1b}[39m", + "\u{1b}[37mr \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mOM\u{1b}[39m", + "\u{1b}[37mYA\u{1b}[39m", + "\u{1b}[37m A\u{1b}[39m", + "\u{1b}[37mnd\u{1b}[39m", + "\u{1b}[37min\u{1b}[39m", + "\u{1b}[37ma \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m38\u{1b}[39m", + "\u{1b}[37m24\u{1b}[39m", + "\u{1b}[37m90\u{1b}[39m", + "\u{1b}[37m99\u{1b}[39m", + "\u{1b}[37m99\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mCa\u{1b}[39m", + "\u{1b}[37mlc\u{1b}[39m", + "\u{1b}[37miu\u{1b}[39m", + "\u{1b}[37mm \u{1b}[39m", + "\u{1b}[37mca\u{1b}[39m", + "\u{1b}[37mrb\u{1b}[39m", + "\u{1b}[37mon\u{1b}[39m", + "\u{1b}[37mat\u{1b}[39m", + "\u{1b}[37me \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mCo\u{1b}[39m", + "\u{1b}[37mlo\u{1b}[39m", + "\u{1b}[37mmb\u{1b}[39m", + "\u{1b}[37mia\u{1b}[39m" + ] + ); + + assert_eq!( + split_keeping_words(text, 1) + .ansi_split("\n") + .collect::<Vec<_>>(), + [ + "\u{1b}[37mT\u{1b}[39m", + "\u{1b}[37mi\u{1b}[39m", + "\u{1b}[37mg\u{1b}[39m", + "\u{1b}[37mr\u{1b}[39m", + "\u{1b}[37me\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mE\u{1b}[39m", + "\u{1b}[37mc\u{1b}[39m", + "\u{1b}[37mu\u{1b}[39m", + "\u{1b}[37ma\u{1b}[39m", + "\u{1b}[37md\u{1b}[39m", + "\u{1b}[37mo\u{1b}[39m", + "\u{1b}[37mr\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mO\u{1b}[39m", + "\u{1b}[37mM\u{1b}[39m", + "\u{1b}[37mY\u{1b}[39m", + "\u{1b}[37mA\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mA\u{1b}[39m", + "\u{1b}[37mn\u{1b}[39m", + "\u{1b}[37md\u{1b}[39m", + "\u{1b}[37mi\u{1b}[39m", + "\u{1b}[37mn\u{1b}[39m", + "\u{1b}[37ma\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m3\u{1b}[39m", + "\u{1b}[37m8\u{1b}[39m", + "\u{1b}[37m2\u{1b}[39m", + "\u{1b}[37m4\u{1b}[39m", + "\u{1b}[37m9\u{1b}[39m", + "\u{1b}[37m0\u{1b}[39m", + "\u{1b}[37m9\u{1b}[39m", + "\u{1b}[37m9\u{1b}[39m", + "\u{1b}[37m9\u{1b}[39m", + "\u{1b}[37m9\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mC\u{1b}[39m", + "\u{1b}[37ma\u{1b}[39m", + "\u{1b}[37ml\u{1b}[39m", + "\u{1b}[37mc\u{1b}[39m", + "\u{1b}[37mi\u{1b}[39m", + "\u{1b}[37mu\u{1b}[39m", + "\u{1b}[37mm\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mc\u{1b}[39m", + "\u{1b}[37ma\u{1b}[39m", + "\u{1b}[37mr\u{1b}[39m", + "\u{1b}[37mb\u{1b}[39m", + "\u{1b}[37mo\u{1b}[39m", + "\u{1b}[37mn\u{1b}[39m", + "\u{1b}[37ma\u{1b}[39m", + "\u{1b}[37mt\u{1b}[39m", + "\u{1b}[37me\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mC\u{1b}[39m", + "\u{1b}[37mo\u{1b}[39m", + "\u{1b}[37ml\u{1b}[39m", + "\u{1b}[37mo\u{1b}[39m", + "\u{1b}[37mm\u{1b}[39m", + "\u{1b}[37mb\u{1b}[39m", + "\u{1b}[37mi\u{1b}[39m", + "\u{1b}[37ma\u{1b}[39m" + ] + ) + } + + #[cfg(feature = "color")] + #[test] + fn split_by_line_keeping_words_color_3_test() { + let split = |text, width| split_keeping_words(text, width, "", ""); + assert_eq!( + split( + "\u{1b}[37m🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻\u{1b}[0m", + 3, + ), + "\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m", + ); + assert_eq!( + split("\u{1b}[37mthis is a long sentence\u{1b}[0m", 7), + "\u{1b}[37mthis is\u{1b}[39m\n\u{1b}[37m a long\u{1b}[39m\n\u{1b}[37m senten\u{1b}[39m\n\u{1b}[37mce\u{1b}[39m " + ); + assert_eq!( + split("\u{1b}[37mHello World\u{1b}[0m", 7), + "\u{1b}[37mHello \u{1b}[39m \n\u{1b}[37mWorld\u{1b}[39m " + ); + assert_eq!( + split("\u{1b}[37mHello Wo\u{1b}[37mrld\u{1b}[0m", 7), + "\u{1b}[37mHello \u{1b}[39m \n\u{1b}[37mWo\u{1b}[39m\u{1b}[37mrld\u{1b}[39m " + ); + assert_eq!( + split("\u{1b}[37mHello Wo\u{1b}[37mrld\u{1b}[0m", 8), + "\u{1b}[37mHello \u{1b}[39m \n\u{1b}[37mWo\u{1b}[39m\u{1b}[37mrld\u{1b}[39m " + ); + } + + #[cfg(not(feature = "color"))] + #[test] + fn split_keeping_words_4_test() { + let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); + + assert_eq!(split_keeping_words("12345678", 3,), "123\n456\n78 "); + assert_eq!(split_keeping_words("12345678", 2,), "12\n34\n56\n78"); + } + + #[cfg(feature = "color")] + #[test] + fn split_keeping_words_4_test() { + let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); + + #[cfg(not(feature = "color"))] + let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); + + assert_eq!(split_keeping_words("12345678", 3,), "123\n456\n78 "); + assert_eq!(split_keeping_words("12345678", 2,), "12\n34\n56\n78"); + } + + #[cfg(feature = "color")] + #[test] + fn chunks_test_with_prefix_and_suffix() { + assert_eq!(chunks("123456", 0, "^", "$"), ["^$"; 0]); + + assert_eq!( + chunks("123456", 1, "^", "$"), + ["^1$", "^2$", "^3$", "^4$", "^5$", "^6$"] + ); + assert_eq!(chunks("123456", 2, "^", "$"), ["^12$", "^34$", "^56$"]); + assert_eq!(chunks("12345", 2, "^", "$"), ["^12$", "^34$", "^5$"]); + + assert_eq!( + chunks("😳😳😳😳😳", 1, "^", "$"), + ["^�$", "^�$", "^�$", "^�$", "^�$"] + ); + assert_eq!( + chunks("😳😳😳😳😳", 2, "^", "$"), + ["^😳$", "^😳$", "^😳$", "^😳$", "^😳$"] + ); + assert_eq!( + chunks("😳😳😳😳😳", 3, "^", "$"), + ["^😳�$", "^😳�$", "^😳$"] + ); + } + + #[cfg(feature = "color")] + #[test] + fn split_by_line_keeping_words_test_with_prefix_and_suffix() { + assert_eq!( + split_keeping_words("123456", 1, "^", "$"), + "^1$\n^2$\n^3$\n^4$\n^5$\n^6$" + ); + assert_eq!( + split_keeping_words("123456", 2, "^", "$"), + "^12$\n^34$\n^56$" + ); + assert_eq!( + split_keeping_words("12345", 2, "^", "$"), + "^12$\n^34$\n^5$ " + ); + + assert_eq!( + split_keeping_words("😳😳😳😳😳", 1, "^", "$"), + "^�$\n^�$\n^�$\n^�$\n^�$" + ); + } + + #[cfg(feature = "color")] + #[test] + fn split_by_line_keeping_words_color_2_test_with_prefix_and_suffix() { + use ansi_str::AnsiStr; + + let text = "\u{1b}[37mTigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia\u{1b}[0m"; + + assert_eq!( + split_keeping_words(text, 2, "^", "$") + .ansi_split("\n") + .collect::<Vec<_>>(), + [ + "^\u{1b}[37mTi\u{1b}[39m$", + "^\u{1b}[37mgr\u{1b}[39m$", + "^\u{1b}[37me \u{1b}[39m$", + "^\u{1b}[37mEc\u{1b}[39m$", + "^\u{1b}[37mua\u{1b}[39m$", + "^\u{1b}[37mdo\u{1b}[39m$", + "^\u{1b}[37mr \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mOM\u{1b}[39m$", + "^\u{1b}[37mYA\u{1b}[39m$", + "^\u{1b}[37m A\u{1b}[39m$", + "^\u{1b}[37mnd\u{1b}[39m$", + "^\u{1b}[37min\u{1b}[39m$", + "^\u{1b}[37ma \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m38\u{1b}[39m$", + "^\u{1b}[37m24\u{1b}[39m$", + "^\u{1b}[37m90\u{1b}[39m$", + "^\u{1b}[37m99\u{1b}[39m$", + "^\u{1b}[37m99\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mCa\u{1b}[39m$", + "^\u{1b}[37mlc\u{1b}[39m$", + "^\u{1b}[37miu\u{1b}[39m$", + "^\u{1b}[37mm \u{1b}[39m$", + "^\u{1b}[37mca\u{1b}[39m$", + "^\u{1b}[37mrb\u{1b}[39m$", + "^\u{1b}[37mon\u{1b}[39m$", + "^\u{1b}[37mat\u{1b}[39m$", + "^\u{1b}[37me \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mCo\u{1b}[39m$", + "^\u{1b}[37mlo\u{1b}[39m$", + "^\u{1b}[37mmb\u{1b}[39m$", + "^\u{1b}[37mia\u{1b}[39m$" + ] + ); + + assert_eq!( + split_keeping_words(text, 1, "^", "$") + .ansi_split("\n") + .collect::<Vec<_>>(), + [ + "^\u{1b}[37mT\u{1b}[39m$", + "^\u{1b}[37mi\u{1b}[39m$", + "^\u{1b}[37mg\u{1b}[39m$", + "^\u{1b}[37mr\u{1b}[39m$", + "^\u{1b}[37me\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mE\u{1b}[39m$", + "^\u{1b}[37mc\u{1b}[39m$", + "^\u{1b}[37mu\u{1b}[39m$", + "^\u{1b}[37ma\u{1b}[39m$", + "^\u{1b}[37md\u{1b}[39m$", + "^\u{1b}[37mo\u{1b}[39m$", + "^\u{1b}[37mr\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mO\u{1b}[39m$", + "^\u{1b}[37mM\u{1b}[39m$", + "^\u{1b}[37mY\u{1b}[39m$", + "^\u{1b}[37mA\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mA\u{1b}[39m$", + "^\u{1b}[37mn\u{1b}[39m$", + "^\u{1b}[37md\u{1b}[39m$", + "^\u{1b}[37mi\u{1b}[39m$", + "^\u{1b}[37mn\u{1b}[39m$", + "^\u{1b}[37ma\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m3\u{1b}[39m$", + "^\u{1b}[37m8\u{1b}[39m$", + "^\u{1b}[37m2\u{1b}[39m$", + "^\u{1b}[37m4\u{1b}[39m$", + "^\u{1b}[37m9\u{1b}[39m$", + "^\u{1b}[37m0\u{1b}[39m$", + "^\u{1b}[37m9\u{1b}[39m$", + "^\u{1b}[37m9\u{1b}[39m$", + "^\u{1b}[37m9\u{1b}[39m$", + "^\u{1b}[37m9\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mC\u{1b}[39m$", + "^\u{1b}[37ma\u{1b}[39m$", + "^\u{1b}[37ml\u{1b}[39m$", + "^\u{1b}[37mc\u{1b}[39m$", + "^\u{1b}[37mi\u{1b}[39m$", + "^\u{1b}[37mu\u{1b}[39m$", + "^\u{1b}[37mm\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mc\u{1b}[39m$", + "^\u{1b}[37ma\u{1b}[39m$", + "^\u{1b}[37mr\u{1b}[39m$", + "^\u{1b}[37mb\u{1b}[39m$", + "^\u{1b}[37mo\u{1b}[39m$", + "^\u{1b}[37mn\u{1b}[39m$", + "^\u{1b}[37ma\u{1b}[39m$", + "^\u{1b}[37mt\u{1b}[39m$", + "^\u{1b}[37me\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mC\u{1b}[39m$", + "^\u{1b}[37mo\u{1b}[39m$", + "^\u{1b}[37ml\u{1b}[39m$", + "^\u{1b}[37mo\u{1b}[39m$", + "^\u{1b}[37mm\u{1b}[39m$", + "^\u{1b}[37mb\u{1b}[39m$", + "^\u{1b}[37mi\u{1b}[39m$", + "^\u{1b}[37ma\u{1b}[39m$" + ] + ) + } + + #[cfg(feature = "color")] + #[test] + fn chunks_wrap_2() { + let text = "\u{1b}[30mDebian\u{1b}[0m\u{1b}[31mDebian\u{1b}[0m\u{1b}[32mDebian\u{1b}[0m\u{1b}[33mDebian\u{1b}[0m\u{1b}[34mDebian\u{1b}[0m\u{1b}[35mDebian\u{1b}[0m\u{1b}[36mDebian\u{1b}[0m\u{1b}[37mDebian\u{1b}[0m\u{1b}[40mDebian\u{1b}[0m\u{1b}[41mDebian\u{1b}[0m\u{1b}[42mDebian\u{1b}[0m\u{1b}[43mDebian\u{1b}[0m\u{1b}[44mDebian\u{1b}[0m"; + assert_eq!( + chunks(text, 30, "", ""), + [ + "\u{1b}[30mDebian\u{1b}[39m\u{1b}[31mDebian\u{1b}[39m\u{1b}[32mDebian\u{1b}[39m\u{1b}[33mDebian\u{1b}[39m\u{1b}[34mDebian\u{1b}[39m", + "\u{1b}[35mDebian\u{1b}[39m\u{1b}[36mDebian\u{1b}[39m\u{1b}[37mDebian\u{1b}[39m\u{1b}[40mDebian\u{1b}[49m\u{1b}[41mDebian\u{1b}[49m", + "\u{1b}[42mDebian\u{1b}[49m\u{1b}[43mDebian\u{1b}[49m\u{1b}[44mDebian\u{1b}[49m", + ] + ); + } + + #[cfg(feature = "color")] + #[test] + fn chunks_wrap_3() { + let text = "\u{1b}[37mCreate bytes from the \u{1b}[0m\u{1b}[7;34marg\u{1b}[0m\u{1b}[37muments.\u{1b}[0m"; + + assert_eq!( + chunks(text, 22, "", ""), + [ + "\u{1b}[37mCreate bytes from the \u{1b}[39m", + "\u{1b}[7m\u{1b}[34marg\u{1b}[27m\u{1b}[39m\u{1b}[37muments.\u{1b}[39m" + ] + ); + } + + #[cfg(feature = "color")] + #[test] + fn chunks_wrap_3_keeping_words() { + let text = "\u{1b}[37mCreate bytes from the \u{1b}[0m\u{1b}[7;34marg\u{1b}[0m\u{1b}[37muments.\u{1b}[0m"; + + assert_eq!( + split_keeping_words(text, 22, "", ""), + "\u{1b}[37mCreate bytes from the \u{1b}[39m\n\u{1b}[7m\u{1b}[34marg\u{1b}[27m\u{1b}[39m\u{1b}[37muments.\u{1b}[39m " + ); + } + + #[cfg(feature = "color")] + #[test] + fn chunks_wrap_4() { + let text = "\u{1b}[37mReturns the floor of a number (l\u{1b}[0m\u{1b}[41;37marg\u{1b}[0m\u{1b}[37mest integer less than or equal to that number).\u{1b}[0m"; + + assert_eq!( + chunks(text, 10, "", ""), + [ + "\u{1b}[37mReturns th\u{1b}[39m", + "\u{1b}[37me floor of\u{1b}[39m", + "\u{1b}[37m a number \u{1b}[39m", + "\u{1b}[37m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest i\u{1b}[39m", + "\u{1b}[37mnteger les\u{1b}[39m", + "\u{1b}[37ms than or \u{1b}[39m", + "\u{1b}[37mequal to t\u{1b}[39m", + "\u{1b}[37mhat number\u{1b}[39m", + "\u{1b}[37m).\u{1b}[39m", + ] + ); + } + + #[cfg(feature = "color")] + #[test] + fn chunks_wrap_4_keeping_words() { + let text = "\u{1b}[37mReturns the floor of a number (l\u{1b}[0m\u{1b}[41;37marg\u{1b}[0m\u{1b}[37mest integer less than or equal to that number).\u{1b}[0m"; + assert_eq!( + split_keeping_words(text, 10, "", ""), + concat!( + "\u{1b}[37mReturns \u{1b}[39m \n", + "\u{1b}[37mthe floor \u{1b}[39m\n", + "\u{1b}[37mof a \u{1b}[39m \n", + "\u{1b}[37mnumber \u{1b}[39m \n", + "\u{1b}[37m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest \u{1b}[39m \n", + "\u{1b}[37minteger \u{1b}[39m \n", + "\u{1b}[37mless than \u{1b}[39m\n", + "\u{1b}[37mor equal \u{1b}[39m \n", + "\u{1b}[37mto that \u{1b}[39m \n", + "\u{1b}[37mnumber).\u{1b}[39m ", + ) + ); + } +} + +// \u{1b}[37mReturns \u{1b}[39m\n +// \u{1b}[37mthe floor \u{1b}[39m\n +// \u{1b}[37mof a \u{1b}[39m\n +// \u{1b}[37mnumber \u{1b}[39m\u{1b}[49m\n +// \u{1b}[37m\u{1b}[41m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest \u{1b}[39m\n +// \u{1b}[37minteger \u{1b}[39m\n +// \u{1b}[37mless than \u{1b}[39m\n +// \u{1b}[37mor equal \u{1b}[39m\n +// \u{1b}[37mto that \u{1b}[39m\n +// \u{1b}[37mnumber).\u{1b}[39m " + +// +// + +// \u{1b}[37mReturns \u{1b}[39m\n +// \u{1b}[37mthe floor \u{1b}[39m\n +// \u{1b}[37mof a \u{1b}[39m\n +// \u{1b}[37mnumber \u{1b}[39m\u{1b}[49m\n +// \u{1b}[37m\u{1b}[41m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest \u{1b}[39m\n +// \u{1b}[37minteger \u{1b}[39m\n +// \u{1b}[37mless than \u{1b}[39m\n +// \u{1b}[37mor equal \u{1b}[39m\n +// \u{1b}[37mto that \u{1b}[39m\n +// \u{1b}[37mnumber).\u{1b}[39m " + +// "\u{1b}[37mReturns\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mthe\u{1b}[37m floor\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mof\u{1b}[37m a\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mnumber\u{1b}[37m \u{1b}[39m\u{1b}[49m\n +// \u{1b}[37m\u{1b}[41m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37minteger\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mless\u{1b}[37m than\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mor\u{1b}[37m equal\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mto\u{1b}[37m that\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mnumber).\u{1b}[39m " diff --git a/vendor/tabled/src/tabled.rs b/vendor/tabled/src/tabled.rs new file mode 100644 index 000000000..636173dc0 --- /dev/null +++ b/vendor/tabled/src/tabled.rs @@ -0,0 +1,150 @@ +use std::borrow::Cow; + +/// Tabled a trait responsible for providing a header fields and a row fields. +/// +/// It's urgent that `header` len is equal to `fields` len. +/// +/// ```text +/// Self::headers().len() == self.fields().len() +/// ``` +pub trait Tabled { + /// A length of fields and headers, + /// which must be the same. + const LENGTH: usize; + + /// Fields method must return a list of cells. + /// + /// The cells will be placed in the same row, preserving the order. + fn fields(&self) -> Vec<Cow<'_, str>>; + /// Headers must return a list of column names. + fn headers() -> Vec<Cow<'static, str>>; +} + +impl<T> Tabled for &T +where + T: Tabled, +{ + const LENGTH: usize = T::LENGTH; + + fn fields(&self) -> Vec<Cow<'_, str>> { + T::fields(self) + } + fn headers() -> Vec<Cow<'static, str>> { + T::headers() + } +} + +impl<T> Tabled for Box<T> +where + T: Tabled, +{ + const LENGTH: usize = T::LENGTH; + + fn fields(&self) -> Vec<Cow<'_, str>> { + T::fields(self) + } + fn headers() -> Vec<Cow<'static, str>> { + T::headers() + } +} + +macro_rules! tuple_table { + ( $($name:ident)+ ) => { + impl<$($name: Tabled),+> Tabled for ($($name,)+){ + const LENGTH: usize = $($name::LENGTH+)+ 0; + + fn fields(&self) -> Vec<Cow<'_, str>> { + #![allow(non_snake_case)] + let ($($name,)+) = self; + let mut fields = Vec::with_capacity(Self::LENGTH); + $(fields.append(&mut $name.fields());)+ + fields + } + + fn headers() -> Vec<Cow<'static, str>> { + let mut fields = Vec::with_capacity(Self::LENGTH); + $(fields.append(&mut $name::headers());)+ + fields + } + } + }; +} + +tuple_table! { A } +tuple_table! { A B } +tuple_table! { A B C } +tuple_table! { A B C D } +tuple_table! { A B C D E } +tuple_table! { A B C D E F } + +macro_rules! default_table { + ( $t:ty ) => { + impl Tabled for $t { + const LENGTH: usize = 1; + + fn fields(&self) -> Vec<Cow<'_, str>> { + vec![Cow::Owned(self.to_string())] + } + fn headers() -> Vec<Cow<'static, str>> { + vec![Cow::Borrowed(stringify!($t))] + } + } + }; + + ( $t:ty = borrowed ) => { + impl Tabled for $t { + const LENGTH: usize = 1; + + fn fields(&self) -> Vec<Cow<'_, str>> { + vec![Cow::Borrowed(self)] + } + fn headers() -> Vec<Cow<'static, str>> { + vec![Cow::Borrowed(stringify!($t))] + } + } + }; +} + +default_table!(&str = borrowed); +default_table!(str = borrowed); +default_table!(String); + +default_table!(char); + +default_table!(bool); + +default_table!(isize); +default_table!(usize); + +default_table!(u8); +default_table!(u16); +default_table!(u32); +default_table!(u64); +default_table!(u128); + +default_table!(i8); +default_table!(i16); +default_table!(i32); +default_table!(i64); +default_table!(i128); + +default_table!(f32); +default_table!(f64); + +impl<T, const N: usize> Tabled for [T; N] +where + T: std::fmt::Display, +{ + const LENGTH: usize = N; + + fn fields(&self) -> Vec<Cow<'_, str>> { + self.iter() + .map(ToString::to_string) + .map(Cow::Owned) + .collect() + } + + fn headers() -> Vec<Cow<'static, str>> { + (0..N).map(|i| Cow::Owned(format!("{i}"))).collect() + } +} 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(()) + } +} |