From ef24de24a82fe681581cc130f342363c47c0969a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 7 Jun 2024 07:48:48 +0200 Subject: Merging upstream version 1.75.0+dfsg1. Signed-off-by: Daniel Baumann --- vendor/tabled/src/settings/split/mod.rs | 420 ++++++++++++++++++++++++++++++++ 1 file changed, 420 insertions(+) create mode 100644 vendor/tabled/src/settings/split/mod.rs (limited to 'vendor/tabled/src/settings/split') 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 TableOption 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(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(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(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( + 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, Range) { + 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) +} -- cgit v1.2.3