diff options
Diffstat (limited to 'vendor/tabled/src/builder')
-rw-r--r-- | vendor/tabled/src/builder/index_builder.rs | 318 | ||||
-rw-r--r-- | vendor/tabled/src/builder/mod.rs | 118 | ||||
-rw-r--r-- | vendor/tabled/src/builder/table_builder.rs | 506 |
3 files changed, 942 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())); +} |