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/alignment/mod.rs | 189 +++ vendor/tabled/src/settings/cell_option.rs | 55 + vendor/tabled/src/settings/color/mod.rs | 346 +++++ vendor/tabled/src/settings/concat/mod.rs | 171 +++ vendor/tabled/src/settings/disable/mod.rs | 196 +++ vendor/tabled/src/settings/duplicate/mod.rs | 150 ++ vendor/tabled/src/settings/extract/mod.rs | 257 ++++ vendor/tabled/src/settings/format/format_config.rs | 14 + .../tabled/src/settings/format/format_content.rs | 86 ++ .../src/settings/format/format_positioned.rs | 51 + vendor/tabled/src/settings/format/mod.rs | 144 ++ .../src/settings/formatting/alignment_strategy.rs | 172 +++ vendor/tabled/src/settings/formatting/charset.rs | 108 ++ .../src/settings/formatting/justification.rs | 127 ++ vendor/tabled/src/settings/formatting/mod.rs | 20 + vendor/tabled/src/settings/formatting/tab_size.rs | 62 + .../src/settings/formatting/trim_strategy.rs | 118 ++ .../src/settings/height/cell_height_increase.rs | 98 ++ .../src/settings/height/cell_height_limit.rs | 103 ++ vendor/tabled/src/settings/height/height_list.rs | 47 + vendor/tabled/src/settings/height/mod.rs | 228 +++ .../src/settings/height/table_height_increase.rs | 90 ++ .../src/settings/height/table_height_limit.rs | 123 ++ vendor/tabled/src/settings/height/util.rs | 22 + vendor/tabled/src/settings/highlight/mod.rs | 452 ++++++ vendor/tabled/src/settings/locator/mod.rs | 202 +++ vendor/tabled/src/settings/margin/mod.rs | 137 ++ vendor/tabled/src/settings/measurement/mod.rs | 161 +++ vendor/tabled/src/settings/merge/mod.rs | 196 +++ vendor/tabled/src/settings/mod.rs | 135 ++ vendor/tabled/src/settings/modify.rs | 81 ++ vendor/tabled/src/settings/object/cell.rs | 70 + vendor/tabled/src/settings/object/columns.rs | 209 +++ vendor/tabled/src/settings/object/frame.rs | 69 + vendor/tabled/src/settings/object/mod.rs | 765 ++++++++++ vendor/tabled/src/settings/object/rows.rs | 213 +++ vendor/tabled/src/settings/object/segment.rs | 151 ++ vendor/tabled/src/settings/object/util.rs | 22 + vendor/tabled/src/settings/padding/mod.rs | 164 +++ vendor/tabled/src/settings/panel/footer.rs | 32 + vendor/tabled/src/settings/panel/header.rs | 32 + .../tabled/src/settings/panel/horizontal_panel.rs | 80 ++ vendor/tabled/src/settings/panel/mod.rs | 127 ++ vendor/tabled/src/settings/panel/vertical_panel.rs | 83 ++ vendor/tabled/src/settings/peaker/mod.rs | 94 ++ vendor/tabled/src/settings/rotate/mod.rs | 155 +++ vendor/tabled/src/settings/settings_list.rs | 71 + vendor/tabled/src/settings/shadow/mod.rs | 195 +++ vendor/tabled/src/settings/span/column.rs | 125 ++ vendor/tabled/src/settings/span/mod.rs | 68 + vendor/tabled/src/settings/span/row.rs | 126 ++ vendor/tabled/src/settings/split/mod.rs | 420 ++++++ vendor/tabled/src/settings/style/border.rs | 159 +++ vendor/tabled/src/settings/style/border_char.rs | 99 ++ vendor/tabled/src/settings/style/border_color.rs | 155 +++ vendor/tabled/src/settings/style/border_text.rs | 253 ++++ vendor/tabled/src/settings/style/builder.rs | 1208 ++++++++++++++++ .../tabled/src/settings/style/horizontal_line.rs | 69 + vendor/tabled/src/settings/style/line.rs | 91 ++ vendor/tabled/src/settings/style/mod.rs | 127 ++ vendor/tabled/src/settings/style/offset.rs | 19 + vendor/tabled/src/settings/style/raw_style.rs | 438 ++++++ .../src/settings/style/span_border_correction.rs | 216 +++ vendor/tabled/src/settings/style/vertical_line.rs | 50 + vendor/tabled/src/settings/table_option.rs | 30 + vendor/tabled/src/settings/themes/colorization.rs | 388 ++++++ vendor/tabled/src/settings/themes/column_names.rs | 355 +++++ vendor/tabled/src/settings/themes/mod.rs | 9 + vendor/tabled/src/settings/width/justify.rs | 96 ++ vendor/tabled/src/settings/width/min_width.rs | 205 +++ vendor/tabled/src/settings/width/mod.rs | 163 +++ vendor/tabled/src/settings/width/truncate.rs | 505 +++++++ vendor/tabled/src/settings/width/util.rs | 265 ++++ vendor/tabled/src/settings/width/width_list.rs | 50 + vendor/tabled/src/settings/width/wrap.rs | 1468 ++++++++++++++++++++ 75 files changed, 14030 insertions(+) create mode 100644 vendor/tabled/src/settings/alignment/mod.rs create mode 100644 vendor/tabled/src/settings/cell_option.rs create mode 100644 vendor/tabled/src/settings/color/mod.rs create mode 100644 vendor/tabled/src/settings/concat/mod.rs create mode 100644 vendor/tabled/src/settings/disable/mod.rs create mode 100644 vendor/tabled/src/settings/duplicate/mod.rs create mode 100644 vendor/tabled/src/settings/extract/mod.rs create mode 100644 vendor/tabled/src/settings/format/format_config.rs create mode 100644 vendor/tabled/src/settings/format/format_content.rs create mode 100644 vendor/tabled/src/settings/format/format_positioned.rs create mode 100644 vendor/tabled/src/settings/format/mod.rs create mode 100644 vendor/tabled/src/settings/formatting/alignment_strategy.rs create mode 100644 vendor/tabled/src/settings/formatting/charset.rs create mode 100644 vendor/tabled/src/settings/formatting/justification.rs create mode 100644 vendor/tabled/src/settings/formatting/mod.rs create mode 100644 vendor/tabled/src/settings/formatting/tab_size.rs create mode 100644 vendor/tabled/src/settings/formatting/trim_strategy.rs create mode 100644 vendor/tabled/src/settings/height/cell_height_increase.rs create mode 100644 vendor/tabled/src/settings/height/cell_height_limit.rs create mode 100644 vendor/tabled/src/settings/height/height_list.rs create mode 100644 vendor/tabled/src/settings/height/mod.rs create mode 100644 vendor/tabled/src/settings/height/table_height_increase.rs create mode 100644 vendor/tabled/src/settings/height/table_height_limit.rs create mode 100644 vendor/tabled/src/settings/height/util.rs create mode 100644 vendor/tabled/src/settings/highlight/mod.rs create mode 100644 vendor/tabled/src/settings/locator/mod.rs create mode 100644 vendor/tabled/src/settings/margin/mod.rs create mode 100644 vendor/tabled/src/settings/measurement/mod.rs create mode 100644 vendor/tabled/src/settings/merge/mod.rs create mode 100644 vendor/tabled/src/settings/mod.rs create mode 100644 vendor/tabled/src/settings/modify.rs create mode 100644 vendor/tabled/src/settings/object/cell.rs create mode 100644 vendor/tabled/src/settings/object/columns.rs create mode 100644 vendor/tabled/src/settings/object/frame.rs create mode 100644 vendor/tabled/src/settings/object/mod.rs create mode 100644 vendor/tabled/src/settings/object/rows.rs create mode 100644 vendor/tabled/src/settings/object/segment.rs create mode 100644 vendor/tabled/src/settings/object/util.rs create mode 100644 vendor/tabled/src/settings/padding/mod.rs create mode 100644 vendor/tabled/src/settings/panel/footer.rs create mode 100644 vendor/tabled/src/settings/panel/header.rs create mode 100644 vendor/tabled/src/settings/panel/horizontal_panel.rs create mode 100644 vendor/tabled/src/settings/panel/mod.rs create mode 100644 vendor/tabled/src/settings/panel/vertical_panel.rs create mode 100644 vendor/tabled/src/settings/peaker/mod.rs create mode 100644 vendor/tabled/src/settings/rotate/mod.rs create mode 100644 vendor/tabled/src/settings/settings_list.rs create mode 100644 vendor/tabled/src/settings/shadow/mod.rs create mode 100644 vendor/tabled/src/settings/span/column.rs create mode 100644 vendor/tabled/src/settings/span/mod.rs create mode 100644 vendor/tabled/src/settings/span/row.rs create mode 100644 vendor/tabled/src/settings/split/mod.rs create mode 100644 vendor/tabled/src/settings/style/border.rs create mode 100644 vendor/tabled/src/settings/style/border_char.rs create mode 100644 vendor/tabled/src/settings/style/border_color.rs create mode 100644 vendor/tabled/src/settings/style/border_text.rs create mode 100644 vendor/tabled/src/settings/style/builder.rs create mode 100644 vendor/tabled/src/settings/style/horizontal_line.rs create mode 100644 vendor/tabled/src/settings/style/line.rs create mode 100644 vendor/tabled/src/settings/style/mod.rs create mode 100644 vendor/tabled/src/settings/style/offset.rs create mode 100644 vendor/tabled/src/settings/style/raw_style.rs create mode 100644 vendor/tabled/src/settings/style/span_border_correction.rs create mode 100644 vendor/tabled/src/settings/style/vertical_line.rs create mode 100644 vendor/tabled/src/settings/table_option.rs create mode 100644 vendor/tabled/src/settings/themes/colorization.rs create mode 100644 vendor/tabled/src/settings/themes/column_names.rs create mode 100644 vendor/tabled/src/settings/themes/mod.rs create mode 100644 vendor/tabled/src/settings/width/justify.rs create mode 100644 vendor/tabled/src/settings/width/min_width.rs create mode 100644 vendor/tabled/src/settings/width/mod.rs create mode 100644 vendor/tabled/src/settings/width/truncate.rs create mode 100644 vendor/tabled/src/settings/width/util.rs create mode 100644 vendor/tabled/src/settings/width/width_list.rs create mode 100644 vendor/tabled/src/settings/width/wrap.rs (limited to 'vendor/tabled/src/settings') 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 crate::settings::CellOption 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 TableOption 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 TableOption 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 TableOption 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 { + /// Modification function of a single cell. + fn change(self, records: &mut R, cfg: &mut C, entity: Entity); +} + +#[cfg(feature = "std")] +impl CellOption for String +where + R: Records + ExactRecords + RecordsMut, +{ + fn change(self, records: &mut R, cfg: &mut C, entity: Entity) { + (&self).change(records, cfg, entity); + } +} + +#[cfg(feature = "std")] +impl CellOption for &String +where + R: Records + ExactRecords + RecordsMut, +{ + 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 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 for AnsiColor<'static> { + fn from(c: Color) -> Self { + c.0 + } +} + +impl From> for Color { + fn from(c: AnsiColor<'static>) -> Self { + Self(c) + } +} + +impl From 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 { + AnsiColor::try_from(value).map(Color) + } +} + +#[cfg(feature = "color")] +impl std::convert::TryFrom for Color { + type Error = (); + + fn try_from(value: String) -> Result { + AnsiColor::try_from(value).map(Color) + } +} + +impl TableOption for Color { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let _ = cfg.set_color(Entity::Global, self.0.clone()); + } +} + +impl CellOption for Color { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let _ = cfg.set_color(entity, self.0.clone()); + } +} + +impl CellOption 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(&self, f: &mut W) -> std::fmt::Result { + self.0.fmt_prefix(f) + } + + fn fmt_suffix(&self, f: &mut W) -> std::fmt::Result { + self.0.fmt_suffix(f) + } + + fn colorize(&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>) -> Self { + self.default_cell = cell.into(); + self + } +} + +impl TableOption for Concat +where + R: Records + ExactRecords + Resizable + PeekableRecords + RecordsMut, +{ + 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 { + locator: L, + target: PhantomData, +} + +impl Disable { + /// 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 Disable { + /// 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 TableOption for Disable +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::>(); + + 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 TableOption for Disable +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::>(); + + 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 { + src: Src, + dst: Dst, +} + +impl Dup { + /// 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 TableOption for Dup +where + Dst: Object, + Src: Object, + R: Records + ExactRecords + PeekableRecords + RecordsMut, +{ + 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(records: &mut R, src: O) -> Vec +where + O: Object, + R: Records + ExactRecords + PeekableRecords + RecordsMut, +{ + 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(records: &mut R, src: &[String], dst: O) +where + O: Object, + R: Records + ExactRecords + PeekableRecords + RecordsMut, +{ + 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 { + rows: R, + columns: C, +} + +impl Extract +where + R: RangeBounds, + C: RangeBounds, +{ + /// 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 Extract +where + R: RangeBounds, +{ + /// 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 Extract +where + C: RangeBounds, +{ + /// 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 TableOption for Extract +where + R: RangeBounds + Clone, + C: RangeBounds + 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( + 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(pub(crate) F); + +impl TableOption for FormatConfig +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, + multiline: bool, +} + +impl FormatContent { + pub(crate) fn new(f: F) -> Self { + Self { + f, + multiline: false, + } + } +} + +impl FormatContent { + /// 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 TableOption for FormatContent +where + F: FnMut(&str) -> String + Clone, + R: Records + ExactRecords + PeekableRecords + RecordsMut, +{ + fn change(self, records: &mut R, cfg: &mut C, _: &mut D) { + CellOption::change(self, records, cfg, Entity::Global); + } +} + +impl CellOption for FormatContent +where + F: FnMut(&str) -> String + Clone, + R: Records + ExactRecords + PeekableRecords + RecordsMut, +{ + 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 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); + +impl FormatContentPositioned { + pub(crate) fn new(f: F) -> Self { + Self(f) + } +} + +impl TableOption for FormatContentPositioned +where + F: FnMut(&str, (usize, usize)) -> String, + R: Records + ExactRecords + PeekableRecords + RecordsMut, +{ + fn change(self, records: &mut R, cfg: &mut C, _: &mut D) { + CellOption::change(self, records, cfg, Entity::Global); + } +} + +impl CellOption for FormatContentPositioned +where + F: FnMut(&str, (usize, usize)) -> String, + R: Records + ExactRecords + PeekableRecords + RecordsMut, +{ + 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) -> FormatContent + 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) -> FormatContentPositioned + 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) -> FormatConfig { + 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 CellOption 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 TableOption for AlignmentStrategy { + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + >::change(self, records, cfg, Entity::Global) + } +} + +impl TableOption 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 TableOption for CleanCharset +where + for<'a> &'a R: Records, + R: RecordsMut, +{ + 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 CellOption for CleanCharset +where + R: Records + ExactRecords + PeekableRecords + RecordsMut, +{ + 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 `' '` (``) 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, + color: Option>, +} + +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 TableOption 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 CellOption 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 TableOption for TabSize +where + for<'a> &'a R: Records, + R: RecordsMut, +{ + 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 CellOption 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 TableOption for TrimStrategy { + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + >::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 { + height: W, +} + +impl CellHeightIncrease { + /// Creates a new object of the structure. + pub fn new(height: W) -> Self + where + W: Measurement, + { + Self { height } + } + + /// The priority makes scence only for table, so the function + /// converts it to [`TableHeightIncrease`] with a given priority. + pub fn priority

(self) -> TableHeightIncrease + where + P: Peaker, + W: Measurement, + { + TableHeightIncrease::new(self.height).priority::

() + } +} + +impl CellOption for CellHeightIncrease +where + W: Measurement, + R: Records + ExactRecords + PeekableRecords + RecordsMut, + 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 TableOption, ColoredConfig> + for CellHeightIncrease +where + W: Measurement, + 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 { + height: W, +} + +impl CellHeightLimit { + /// Constructs a new object. + pub fn new(height: W) -> Self + where + W: Measurement, + { + Self { height } + } + + /// Set's a priority by which the limit logic will be applied. + pub fn priority

(self) -> TableHeightLimit + where + P: Peaker, + W: Measurement, + { + TableHeightLimit::new(self.height).priority::

() + } +} + +impl CellOption for CellHeightLimit +where + W: Measurement, + R: Records + ExactRecords + PeekableRecords + RecordsMut, + 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 TableOption, ColoredConfig> + for CellHeightLimit +where + W: Measurement, + R: Records + ExactRecords + PeekableRecords + RecordsMut, + 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, +} + +impl HeightList { + /// Creates a new object. + pub fn new(list: Vec) -> Self { + Self { list } + } +} + +impl From> for HeightList { + fn from(list: Vec) -> Self { + Self::new(list) + } +} + +impl FromIterator for HeightList { + fn from_iter>(iter: T) -> Self { + Self::new(iter.into_iter().collect()) + } +} + +impl TableOption, 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>(width: W) -> CellHeightIncrease { + 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>(width: W) -> CellHeightLimit { + 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>(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 { + height: W, + priority: P, +} + +impl TableHeightIncrease { + /// Creates a new object. + pub fn new(height: W) -> Self + where + W: Measurement, + { + Self { + height, + priority: PriorityNone::default(), + } + } + + /// Sets a different priority logic. + pub fn priority

(self) -> TableHeightIncrease + where + P: Peaker, + { + TableHeightIncrease { + priority: P::create(), + height: self.height, + } + } +} + +impl TableOption, ColoredConfig> + for TableHeightIncrease +where + W: Measurement, + 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

(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 { + height: W, + priority: P, +} + +impl TableHeightLimit { + /// Creates a new object. + pub fn new(height: W) -> Self + where + W: Measurement, + { + Self { + height, + priority: PriorityNone::default(), + } + } + + /// Sets a different priority logic. + pub fn priority

(self) -> TableHeightLimit + where + P: Peaker, + { + TableHeightLimit { + priority: P::create(), + height: self.height, + } + } +} + +impl TableOption, ColoredConfig> + for TableHeightLimit +where + W: Measurement, + P: Peaker + Clone, + R: ExactRecords + PeekableRecords + RecordsMut, + 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

(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( + records: R, + cfg: &SpannedConfig, +) -> (usize, Vec) { + 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::(); + + 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 { + target: O, + border: Border, +} + +// todo: Add BorderColor. + +impl Highlight { + /// 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 Highlight { + /// Build a new instance of [`HighlightColored`] + pub fn colored(target: O, border: BorderColor) -> HighlightColored { + HighlightColored { target, border } + } +} + +impl TableOption for Highlight +where + O: Object, + 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 { + target: O, + border: BorderColor, +} + +impl TableOption for HighlightColored +where + O: Object, + 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, + count_rows: usize, + count_cols: usize, +) -> Vec> { + let mut segments: Vec> = 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> = 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( + sector: &HashSet<(usize, usize)>, + (row, col): Position, + border: &GridBorder, +) -> GridBorder +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 { + /// 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; + + /// Search for the thing in [`Records`], returning a list of coordinates. + fn locate(&mut self, records: Records) -> Self::IntoIter; +} + +impl Locator for Columns +where + B: RangeBounds, + R: Records, +{ + type Coordinate = usize; + type IntoIter = Range; + + 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 Locator for Column { + type Coordinate = usize; + type IntoIter = Once; + + fn locate(&mut self, _: R) -> Self::IntoIter { + iter::once((*self).into()) + } +} + +impl Locator for FirstColumn { + type Coordinate = usize; + type IntoIter = Once; + + fn locate(&mut self, _: R) -> Self::IntoIter { + iter::once(0) + } +} + +impl Locator for LastColumn +where + R: Records, +{ + type Coordinate = usize; + type IntoIter = Once; + + fn locate(&mut self, records: R) -> Self::IntoIter { + if records.count_columns() > 0 { + iter::once(records.count_columns() - 1) + } else { + iter::once(0) + } + } +} + +impl Locator for Rows +where + R: Records, + B: RangeBounds, +{ + type Coordinate = usize; + type IntoIter = Range; + + 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 Locator for Row { + type Coordinate = usize; + type IntoIter = Once; + + fn locate(&mut self, _: R) -> Self::IntoIter { + iter::once((*self).into()) + } +} + +impl Locator for FirstRow { + type Coordinate = usize; + type IntoIter = Once; + + fn locate(&mut self, _: R) -> Self::IntoIter { + iter::once(0) + } +} + +impl Locator for LastRow +where + R: ExactRecords, +{ + type Coordinate = usize; + type IntoIter = Once; + + 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); + +impl ByColumnName { + /// Constructs a new object of the structure. + pub fn new(text: S) -> Self + where + S: AsRef, + { + Self(text) + } +} + +impl Locator for ByColumnName +where + S: AsRef, + R: Records + ExactRecords + PeekableRecords, +{ + type Coordinate = usize; + type IntoIter = Vec; + + 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::>() + } +} + +impl Object for ByColumnName +where + S: AsRef, + R: Records + PeekableRecords + ExactRecords, +{ + type Iter = std::vec::IntoIter; + + 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::>() + .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 { + indent: Sides, + colors: Option>, +} + +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 Margin { + /// 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(self, left: C, right: C, top: C, bottom: C) -> Margin { + Margin { + indent: self.indent, + colors: Some(Sides::new(left, right, top, bottom)), + } + } +} + +#[cfg(feature = "std")] +impl TableOption for Margin +where + C: Into> + 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 TableOption for Margin +where + C: Into + 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 TableOption for Margin +where + C: Into + 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 { + /// Returns a measurement value. + fn measure( + &self, + records: R, + cfg: &SpannedConfig, + ) -> usize; +} + +impl Measurement for usize { + fn measure(&self, _: R, _: &SpannedConfig) -> usize { + *self + } +} + +/// Max width value. +#[derive(Debug)] +pub struct Max; + +impl Measurement for Max { + fn measure( + &self, + records: R, + _: &SpannedConfig, + ) -> usize { + grid_widths(&records) + .map(|r| r.max().unwrap_or(0)) + .max() + .unwrap_or(0) + } +} + +impl Measurement for Max { + fn measure( + &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 for Min { + fn measure( + &self, + records: R, + _: &SpannedConfig, + ) -> usize { + grid_widths(&records) + .map(|r| r.min().unwrap_or(0)) + .max() + .unwrap_or(0) + } +} + +impl Measurement for Min { + fn measure( + &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 for Percent { + fn measure(&self, records: R, cfg: &SpannedConfig) -> usize + where + R: Records, + { + let (_, total) = get_table_widths_with_total(records, cfg); + (total * self.0) / 100 + } +} + +impl Measurement for Percent { + fn measure(&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( + records: &R, +) -> impl Iterator + '_> + '_ { + 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(records: R, cfg: &SpannedConfig) -> (Vec, 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::(); + + total + cfg.count_vertical(list.len()) +} + +fn records_heights(records: &R) -> impl Iterator + '_> + '_ +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(records: R, cfg: &SpannedConfig) -> (Vec, 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::(); + 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 TableOption 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 TableOption 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 { + obj: O, +} + +impl Modify { + /// 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(obj: O, next: M) -> ModifyList { + 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(self, next: M) -> ModifyList { + 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 { + obj: O, + modifiers: S, +} + +impl ModifyList { + /// 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(self, next: M2) -> ModifyList> { + ModifyList { + obj: self.obj, + modifiers: Settings::new(self.modifiers, next), + } + } +} + +impl TableOption for ModifyList +where + O: Object, + M: CellOption + 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 for Cell { + fn from((row, col): Position) -> Self { + Self(row, col) + } +} + +impl Object for Cell { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Cell(self.0, self.1))) + } +} + +impl Object 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, +} + +impl EntityOnce { + pub(crate) const fn new(entity: Option) -> Self { + Self { entity } + } +} + +impl Iterator for EntityOnce { + type Item = Entity; + + fn next(&mut self) -> Option { + 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 { + range: R, +} + +impl Columns { + /// 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, + { + 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 Object for Columns +where + R: RangeBounds, + 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 Object 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 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 Object 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 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 Object for Column { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Column(self.0))) + } +} + +impl From for Column { + fn from(i: usize) -> Self { + Self(i) + } +} + +impl From 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 Object 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 { + 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 Object 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 { + 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 { + /// An [`Iterator`] which returns a list of cells. + type Iter: Iterator; + + /// Cells returns a set of coordinates of cells. + fn cells(&self, records: &R) -> Self::Iter; + + /// Combines cells. + /// It doesn't repeat cells. + fn and(self, rhs: O) -> UnionCombination + where + Self: Sized, + { + UnionCombination::new(self, rhs) + } + + /// Excludes rhs cells from this cells. + fn not(self, rhs: O) -> DiffCombination + where + Self: Sized, + { + DiffCombination::new(self, rhs) + } + + /// Returns cells which are present in both [`Object`]s only. + fn intersect(self, rhs: O) -> IntersectionCombination + where + Self: Sized, + { + IntersectionCombination::new(self, rhs) + } + + /// Returns cells which are not present in target [`Object`]. + fn inverse(self) -> InversionCombination + 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 { + lhs: L, + rhs: R, + _records: PhantomData, +} + +impl UnionCombination { + fn new(lhs: L, rhs: R) -> Self { + Self { + lhs, + rhs, + _records: PhantomData, + } + } +} + +impl Object for UnionCombination +where + L: Object, + R: Object, + I: Records + ExactRecords, +{ + type Iter = UnionIter; + + 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 { + lhs: L, + rhs: R, + _records: PhantomData, +} + +impl DiffCombination { + fn new(lhs: L, rhs: R) -> Self { + Self { + lhs, + rhs, + _records: PhantomData, + } + } +} + +impl Object for DiffCombination +where + L: Object, + R: Object, + I: Records + ExactRecords, +{ + type Iter = DiffIter; + + 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 { + lhs: L, + rhs: R, + _records: PhantomData, +} + +impl IntersectionCombination { + fn new(lhs: L, rhs: R) -> Self { + Self { + lhs, + rhs, + _records: PhantomData, + } + } +} + +impl Object for IntersectionCombination +where + L: Object, + R: Object, + I: Records + ExactRecords, +{ + type Iter = IntersectIter; + + 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 { + obj: O, + _records: PhantomData, +} + +impl InversionCombination { + fn new(obj: O) -> Self { + Self { + obj, + _records: PhantomData, + } + } +} + +impl Object for InversionCombination +where + O: Object, + 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 { + lhs: Option, + rhs: R, + seen: HashSet<(usize, usize)>, + current: Option, + count_rows: usize, + count_cols: usize, +} + +impl UnionIter +where + L: Iterator, + R: Iterator, +{ + 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 Iterator for UnionIter +where + L: Iterator, + R: Iterator, +{ + type Item = Entity; + + fn next(&mut self) -> Option { + 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 { + lhs: L, + seen: HashSet<(usize, usize)>, + count_rows: usize, + count_cols: usize, + current: Option, +} + +impl DiffIter +where + L: Iterator, +{ + fn new(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self + where + R: Iterator, + { + 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 Iterator for DiffIter +where + L: Iterator, +{ + type Item = Entity; + + fn next(&mut self) -> Option { + 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 { + lhs: L, + seen: HashSet<(usize, usize)>, + count_rows: usize, + count_cols: usize, + current: Option, +} + +impl IntersectIter +where + L: Iterator, +{ + fn new(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self + where + R: Iterator, + { + 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 Iterator for IntersectIter +where + L: Iterator, +{ + type Item = Entity; + + fn next(&mut self) -> Option { + 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(obj: O, count_rows: usize, count_columns: usize) -> Self + where + O: Iterator, + { + 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 { + 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: O, + count_rows: usize, + count_cols: usize, + ) -> Vec { + let data = vec![vec![String::default(); count_cols]; count_rows]; + let records = VecRecords::new(data); + o.cells(&records).collect::>() + } +} 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 { + range: R, +} + +impl Rows { + /// 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, + { + 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 Object for Rows +where + R: RangeBounds, + 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 Object for Row { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Row(self.index))) + } +} + +impl From 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 Object 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 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 Object 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 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 Object 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 { + 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 { + columns: C, + rows: R, +} + +impl Segment { + /// Returns a table segment on which are present all cells. + pub fn all() -> SegmentAll { + SegmentAll + } +} + +impl Segment +where + C: RangeBounds, + R: RangeBounds, +{ + /// This function builds a [`Segment`]. + pub fn new(rows: R, columns: C) -> Self { + Self { columns, rows } + } +} + +impl Object for Segment +where + C: RangeBounds, + R: RangeBounds, + 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 Object 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 { + 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 { + 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 { + indent: Sides, + colors: Option>, +} + +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 Padding { + /// 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(self, left: C, right: C, top: C, bottom: C) -> Padding { + Padding { + indent: self.indent, + colors: Some(Sides::new(left, right, top, bottom)), + } + } +} + +#[cfg(feature = "std")] +impl CellOption for Padding +where + C: Into> + 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 TableOption for Padding +where + C: Into> + Clone, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + >::change(self, records, cfg, Entity::Global) + } +} + +impl TableOption for Padding +where + C: Into + 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 TableOption for Padding +where + C: Into + 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); + +impl Footer { + /// Creates a new object. + pub fn new(text: S) -> Self + where + S: AsRef, + { + Self(text) + } +} + +impl TableOption for Footer +where + S: AsRef, + R: Records + ExactRecords + Resizable + RecordsMut, +{ + 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); + +impl Header { + /// Creates a new object. + pub fn new(text: S) -> Self + where + S: AsRef, + { + Self(text) + } +} + +impl TableOption for Header +where + S: AsRef, + R: Records + ExactRecords + Resizable + RecordsMut, +{ + 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 { + text: S, + row: usize, +} + +impl HorizontalPanel { + /// Creates a new horizontal panel. + pub fn new(row: usize, text: S) -> Self { + Self { row, text } + } +} + +impl TableOption for HorizontalPanel +where + S: AsRef, + R: Records + ExactRecords + Resizable + RecordsMut, +{ + 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(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>(column: usize, text: S) -> VerticalPanel { + 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>(row: usize, text: S) -> HorizontalPanel { + HorizontalPanel::new(row, text) + } + + /// Creates an horizontal row at first row. + pub fn header>(text: S) -> Header { + Header::new(text) + } + + /// Creates an horizontal row at last row. + pub fn footer>(text: S) -> Footer { + 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 { + text: S, + col: usize, +} + +impl VerticalPanel { + /// Creates a new vertical panel. + pub fn new(col: usize, text: S) -> Self + where + S: AsRef, + { + Self { text, col } + } +} + +impl TableOption for VerticalPanel +where + S: AsRef, + R: Records + ExactRecords + Resizable + RecordsMut, +{ + 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(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; +} + +/// 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 { + 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 { + 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 { + 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 TableOption 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, B); + +impl Default for Settings { + fn default() -> Self { + Self(EmptySettings, EmptySettings) + } +} + +impl Settings<(), ()> { + /// Creates an empty list. + pub const fn empty() -> Settings { + Settings(EmptySettings, EmptySettings) + } +} + +impl Settings { + /// Creates a new combinator. + pub const fn new(settings1: A, settings2: B) -> Settings { + Settings(settings1, settings2) + } + + /// Add an option to a combinator. + pub const fn with(self, settings: C) -> Settings { + Settings(self, settings) + } +} + +#[cfg(feature = "std")] +impl CellOption for Settings +where + A: CellOption, + B: CellOption, +{ + fn change(self, records: &mut R, cfg: &mut C, entity: Entity) { + self.0.change(records, cfg, entity); + self.1.change(records, cfg, entity); + } +} + +impl TableOption for Settings +where + A: TableOption, + B: TableOption, +{ + 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 CellOption for EmptySettings { + fn change(self, _: &mut R, _: &mut C, _: Entity) {} +} + +impl TableOption 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, + color: Option, +} + +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 TableOption 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) { + let mut margin: Sides = 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) { + 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) { + let mut margin: Sides>> = 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 CellOption 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 { + 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 CellOption 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 { + 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 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) +} 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); + +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 CellOption 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> for Border { + fn from(b: GBorder) -> Border { + Border(b) + } +} + +impl From for GBorder { + fn from(value: Border) -> Self { + value.0 + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct EmptyBorder; + +impl CellOption 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 CellOption 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>( + 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>( + 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>); + +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 CellOption 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 TableOption 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 for Border> { + 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 { + text: String, + offset: Offset, + color: Option>, + 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>(text: S) -> Self { + BorderText { + text: text.into(), + line: (), + offset: Offset::Begin(0), + color: None, + } + } +} + +impl BorderText { + /// Set a line on which we will set the text. + pub fn horizontal(self, line: L) -> BorderText { + 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 TableOption for BorderText +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 TableOption for BorderText +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 TableOption for BorderText +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( + cfg: &mut SpannedConfig, + dims: &D, + offset: Offset, + line: usize, + text: &str, + color: &Option>, + 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( + cfg: &SpannedConfig, + dims: &D, + offset: Offset, + count_columns: usize, +) -> Option { + 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(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, VLines = VLineArray<0>> { + borders: Borders, + horizontals: HLines, + verticals: VLines, + _top: PhantomData, + _bottom: PhantomData, + _left: PhantomData, + _right: PhantomData, + _horizontal: PhantomData, + _vertical: PhantomData, +} + +type HLineArray = [HorizontalLine; N]; + +type VLineArray = [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 { + 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 { + 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> { + 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> { + 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 { + 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 { + 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> { + 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 { + Style::new( + create_borders( + Line::full('-', '-', '.', '.'), + Line::full('-', '-', '\'', '\''), + Line::empty(), + Some('|'), + Some('|'), + Some('|'), + ), + [], + [], + ) + } +} + +impl Style { + /// 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 + where + for<'a> &'a mut VLines: IntoIterator, + { + 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 + where + for<'a> &'a mut VLines: IntoIterator, + { + 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 + where + for<'a> &'a mut HLines: IntoIterator, + { + 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 + where + for<'a> &'a mut HLines: IntoIterator, + { + 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 + where + for<'a> &'a mut VLines: IntoIterator, + { + 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 + where + for<'a> &'a mut HLines: IntoIterator, + { + 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(self, lines: NewLines) -> Style + where + NewLines: IntoIterator + 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(self, lines: NewLines) -> Style + where + NewLines: IntoIterator + Clone, + { + Style::new(self.borders, self.horizontals, lines) + } + + /// Removes all horizontal lines set by [`Style::horizontals`] + pub fn remove_horizontals(self) -> Style, VLines> { + Style::new(self.borders, [], self.verticals) + } + + /// Removes all verticals lines set by [`Style::verticals`] + pub fn remove_verticals(self) -> Style> { + Style::new(self.borders, self.horizontals, []) + } +} + +impl Style { + /// 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 Style { + /// 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 Style { + /// 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 Style { + /// 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 Style { + /// 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 Style { + /// 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 Style { + /// 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 Style { + /// 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 Style { + /// 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 Style { + /// Removes top border. + pub fn remove_top( + mut self, + ) -> Style<(), B, L, R, H, V, HLines, VerticalLineIter> + where + VLines: IntoIterator + 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 Style { + /// Removes bottom border. + pub fn remove_bottom( + mut self, + ) -> Style> + where + VLines: IntoIterator + 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 Style { + /// Removes left border. + pub fn remove_left( + mut self, + ) -> Style, VLines> + where + HLines: IntoIterator + 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 Style { + /// Removes right border. + pub fn remove_right( + mut self, + ) -> Style, VLines> + where + HLines: IntoIterator + 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 Style { + /// Removes horizontal split lines. + /// + /// Not including custom split lines. + pub fn remove_horizontal( + mut self, + ) -> Style> + where + VLines: IntoIterator + 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 Style { + /// Removes vertical split lines. + pub fn remove_vertical( + mut self, + ) -> Style, VLines> + where + HLines: IntoIterator + 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 Style { + const fn new(borders: Borders, 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 { + &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 TableOption + for Style +where + HLines: IntoIterator + Clone, + VLines: IntoIterator + 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 TableOption + for Style +where + HLines: IntoIterator + 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 TableOption + for Style +where + HLines: IntoIterator + 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 { + iter: I, + intersection: bool, + left: bool, + right: bool, +} + +impl HorizontalLineIter { + fn new(iter: I, intersection: bool, left: bool, right: bool) -> Self { + Self { + iter, + intersection, + left, + right, + } + } +} + +impl Iterator for HorizontalLineIter +where + I: Iterator, +{ + type Item = HorizontalLine; + + fn next(&mut self) -> Option { + 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 { + iter: I, + intersection: bool, + top: bool, + bottom: bool, +} + +impl VerticalLineIter { + fn new(iter: I, intersection: bool, top: bool, bottom: bool) -> Self { + Self { + iter, + intersection, + top, + bottom, + } + } +} + +impl Iterator for VerticalLineIter +where + I: Iterator, +{ + type Item = VerticalLine; + + fn next(&mut self) -> Option { + 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, + right: Option, + vertical: Option, +) -> Borders { + 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) -> Self { + self.line.main = c; + self + } + + /// Sets a vertical intersection character. + pub const fn intersection(mut self, c: Option) -> Self { + self.line.intersection = c; + self + } + + /// Sets a left character. + pub const fn left(mut self, c: Option) -> Self { + self.line.connector1 = c; + self + } + + /// Sets a right character. + pub const fn right(mut self, c: Option) -> Self { + self.line.connector2 = c; + self + } +} + +#[cfg(feature = "std")] +impl TableOption for HorizontalLine { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + cfg.insert_horizontal_line(self.index, GridLine::from(self.line)) + } +} + +impl TableOption 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 TableOption 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, + pub(crate) intersection: Option, + pub(crate) connector1: Option, + pub(crate) connector2: Option, +} + +impl Line { + /// Creates a new [`Line`] object. + pub const fn new( + main: Option, + intersection: Option, + connector1: Option, + connector2: Option, + ) -> 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 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 for VerticalLine { + fn from(l: Line) -> Self { + Self { + main: l.main, + intersection: l.intersection, + top: l.connector1, + bottom: l.connector2, + } + } +} + +impl From for papergrid::config::Line { + 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 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, + colors: Borders>, + horizontals: HashMap, + verticals: HashMap, +} + +impl RawStyle { + /// Set a top border character. + pub fn set_top(&mut self, s: Option) -> &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) -> &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) -> &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) -> &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) -> &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) -> &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) -> &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) -> &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) -> &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) -> &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) -> &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) -> &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) -> &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) -> &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) -> &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) -> &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 { + 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) -> &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 { + self.borders.left + } + + /// Get a left intersection char. + pub fn get_left_intersection(&self) -> Option { + self.borders.left_intersection + } + + /// Get a right char. + pub fn get_right(&self) -> Option { + self.borders.right + } + + /// Get a right intersection char. + pub fn get_right_intersection(&self) -> Option { + self.borders.right_intersection + } + + /// Get a top char. + pub fn get_top(&self) -> Option { + self.borders.top + } + + /// Get a top left char. + pub fn get_top_left(&self) -> Option { + self.borders.top_left + } + + /// Get a top right char. + pub fn get_top_right(&self) -> Option { + self.borders.top_right + } + + /// Get a top intersection char. + pub fn get_top_intersection(&self) -> Option { + self.borders.top_intersection + } + + /// Get a bottom intersection char. + pub fn get_bottom(&self) -> Option { + self.borders.bottom + } + + /// Get a bottom intersection char. + pub fn get_bottom_left(&self) -> Option { + self.borders.bottom_left + } + + /// Get a bottom intersection char. + pub fn get_bottom_right(&self) -> Option { + self.borders.bottom_right + } + + /// Get a bottom intersection char. + pub fn get_bottom_intersection(&self) -> Option { + 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 { + self.borders + } +} + +impl From> for RawStyle { + fn from(borders: Borders) -> Self { + Self { + borders, + horizontals: HashMap::new(), + verticals: HashMap::new(), + colors: Borders::default(), + } + } +} + +impl TableOption for RawStyle +where + R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) { + (&self).change(records, cfg, dimension) + } +} + +impl TableOption 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 From> for RawStyle +where + HLines: IntoIterator + Clone, + VLines: IntoIterator + Clone, +{ + fn from(style: Style) -> 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 TableOption 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::>(); + 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 + '_ { + // 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) -> Self { + self.line.main = c; + self + } + + /// Sets a vertical intersection character. + pub const fn intersection(mut self, c: Option) -> Self { + self.line.intersection = c; + self + } + + /// Sets a top character. + pub const fn top(mut self, c: Option) -> Self { + self.line.connector1 = c; + self + } + + /// Sets a bottom character. + pub const fn bottom(mut self, c: Option) -> Self { + self.line.connector2 = c; + self + } +} + +#[cfg(feature = "std")] +impl crate::settings::TableOption 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 { + /// The function allows modification of records and a grid configuration. + fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D); +} + +impl TableOption for &[T] +where + for<'a> &'a T: TableOption, +{ + 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 TableOption for Vec +where + T: TableOption, +{ + 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, +} + +#[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(colors: I, target: O) -> ExactColorization + where + I: IntoIterator, + I::Item: Into, + { + 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(colors: I) -> Self + where + I: IntoIterator, + I::Item: Into, + { + 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(colors: I) -> Self + where + I: IntoIterator, + I::Item: Into, + { + 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(colors: I) -> Self + where + I: IntoIterator, + I::Item: Into, + { + 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(colors: I) -> Self + where + I: IntoIterator, + I::Item: Into, + { + Self::new(colors, ColorizationPattern::ByColumn) + } + + fn new(colors: I, pattern: ColorizationPattern) -> Self + where + I: IntoIterator, + I::Item: Into, + { + let colors = colors.into_iter().map(Into::into).collect(); + Self { colors, pattern } + } +} + +impl TableOption 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 { + colors: Vec, + target: O, +} + +impl ExactColorization { + fn new(colors: Vec, target: O) -> Self { + Self { colors, target } + } +} + +impl TableOption for ExactColorization +where + O: Object, +{ + 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>, + colors: Vec>, + 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(names: I) -> Self + where + I: IntoIterator, + I::Item: Into, + { + let names = names.into_iter().map(Into::into).collect::>(); + 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(self, colors: I) -> Self + where + I: IntoIterator, + I::Item: Into>, + { + let colors = colors.into_iter().map(Into::into).collect::>(); + 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>, CompleteDimensionVecRecords<'static>, ColoredConfig> + for ColumnNames +{ + fn change( + self, + records: &mut VecRecords>, + 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::>(); + + 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::>(); + + 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 { + 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], 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 { + width: W, +} + +impl Justify +where + W: Measurement, +{ + /// 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 { + /// Creates a new Justify instance with a Max width used as a value. + pub fn max() -> Self { + Self { width: Max } + } +} + +impl Justify { + /// Creates a new Justify instance with a Min width used as a value. + pub fn min() -> Self { + Self { width: Min } + } +} + +impl TableOption for Justify +where + W: Measurement, + R: Records + ExactRecords + PeekableRecords + RecordsMut, + 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 { + width: W, + fill: char, + _priority: PhantomData

, +} + +impl MinWidth +where + W: Measurement, +{ + /// Creates a new instance of [`MinWidth`]. + pub fn new(width: W) -> Self { + Self { + width, + fill: ' ', + _priority: PhantomData, + } + } +} + +impl MinWidth { + /// 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(self) -> MinWidth { + MinWidth { + fill: self.fill, + width: self.width, + _priority: PhantomData, + } + } +} + +impl CellOption for MinWidth +where + W: Measurement, + R: Records + ExactRecords + PeekableRecords + RecordsMut, + 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 TableOption, ColoredConfig> for MinWidth +where + W: Measurement, + 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( + mut widths: Vec, + need: usize, + mut current: usize, + mut peaker: F, +) -> Vec +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::>() + .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>(width: W) -> Wrap { + Wrap::new(width) + } + + /// Returns a [`Truncate`] structure. + pub fn truncate>(width: W) -> Truncate<'static, W> { + Truncate::new(width) + } + + /// Returns a [`MinWidth`] structure. + pub fn increase>(width: W) -> MinWidth { + MinWidth::new(width) + } + + /// Returns a [`Justify`] structure. + pub fn justify>(width: W) -> Justify { + 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>(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>, + multiline: bool, + _priority: PhantomData

, +} +#[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 Truncate<'static, W> +where + W: Measurement, +{ + /// 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>>(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(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 CellOption for Truncate<'_, W, P> +where + W: Measurement, + R: Records + ExactRecords + PeekableRecords + RecordsMut, + 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>) -> 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 TableOption, ColoredConfig> + for Truncate<'_, W, P> +where + W: Measurement, + P: Peaker, + R: Records + ExactRecords + PeekableRecords + RecordsMut, + 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( + records: &mut R, + cfg: &mut ColoredConfig, + mut widths: Vec, + total: usize, + width: usize, + priority: P, + suffix: Option>, + multiline: bool, +) -> Vec +where + for<'a> &'a R: Records, + P: Peaker, + R: Records + PeekableRecords + ExactRecords + RecordsMut, +{ + 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::(); + let min_width = (col..col + span).map(|i| min_widths[i]).sum::(); + 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( + 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(records: R, cfg: &SpannedConfig) -> Vec { + SpannedGridDimension::width(records, cfg) +} + +pub(crate) fn get_table_widths_with_total( + records: R, + cfg: &SpannedConfig, +) -> (Vec, 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::() + + 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 +/// +/// +/// The function is based on Dan Davison delta ansi library. +#[cfg(feature = "color")] +pub(crate) fn strip_osc(text: &str) -> (String, Option) { + #[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::() + .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, +} + +impl WidthList { + /// Creates a new object. + pub fn new(list: Vec) -> Self { + Self { list } + } +} + +impl From> for WidthList { + fn from(list: Vec) -> Self { + Self::new(list) + } +} + +impl FromIterator for WidthList { + fn from_iter>(iter: T) -> Self { + Self::new(iter.into_iter().collect()) + } +} + +impl TableOption, 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 { + width: W, + keep_words: bool, + _priority: PhantomData

, +} + +impl Wrap { + /// Creates a [`Wrap`] object + pub fn new(width: W) -> Self + where + W: Measurement, + { + Wrap { + width, + keep_words: false, + _priority: PhantomData, + } + } +} + +impl Wrap { + /// 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(self) -> Wrap { + 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 TableOption, ColoredConfig> for Wrap +where + W: Measurement, + P: Peaker, + R: Records + ExactRecords + PeekableRecords + RecordsMut, + 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 CellOption for Wrap +where + W: Measurement, + R: Records + ExactRecords + PeekableRecords + RecordsMut, + 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( + records: &mut R, + cfg: &mut ColoredConfig, + mut widths: Vec, + total_width: usize, + width: usize, + keep_words: bool, + priority: P, +) -> Vec +where + R: Records + ExactRecords + PeekableRecords + RecordsMut, + 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; + >::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) = 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) { + 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 { + 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 { + 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>, + } + + impl<'a> Blocks<'a> { + pub(super) fn new(iter: AnsiBlockIter<'a>) -> Self { + Self { + iter, + current: None, + } + } + + pub(super) fn next_block(&mut self) -> Option> { + 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( + 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::(); + let min_width = (col..col + span).map(|i| min_widths[i]).sum::(); + 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::>(), + [ + "\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::>(), + [ + "\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::>(), + [ + "^\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::>(), + [ + "^\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 " -- cgit v1.2.3