summaryrefslogtreecommitdiffstats
path: root/vendor/tabled/src/settings
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/tabled/src/settings')
-rw-r--r--vendor/tabled/src/settings/alignment/mod.rs189
-rw-r--r--vendor/tabled/src/settings/cell_option.rs55
-rw-r--r--vendor/tabled/src/settings/color/mod.rs346
-rw-r--r--vendor/tabled/src/settings/concat/mod.rs171
-rw-r--r--vendor/tabled/src/settings/disable/mod.rs196
-rw-r--r--vendor/tabled/src/settings/duplicate/mod.rs150
-rw-r--r--vendor/tabled/src/settings/extract/mod.rs257
-rw-r--r--vendor/tabled/src/settings/format/format_config.rs14
-rw-r--r--vendor/tabled/src/settings/format/format_content.rs86
-rw-r--r--vendor/tabled/src/settings/format/format_positioned.rs51
-rw-r--r--vendor/tabled/src/settings/format/mod.rs144
-rw-r--r--vendor/tabled/src/settings/formatting/alignment_strategy.rs172
-rw-r--r--vendor/tabled/src/settings/formatting/charset.rs108
-rw-r--r--vendor/tabled/src/settings/formatting/justification.rs127
-rw-r--r--vendor/tabled/src/settings/formatting/mod.rs20
-rw-r--r--vendor/tabled/src/settings/formatting/tab_size.rs62
-rw-r--r--vendor/tabled/src/settings/formatting/trim_strategy.rs118
-rw-r--r--vendor/tabled/src/settings/height/cell_height_increase.rs98
-rw-r--r--vendor/tabled/src/settings/height/cell_height_limit.rs103
-rw-r--r--vendor/tabled/src/settings/height/height_list.rs47
-rw-r--r--vendor/tabled/src/settings/height/mod.rs228
-rw-r--r--vendor/tabled/src/settings/height/table_height_increase.rs90
-rw-r--r--vendor/tabled/src/settings/height/table_height_limit.rs123
-rw-r--r--vendor/tabled/src/settings/height/util.rs22
-rw-r--r--vendor/tabled/src/settings/highlight/mod.rs452
-rw-r--r--vendor/tabled/src/settings/locator/mod.rs202
-rw-r--r--vendor/tabled/src/settings/margin/mod.rs137
-rw-r--r--vendor/tabled/src/settings/measurement/mod.rs161
-rw-r--r--vendor/tabled/src/settings/merge/mod.rs196
-rw-r--r--vendor/tabled/src/settings/mod.rs135
-rw-r--r--vendor/tabled/src/settings/modify.rs81
-rw-r--r--vendor/tabled/src/settings/object/cell.rs70
-rw-r--r--vendor/tabled/src/settings/object/columns.rs209
-rw-r--r--vendor/tabled/src/settings/object/frame.rs69
-rw-r--r--vendor/tabled/src/settings/object/mod.rs765
-rw-r--r--vendor/tabled/src/settings/object/rows.rs213
-rw-r--r--vendor/tabled/src/settings/object/segment.rs151
-rw-r--r--vendor/tabled/src/settings/object/util.rs22
-rw-r--r--vendor/tabled/src/settings/padding/mod.rs164
-rw-r--r--vendor/tabled/src/settings/panel/footer.rs32
-rw-r--r--vendor/tabled/src/settings/panel/header.rs32
-rw-r--r--vendor/tabled/src/settings/panel/horizontal_panel.rs80
-rw-r--r--vendor/tabled/src/settings/panel/mod.rs127
-rw-r--r--vendor/tabled/src/settings/panel/vertical_panel.rs83
-rw-r--r--vendor/tabled/src/settings/peaker/mod.rs94
-rw-r--r--vendor/tabled/src/settings/rotate/mod.rs155
-rw-r--r--vendor/tabled/src/settings/settings_list.rs71
-rw-r--r--vendor/tabled/src/settings/shadow/mod.rs195
-rw-r--r--vendor/tabled/src/settings/span/column.rs125
-rw-r--r--vendor/tabled/src/settings/span/mod.rs68
-rw-r--r--vendor/tabled/src/settings/span/row.rs126
-rw-r--r--vendor/tabled/src/settings/split/mod.rs420
-rw-r--r--vendor/tabled/src/settings/style/border.rs159
-rw-r--r--vendor/tabled/src/settings/style/border_char.rs99
-rw-r--r--vendor/tabled/src/settings/style/border_color.rs155
-rw-r--r--vendor/tabled/src/settings/style/border_text.rs253
-rw-r--r--vendor/tabled/src/settings/style/builder.rs1208
-rw-r--r--vendor/tabled/src/settings/style/horizontal_line.rs69
-rw-r--r--vendor/tabled/src/settings/style/line.rs91
-rw-r--r--vendor/tabled/src/settings/style/mod.rs127
-rw-r--r--vendor/tabled/src/settings/style/offset.rs19
-rw-r--r--vendor/tabled/src/settings/style/raw_style.rs438
-rw-r--r--vendor/tabled/src/settings/style/span_border_correction.rs216
-rw-r--r--vendor/tabled/src/settings/style/vertical_line.rs50
-rw-r--r--vendor/tabled/src/settings/table_option.rs30
-rw-r--r--vendor/tabled/src/settings/themes/colorization.rs388
-rw-r--r--vendor/tabled/src/settings/themes/column_names.rs355
-rw-r--r--vendor/tabled/src/settings/themes/mod.rs9
-rw-r--r--vendor/tabled/src/settings/width/justify.rs96
-rw-r--r--vendor/tabled/src/settings/width/min_width.rs205
-rw-r--r--vendor/tabled/src/settings/width/mod.rs163
-rw-r--r--vendor/tabled/src/settings/width/truncate.rs505
-rw-r--r--vendor/tabled/src/settings/width/util.rs265
-rw-r--r--vendor/tabled/src/settings/width/width_list.rs50
-rw-r--r--vendor/tabled/src/settings/width/wrap.rs1468
75 files changed, 14030 insertions, 0 deletions
diff --git a/vendor/tabled/src/settings/alignment/mod.rs b/vendor/tabled/src/settings/alignment/mod.rs
new file mode 100644
index 000000000..81d1717a6
--- /dev/null
+++ b/vendor/tabled/src/settings/alignment/mod.rs
@@ -0,0 +1,189 @@
+//! This module contains an [`Alignment`] setting for cells on the [`Table`].
+//!
+//! # Example
+//!
+#![cfg_attr(feature = "std", doc = "```")]
+#![cfg_attr(not(feature = "std"), doc = "```ignore")]
+//! # use tabled::{Table, settings::{Alignment, Modify, object::Rows}};
+//! # let data: Vec<&'static str> = Vec::new();
+//! let mut table = Table::new(&data);
+//! table.with(Modify::new(Rows::single(0)).with(Alignment::center()));
+//! ```
+//!
+//! [`Table`]: crate::Table
+
+use crate::{
+ grid::config::CompactConfig,
+ grid::config::{AlignmentHorizontal, AlignmentVertical, CompactMultilineConfig},
+ settings::TableOption,
+};
+
+use AlignmentInner::*;
+
+#[cfg(feature = "std")]
+use crate::grid::config::{ColoredConfig, Entity};
+
+/// Alignment represent a horizontal and vertical alignment setting for any cell on a [`Table`].
+///
+/// An alignment strategy can be set by [`AlignmentStrategy`].
+///
+/// # Example
+///
+#[cfg_attr(feature = "std", doc = "```")]
+#[cfg_attr(not(feature = "std"), doc = "```ignore")]
+/// use tabled::{
+/// Table,
+/// settings::{
+/// formatting::AlignmentStrategy,
+/// object::Segment, Alignment, Modify, Style,
+/// }
+/// };
+///
+/// let data = [
+/// ["1", "2", "3"],
+/// ["Some\nMulti\nLine\nText", "and a line", "here"],
+/// ["4", "5", "6"],
+/// ];
+///
+/// let mut table = Table::new(&data);
+/// table
+/// .with(Style::modern())
+/// .with(
+/// Modify::new(Segment::all())
+/// .with(Alignment::right())
+/// .with(Alignment::center())
+/// .with(AlignmentStrategy::PerCell)
+/// );
+///
+/// assert_eq!(
+/// table.to_string(),
+/// concat!(
+/// "┌───────┬────────────┬──────┐\n",
+/// "│ 0 │ 1 │ 2 │\n",
+/// "├───────┼────────────┼──────┤\n",
+/// "│ 1 │ 2 │ 3 │\n",
+/// "├───────┼────────────┼──────┤\n",
+/// "│ Some │ and a line │ here │\n",
+/// "│ Multi │ │ │\n",
+/// "│ Line │ │ │\n",
+/// "│ Text │ │ │\n",
+/// "├───────┼────────────┼──────┤\n",
+/// "│ 4 │ 5 │ 6 │\n",
+/// "└───────┴────────────┴──────┘",
+/// ),
+/// )
+/// ```
+///
+/// [`Table`]: crate::Table
+/// [`AlignmentStrategy`]: crate::settings::formatting::AlignmentStrategy
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Alignment {
+ inner: AlignmentInner,
+}
+
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
+enum AlignmentInner {
+ /// A horizontal alignment.
+ Horizontal(AlignmentHorizontal),
+ /// A vertical alignment.
+ Vertical(AlignmentVertical),
+}
+
+impl Alignment {
+ /// Left constructs a horizontal alignment to [`AlignmentHorizontal::Left`]
+ pub fn left() -> Self {
+ Self::horizontal(AlignmentHorizontal::Left)
+ }
+
+ /// Right constructs a horizontal alignment to [`AlignmentHorizontal::Right`]
+ ///
+ /// ## Notice
+ ///
+ /// When you use [`MinWidth`] the alignment might not work as you expected.
+ /// You could try to apply [`TrimStrategy`] which may help.
+ ///
+ /// [`MinWidth`]: crate::settings::width::MinWidth
+ /// [`TrimStrategy`]: crate::settings::formatting::TrimStrategy
+ pub fn right() -> Self {
+ Self::horizontal(AlignmentHorizontal::Right)
+ }
+
+ /// Center constructs a horizontal alignment to [`AlignmentHorizontal::Center`]
+ ///
+ /// ## Notice
+ ///
+ /// When you use [`MinWidth`] the alignment might not work as you expected.
+ /// You could try to apply [`TrimStrategy`] which may help.
+ ///
+ /// [`MinWidth`]: crate::settings::width::MinWidth
+ /// [`TrimStrategy`]: crate::settings::formatting::TrimStrategy
+ pub const fn center() -> Self {
+ Self::horizontal(AlignmentHorizontal::Center)
+ }
+
+ /// Top constructs a vertical alignment to [`AlignmentVertical::Top`]
+ pub const fn top() -> Self {
+ Self::vertical(AlignmentVertical::Top)
+ }
+
+ /// Bottom constructs a vertical alignment to [`AlignmentVertical::Bottom`]
+ pub const fn bottom() -> Self {
+ Self::vertical(AlignmentVertical::Bottom)
+ }
+
+ /// `Center_vertical` constructs a vertical alignment to [`AlignmentVertical::Center`]
+ pub const fn center_vertical() -> Self {
+ Self::vertical(AlignmentVertical::Center)
+ }
+
+ /// Returns an alignment with the given horizontal alignment.
+ const fn horizontal(alignment: AlignmentHorizontal) -> Self {
+ Self::new(Horizontal(alignment))
+ }
+
+ /// Returns an alignment with the given vertical alignment.
+ const fn vertical(alignment: AlignmentVertical) -> Self {
+ Self::new(Vertical(alignment))
+ }
+
+ const fn new(inner: AlignmentInner) -> Self {
+ Self { inner }
+ }
+}
+
+#[cfg(feature = "std")]
+impl<R> crate::settings::CellOption<R, ColoredConfig> for Alignment {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ match self.inner {
+ Horizontal(a) => cfg.set_alignment_horizontal(entity, a),
+ Vertical(a) => cfg.set_alignment_vertical(entity, a),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl<R, D> TableOption<R, D, ColoredConfig> for Alignment {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ match self.inner {
+ Horizontal(a) => cfg.set_alignment_horizontal(Entity::Global, a),
+ Vertical(a) => cfg.set_alignment_vertical(Entity::Global, a),
+ }
+ }
+}
+
+impl<R, D> TableOption<R, D, CompactConfig> for Alignment {
+ fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) {
+ if let Horizontal(a) = self.inner {
+ *cfg = cfg.set_alignment_horizontal(a)
+ }
+ }
+}
+
+impl<R, D> TableOption<R, D, CompactMultilineConfig> for Alignment {
+ fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) {
+ match self.inner {
+ Horizontal(a) => *cfg = cfg.set_alignment_horizontal(a),
+ Vertical(a) => *cfg = cfg.set_alignment_vertical(a),
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/cell_option.rs b/vendor/tabled/src/settings/cell_option.rs
new file mode 100644
index 000000000..f589ac47f
--- /dev/null
+++ b/vendor/tabled/src/settings/cell_option.rs
@@ -0,0 +1,55 @@
+use crate::grid::{
+ config::Entity,
+ records::{ExactRecords, Records, RecordsMut},
+};
+
+/// A trait for configuring a single cell.
+///
+/// ~~~~ Where cell represented by 'row' and 'column' indexes. ~~~~
+///
+/// A cell can be targeted by [`Cell`].
+///
+/// [`Cell`]: crate::object::Cell
+pub trait CellOption<R, C> {
+ /// Modification function of a single cell.
+ fn change(self, records: &mut R, cfg: &mut C, entity: Entity);
+}
+
+#[cfg(feature = "std")]
+impl<R, C> CellOption<R, C> for String
+where
+ R: Records + ExactRecords + RecordsMut<String>,
+{
+ fn change(self, records: &mut R, cfg: &mut C, entity: Entity) {
+ (&self).change(records, cfg, entity);
+ }
+}
+
+#[cfg(feature = "std")]
+impl<R, C> CellOption<R, C> for &String
+where
+ R: Records + ExactRecords + RecordsMut<String>,
+{
+ fn change(self, records: &mut R, _: &mut C, entity: Entity) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ for pos in entity.iter(count_rows, count_cols) {
+ records.set(pos, self.clone());
+ }
+ }
+}
+
+impl<'a, R, C> CellOption<R, C> for &'a str
+where
+ R: Records + ExactRecords + RecordsMut<&'a str>,
+{
+ fn change(self, records: &mut R, _: &mut C, entity: Entity) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ for pos in entity.iter(count_rows, count_cols) {
+ records.set(pos, self);
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/color/mod.rs b/vendor/tabled/src/settings/color/mod.rs
new file mode 100644
index 000000000..f4bfa5c32
--- /dev/null
+++ b/vendor/tabled/src/settings/color/mod.rs
@@ -0,0 +1,346 @@
+//! This module contains a configuration of a [`Border`] or a [`Table`] to set its borders color via [`Color`].
+//!
+//! [`Border`]: crate::settings::Border
+//! [`Table`]: crate::Table
+
+use std::{borrow::Cow, ops::BitOr};
+
+use crate::{
+ grid::{
+ color::{AnsiColor, StaticColor},
+ config::{ColoredConfig, Entity},
+ },
+ settings::{CellOption, TableOption},
+};
+
+/// Color represents a color which can be set to things like [`Border`], [`Padding`] and [`Margin`].
+///
+/// # Example
+///
+/// ```
+/// use tabled::{settings::Color, Table};
+///
+/// let data = [
+/// (0u8, "Hello"),
+/// (1u8, "World"),
+/// ];
+///
+/// let table = Table::new(data)
+/// .with(Color::BG_BLUE)
+/// .to_string();
+///
+/// println!("{}", table);
+/// ```
+///
+/// [`Padding`]: crate::settings::Padding
+/// [`Margin`]: crate::settings::Margin
+/// [`Border`]: crate::settings::Border
+#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Color(AnsiColor<'static>);
+
+// todo: Add | operation to combine colors
+
+#[rustfmt::skip]
+impl Color {
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[30m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[34m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_BRIGHT_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[90m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_BRIGHT_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[94m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_BRIGHT_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[96m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_BRIGHT_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[92m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_BRIGHT_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[95m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_BRIGHT_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[91m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_BRIGHT_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[97m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_BRIGHT_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[93m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[36m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[32m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[35m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[31m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[37m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const FG_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[33m"), Cow::Borrowed("\u{1b}[39m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+
+ pub const BG_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[40m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[44m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_BRIGHT_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[100m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_BRIGHT_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[104m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_BRIGHT_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[106m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_BRIGHT_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[102m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_BRIGHT_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[105m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_BRIGHT_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[101m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_BRIGHT_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[107m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_BRIGHT_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[103m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[46m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[42m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[45m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[41m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[47m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BG_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[43m"), Cow::Borrowed("\u{1b}[49m")));
+ /// A color representation.
+ ///
+ /// Notice that the colors are constants so you can't combine them.
+ pub const BOLD: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[1m"), Cow::Borrowed("\u{1b}[22m")));
+}
+
+impl Color {
+ /// Creates a new [`Color`]` instance, with ANSI prefix and ANSI suffix.
+ /// You can use [`TryFrom`] to construct it from [`String`].
+ pub fn new(prefix: String, suffix: String) -> Self {
+ Self(AnsiColor::new(prefix.into(), suffix.into()))
+ }
+}
+
+impl From<Color> for AnsiColor<'static> {
+ fn from(c: Color) -> Self {
+ c.0
+ }
+}
+
+impl From<AnsiColor<'static>> for Color {
+ fn from(c: AnsiColor<'static>) -> Self {
+ Self(c)
+ }
+}
+
+impl From<StaticColor> for Color {
+ fn from(c: StaticColor) -> Self {
+ Self(AnsiColor::new(
+ Cow::Borrowed(c.get_prefix()),
+ Cow::Borrowed(c.get_suffix()),
+ ))
+ }
+}
+
+impl BitOr for Color {
+ type Output = Color;
+
+ fn bitor(self, rhs: Self) -> Self::Output {
+ let l_prefix = self.0.get_prefix();
+ let l_suffix = self.0.get_suffix();
+ let r_prefix = rhs.0.get_prefix();
+ let r_suffix = rhs.0.get_suffix();
+
+ let mut prefix = l_prefix.to_string();
+ if l_prefix != r_prefix {
+ prefix.push_str(r_prefix);
+ }
+
+ let mut suffix = l_suffix.to_string();
+ if l_suffix != r_suffix {
+ suffix.push_str(r_suffix);
+ }
+
+ Self::new(prefix, suffix)
+ }
+}
+
+#[cfg(feature = "color")]
+impl std::convert::TryFrom<&str> for Color {
+ type Error = ();
+
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ AnsiColor::try_from(value).map(Color)
+ }
+}
+
+#[cfg(feature = "color")]
+impl std::convert::TryFrom<String> for Color {
+ type Error = ();
+
+ fn try_from(value: String) -> Result<Self, Self::Error> {
+ AnsiColor::try_from(value).map(Color)
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for Color {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let _ = cfg.set_color(Entity::Global, self.0.clone());
+ }
+}
+
+impl<R> CellOption<R, ColoredConfig> for Color {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let _ = cfg.set_color(entity, self.0.clone());
+ }
+}
+
+impl<R> CellOption<R, ColoredConfig> for &Color {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let _ = cfg.set_color(entity, self.0.clone());
+ }
+}
+
+impl crate::grid::color::Color for Color {
+ fn fmt_prefix<W: std::fmt::Write>(&self, f: &mut W) -> std::fmt::Result {
+ self.0.fmt_prefix(f)
+ }
+
+ fn fmt_suffix<W: std::fmt::Write>(&self, f: &mut W) -> std::fmt::Result {
+ self.0.fmt_suffix(f)
+ }
+
+ fn colorize<W: std::fmt::Write>(&self, f: &mut W, text: &str) -> std::fmt::Result {
+ self.0.colorize(f, text)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[cfg(feature = "color")]
+ use ::{owo_colors::OwoColorize, std::convert::TryFrom};
+
+ #[test]
+ fn test_xor_operation() {
+ assert_eq!(
+ Color::FG_BLACK | Color::FG_BLUE,
+ Color::new(
+ String::from("\u{1b}[30m\u{1b}[34m"),
+ String::from("\u{1b}[39m")
+ )
+ );
+ assert_eq!(
+ Color::FG_BRIGHT_GREEN | Color::BG_BLUE,
+ Color::new(
+ String::from("\u{1b}[92m\u{1b}[44m"),
+ String::from("\u{1b}[39m\u{1b}[49m")
+ )
+ );
+ assert_eq!(
+ Color::new(String::from("..."), String::from("!!!"))
+ | Color::new(String::from("@@@"), String::from("###")),
+ Color::new(String::from("...@@@"), String::from("!!!###"))
+ );
+ assert_eq!(
+ Color::new(String::from("..."), String::from("!!!"))
+ | Color::new(String::from("@@@"), String::from("###"))
+ | Color::new(String::from("$$$"), String::from("%%%")),
+ Color::new(String::from("...@@@$$$"), String::from("!!!###%%%"))
+ );
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn test_try_from() {
+ assert_eq!(Color::try_from(""), Err(()));
+ assert_eq!(Color::try_from("".red().on_green().to_string()), Err(()));
+ assert_eq!(
+ Color::try_from("."),
+ Ok(Color::new(String::new(), String::new()))
+ );
+ assert_eq!(
+ Color::try_from("...."),
+ Ok(Color::new(String::new(), String::new()))
+ );
+ assert_eq!(
+ Color::try_from(".".red().on_green().to_string()),
+ Ok(Color::new(
+ String::from("\u{1b}[31m\u{1b}[42m"),
+ String::from("\u{1b}[39m\u{1b}[49m")
+ ))
+ );
+ assert_eq!(
+ Color::try_from("....".red().on_green().to_string()),
+ Ok(Color::new(
+ String::from("\u{1b}[31m\u{1b}[42m"),
+ String::from("\u{1b}[39m\u{1b}[49m")
+ ))
+ );
+ }
+}
diff --git a/vendor/tabled/src/settings/concat/mod.rs b/vendor/tabled/src/settings/concat/mod.rs
new file mode 100644
index 000000000..23b0dcda1
--- /dev/null
+++ b/vendor/tabled/src/settings/concat/mod.rs
@@ -0,0 +1,171 @@
+//! This module contains a [`Concat`] primitive which can be in order to combine 2 [`Table`]s into 1.
+//!
+//! # Example
+//!
+//! ```
+//! use tabled::{Table, settings::Concat};
+//! let table1 = Table::new([0, 1, 2, 3]);
+//! let table2 = Table::new(["A", "B", "C", "D"]);
+//!
+//! let mut table3 = table1;
+//! table3.with(Concat::horizontal(table2));
+//! ```
+
+use std::borrow::Cow;
+
+use crate::{
+ grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut, Resizable},
+ settings::TableOption,
+ Table,
+};
+
+/// [`Concat`] concatenates tables along a particular axis [Horizontal | Vertical].
+/// It doesn't do any key or column comparisons like SQL's join does.
+///
+/// When the tables has different sizes, empty cells will be created by default.
+///
+/// [`Concat`] in horizontal mode has similar behaviour to tuples `(a, b)`.
+/// But it behaves on tables rather than on an actual data.
+///
+/// # Example
+///
+///
+#[cfg_attr(feature = "derive", doc = "```")]
+#[cfg_attr(not(feature = "derive"), doc = "```ignore")]
+/// use tabled::{Table, Tabled, settings::{Style, Concat}};
+///
+/// #[derive(Tabled)]
+/// struct Message {
+/// id: &'static str,
+/// text: &'static str,
+/// }
+///
+/// #[derive(Tabled)]
+/// struct Department(#[tabled(rename = "department")] &'static str);
+///
+/// let messages = [
+/// Message { id: "0", text: "Hello World" },
+/// Message { id: "1", text: "Do do do something", },
+/// ];
+///
+/// let departments = [
+/// Department("Admins"),
+/// Department("DevOps"),
+/// Department("R&D"),
+/// ];
+///
+/// let mut table = Table::new(messages);
+/// table
+/// .with(Concat::horizontal(Table::new(departments)))
+/// .with(Style::extended());
+///
+/// assert_eq!(
+/// table.to_string(),
+/// concat!(
+/// "╔════╦════════════════════╦════════════╗\n",
+/// "║ id ║ text ║ department ║\n",
+/// "╠════╬════════════════════╬════════════╣\n",
+/// "║ 0 ║ Hello World ║ Admins ║\n",
+/// "╠════╬════════════════════╬════════════╣\n",
+/// "║ 1 ║ Do do do something ║ DevOps ║\n",
+/// "╠════╬════════════════════╬════════════╣\n",
+/// "║ ║ ║ R&D ║\n",
+/// "╚════╩════════════════════╩════════════╝",
+/// )
+/// )
+/// ```
+
+#[derive(Debug)]
+pub struct Concat {
+ table: Table,
+ mode: ConcatMode,
+ default_cell: Cow<'static, str>,
+}
+
+#[derive(Debug)]
+enum ConcatMode {
+ Vertical,
+ Horizontal,
+}
+
+impl Concat {
+ fn new(table: Table, mode: ConcatMode) -> Self {
+ Self {
+ table,
+ mode,
+ default_cell: Cow::Borrowed(""),
+ }
+ }
+
+ /// Concatenate 2 tables horizontally (along axis=0)
+ pub fn vertical(table: Table) -> Self {
+ Self::new(table, ConcatMode::Vertical)
+ }
+
+ /// Concatenate 2 tables vertically (along axis=1)
+ pub fn horizontal(table: Table) -> Self {
+ Self::new(table, ConcatMode::Horizontal)
+ }
+
+ /// Sets a cell's content for cases where 2 tables has different sizes.
+ pub fn default_cell(mut self, cell: impl Into<Cow<'static, str>>) -> Self {
+ self.default_cell = cell.into();
+ self
+ }
+}
+
+impl<R, D, C> TableOption<R, D, C> for Concat
+where
+ R: Records + ExactRecords + Resizable + PeekableRecords + RecordsMut<String>,
+{
+ fn change(mut self, records: &mut R, _: &mut C, _: &mut D) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ let rhs = &mut self.table;
+ match self.mode {
+ ConcatMode::Horizontal => {
+ for _ in 0..rhs.count_columns() {
+ records.push_column();
+ }
+
+ for row in count_rows..rhs.count_rows() {
+ records.push_row();
+
+ for col in 0..records.count_columns() {
+ records.set((row, col), self.default_cell.to_string());
+ }
+ }
+
+ for row in 0..rhs.shape().0 {
+ for col in 0..rhs.shape().1 {
+ let text = rhs.get_records().get_text((row, col)).to_string();
+ let col = col + count_cols;
+ records.set((row, col), text);
+ }
+ }
+ }
+ ConcatMode::Vertical => {
+ for _ in 0..rhs.count_rows() {
+ records.push_row();
+ }
+
+ for col in count_cols..rhs.shape().1 {
+ records.push_column();
+
+ for row in 0..records.count_rows() {
+ records.set((row, col), self.default_cell.to_string());
+ }
+ }
+
+ for row in 0..rhs.shape().0 {
+ for col in 0..rhs.shape().1 {
+ let text = rhs.get_records().get_text((row, col)).to_string();
+ let row = row + count_rows;
+ records.set((row, col), text);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/disable/mod.rs b/vendor/tabled/src/settings/disable/mod.rs
new file mode 100644
index 000000000..bfbf03db0
--- /dev/null
+++ b/vendor/tabled/src/settings/disable/mod.rs
@@ -0,0 +1,196 @@
+//! This module contains a [`Disable`] structure which helps to
+//! remove an etheir column or row from a [`Table`].
+//!
+//! # Example
+//!
+//! ```rust,no_run
+//! # use tabled::{Table, settings::{Disable, object::Rows}};
+//! # let data: Vec<&'static str> = Vec::new();
+//! let table = Table::new(&data).with(Disable::row(Rows::first()));
+//! ```
+//!
+//! [`Table`]: crate::Table
+
+use std::marker::PhantomData;
+
+use crate::{
+ grid::records::{ExactRecords, Records, Resizable},
+ settings::{locator::Locator, TableOption},
+};
+
+/// Disable removes particular rows/columns from a [`Table`].
+///
+/// It tries to keeps track of style changes which may occur.
+/// But it's not guaranteed will be the way you would expect it to be.
+///
+/// Generally you should avoid use of [`Disable`] because it's a slow function and modifies the underlying records.
+/// Providing correct data right away is better.
+///
+/// # Example
+///
+/// ```
+/// use tabled::{Table, settings::{Disable, object::Rows}};
+///
+/// let data = vec!["Hello", "World", "!!!"];
+///
+/// let table = Table::new(data).with(Disable::row(Rows::new(1..2))).to_string();
+///
+/// assert_eq!(
+/// table,
+/// "+-------+\n\
+/// | &str |\n\
+/// +-------+\n\
+/// | World |\n\
+/// +-------+\n\
+/// | !!! |\n\
+/// +-------+"
+/// );
+///
+/// ```
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct Disable<L, Target> {
+ locator: L,
+ target: PhantomData<Target>,
+}
+
+impl<L> Disable<L, TargetColumn> {
+ /// Disable columns.
+ ///
+ /// Available locators are:
+ ///
+ /// - [`Columns`]
+ /// - [`Column`]
+ /// - [`FirstColumn`]
+ /// - [`LastColumn`]
+ /// - [`ByColumnName`]
+ ///
+ /// ```rust
+ /// use tabled::{builder::Builder, settings::{Disable, locator::ByColumnName, object::Columns}};
+ ///
+ /// let mut builder = Builder::default();
+ ///
+ /// builder.push_record(["col1", "col2", "col3"]);
+ /// builder.push_record(["Hello", "World", "1"]);
+ ///
+ /// let table = builder.build()
+ /// .with(Disable::column(ByColumnName::new("col3")))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "+-------+-------+\n\
+ /// | col1 | col2 |\n\
+ /// +-------+-------+\n\
+ /// | Hello | World |\n\
+ /// +-------+-------+"
+ /// );
+ /// ```
+ ///
+ /// [`Columns`]: crate::settings::object::Columns
+ /// [`Column`]: crate::settings::object::Column
+ /// [`FirstColumn`]: crate::settings::object::FirstColumn
+ /// [`LastColumn`]: crate::settings::object::LastColumn
+ /// [`ByColumnName`]: crate::settings::locator::ByColumnName
+ pub fn column(locator: L) -> Self {
+ Self {
+ locator,
+ target: PhantomData,
+ }
+ }
+}
+
+impl<L> Disable<L, TargetRow> {
+ /// Disable rows.
+ ///
+ /// Available locators are:
+ ///
+ /// - [`Rows`]
+ /// - [`Row`]
+ /// - [`FirstRow`]
+ /// - [`LastRow`]
+ ///
+ /// ```rust
+ /// use tabled::{settings::{Disable, object::Rows}, builder::Builder};
+ ///
+ /// let mut builder = Builder::default();
+ /// builder.push_record(["col1", "col2", "col3"]);
+ /// builder.push_record(["Hello", "World", "1"]);
+ ///
+ /// let table = builder.build()
+ /// .with(Disable::row(Rows::first()))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "+-------+-------+---+\n\
+ /// | Hello | World | 1 |\n\
+ /// +-------+-------+---+"
+ /// );
+ /// ```
+ ///
+ /// [`Rows`]: crate::settings::object::Rows
+ /// [`Row`]: crate::settings::object::Row
+ /// [`FirstRow`]: crate::settings::object::FirstRow
+ /// [`LastRow`]: crate::settings::object::LastRow
+ pub fn row(locator: L) -> Self {
+ Self {
+ locator,
+ target: PhantomData,
+ }
+ }
+}
+
+/// A marker struct for [`Disable`].
+#[derive(Debug)]
+pub struct TargetRow;
+
+/// A marker struct for [`Disable`].
+#[derive(Debug)]
+pub struct TargetColumn;
+
+impl<L, R, D, C> TableOption<R, D, C> for Disable<L, TargetColumn>
+where
+ for<'a> L: Locator<&'a R, Coordinate = usize>,
+ R: Records + Resizable,
+{
+ fn change(mut self, records: &mut R, _: &mut C, _: &mut D) {
+ let columns = self.locator.locate(records).into_iter().collect::<Vec<_>>();
+
+ let mut shift = 0;
+ for col in columns.into_iter() {
+ if col - shift > records.count_columns() {
+ continue;
+ }
+
+ records.remove_column(col - shift);
+ shift += 1;
+ }
+
+ // fixme: I am pretty sure that we violate span constrains by removing rows/cols
+ // Because span may be bigger then the max number of rows/cols
+ }
+}
+
+impl<L, R, D, C> TableOption<R, D, C> for Disable<L, TargetRow>
+where
+ for<'a> L: Locator<&'a R, Coordinate = usize>,
+ R: ExactRecords + Resizable,
+{
+ fn change(mut self, records: &mut R, _: &mut C, _: &mut D) {
+ let rows = self.locator.locate(records).into_iter().collect::<Vec<_>>();
+
+ let mut shift = 0;
+ for row in rows.into_iter() {
+ if row - shift > records.count_rows() {
+ continue;
+ }
+
+ records.remove_row(row - shift);
+ shift += 1;
+ }
+
+ // fixme: I am pretty sure that we violate span constrains by removing rows/cols
+ // Because span may be bigger then the max number of rows/cols
+ }
+}
diff --git a/vendor/tabled/src/settings/duplicate/mod.rs b/vendor/tabled/src/settings/duplicate/mod.rs
new file mode 100644
index 000000000..dec967bd6
--- /dev/null
+++ b/vendor/tabled/src/settings/duplicate/mod.rs
@@ -0,0 +1,150 @@
+//! This module contains an [`Dup`] setting the [`Table`].
+//!
+//! # Example
+//!
+//! ```
+//! # use tabled::{Table, settings::{Dup, object::{Columns, Rows}}};
+//! # let data: Vec<&'static str> = Vec::new();
+//! let mut table = Table::new(&data);
+//! table.with(Dup::new(Rows::first(), Columns::first()));
+//! ```
+//!
+//! [`Table`]: crate::Table
+
+use papergrid::config::Position;
+
+use crate::{
+ grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut},
+ settings::{object::Object, TableOption},
+};
+
+/// [`Dup`] duplicates a given set of cells into another set of ones [`Table`].
+///
+/// # Example
+///
+/// ```
+/// use tabled::{Table, settings::{object::Rows, Dup}};
+///
+/// let data = [
+/// ["1", "2", "3"],
+/// ["Some\nMulti\nLine\nText", "and a line", "here"],
+/// ["4", "5", "6"],
+/// ];
+///
+/// let mut table = Table::new(&data);
+/// table.with(Dup::new(Rows::single(1), Rows::single(2)));
+///
+/// assert_eq!(
+/// table.to_string(),
+/// "+-------+------------+------+\n\
+/// | 0 | 1 | 2 |\n\
+/// +-------+------------+------+\n\
+/// | Some | and a line | here |\n\
+/// | Multi | | |\n\
+/// | Line | | |\n\
+/// | Text | | |\n\
+/// +-------+------------+------+\n\
+/// | Some | and a line | here |\n\
+/// | Multi | | |\n\
+/// | Line | | |\n\
+/// | Text | | |\n\
+/// +-------+------------+------+\n\
+/// | 4 | 5 | 6 |\n\
+/// +-------+------------+------+",
+/// )
+/// ```
+///
+/// [`Table`]: crate::Table
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Dup<Dst, Src> {
+ src: Src,
+ dst: Dst,
+}
+
+impl<Dst, Src> Dup<Dst, Src> {
+ /// New creates a new [`Dup`] modifier.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use tabled::{Table, settings::{Dup, object::{Columns, Rows}}};
+ /// # let data: Vec<&'static str> = Vec::new();
+ /// let mut table = Table::new(&data);
+ /// table.with(Dup::new(Rows::first(), Columns::last()));
+ /// ```
+ pub fn new(dst: Dst, src: Src) -> Self {
+ Self { src, dst }
+ }
+}
+
+impl<Dst, Src, R, D, C> TableOption<R, D, C> for Dup<Dst, Src>
+where
+ Dst: Object<R>,
+ Src: Object<R>,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+{
+ fn change(self, records: &mut R, _: &mut C, _: &mut D) {
+ let input = collect_input(records, self.src);
+ set_cells(records, &input, self.dst);
+ }
+}
+
+fn collect_input<R, O>(records: &mut R, src: O) -> Vec<String>
+where
+ O: Object<R>,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+{
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ let mut input = Vec::new();
+ for entity in src.cells(records) {
+ for pos in entity.iter(count_rows, count_columns) {
+ if !is_valid_cell(pos, count_rows, count_columns) {
+ continue;
+ }
+
+ let text = records.get_text(pos).to_owned();
+ input.push(text);
+ }
+ }
+
+ input
+}
+
+fn set_cells<R, O>(records: &mut R, src: &[String], dst: O)
+where
+ O: Object<R>,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+{
+ if src.is_empty() {
+ return;
+ }
+
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ for entity in dst.cells(records) {
+ let mut source = src.iter().cycle();
+ for pos in entity.iter(count_rows, count_columns) {
+ if !is_valid_cell(pos, count_rows, count_columns) {
+ continue;
+ }
+
+ let text = source.next().unwrap().clone();
+ records.set(pos, text);
+ }
+ }
+}
+
+fn is_valid_cell((row, col): Position, count_rows: usize, count_columns: usize) -> bool {
+ if row > count_rows {
+ return false;
+ }
+
+ if col > count_columns {
+ return false;
+ }
+
+ true
+}
diff --git a/vendor/tabled/src/settings/extract/mod.rs b/vendor/tabled/src/settings/extract/mod.rs
new file mode 100644
index 000000000..bba90a0db
--- /dev/null
+++ b/vendor/tabled/src/settings/extract/mod.rs
@@ -0,0 +1,257 @@
+//! This module contains an [`Extract`] structure which is used to
+//! obtain an ordinary segment from the [`Table`].
+//!
+//! There's a similar structure [`Highlight`] which does a highlighting a of segments.
+//!
+//! [`Table`]: crate::Table
+//! [`Highlight`]: crate::settings::highlight::Highlight
+
+use core::cmp::min;
+use core::ops::{Bound, RangeBounds, RangeFull};
+
+use crate::{
+ grid::records::{ExactRecords, Records, Resizable},
+ settings::TableOption,
+};
+
+/// Returns a new [`Table`] that reflects a segment of the referenced [`Table`]
+///
+/// # Example
+///
+#[cfg_attr(feature = "std", doc = "```")]
+#[cfg_attr(not(feature = "std"), doc = "```ignore")]
+/// use tabled::{Table, settings::{Format, object::Rows, Modify, Extract}};
+///
+/// let data = vec![
+/// (0, "Grodno", true),
+/// (1, "Minsk", true),
+/// (2, "Hamburg", false),
+/// (3, "Brest", true),
+/// ];
+///
+/// let table = Table::new(&data)
+/// .with(Modify::new(Rows::new(1..)).with(Format::content(|s| format!(": {} :", s))))
+/// .with(Extract::segment(1..=2, 1..))
+/// .to_string();
+///
+/// assert_eq!(table, "+------------+----------+\n\
+/// | : Grodno : | : true : |\n\
+/// +------------+----------+\n\
+/// | : Minsk : | : true : |\n\
+/// +------------+----------+");
+/// ```
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct Extract<R, C> {
+ rows: R,
+ columns: C,
+}
+
+impl<R, C> Extract<R, C>
+where
+ R: RangeBounds<usize>,
+ C: RangeBounds<usize>,
+{
+ /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`]
+ ///
+ /// ```rust,no_run
+ /// # use tabled::settings::Extract;
+ /// let rows = 1..3;
+ /// let columns = 1..;
+ /// Extract::segment(rows, columns);
+ /// ```
+ ///
+ /// # Range
+ ///
+ /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`]
+ ///
+ /// If a [`RangeBounds`] argument is malformed or too large the thread will panic
+ ///
+ /// ```text
+ /// // Empty Full Out of bounds
+ /// Extract::segment(0..0, 0..0) Extract::segment(.., ..) Extract::segment(0..1, ..4)
+ /// []. . . [O O O [O O O X] //ERROR
+ /// . . . O O O . . .
+ /// . . . O O O] . . .
+ /// ```
+ ///
+ /// [`Table`]: crate::Table
+ pub fn segment(rows: R, columns: C) -> Self {
+ Extract { rows, columns }
+ }
+}
+
+impl<R> Extract<R, RangeFull>
+where
+ R: RangeBounds<usize>,
+{
+ /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`]
+ ///
+ /// The segment is defined by [`RangeBounds`] for Rows
+ ///
+ /// ```rust,no_run
+ /// # use tabled::settings::Extract;
+ /// Extract::rows(1..3);
+ /// ```
+ ///
+ /// # Range
+ ///
+ /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`]
+ ///
+ /// If a [`RangeBounds`] argument is malformed or too large the thread will panic
+ ///
+ /// ```text
+ /// // Empty Full Out of bounds
+ /// Extract::rows(0..0) Extract::rows(..) Extract::rows(0..4)
+ /// []. . . [O O O [O O O
+ /// . . . O O O O O O
+ /// . . . O O O] O O O
+ /// X X X] // ERROR
+ /// ```
+ ///
+ /// [`Table`]: crate::Table
+ pub fn rows(rows: R) -> Self {
+ Extract { rows, columns: .. }
+ }
+}
+
+impl<C> Extract<RangeFull, C>
+where
+ C: RangeBounds<usize>,
+{
+ /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`]
+ ///
+ /// The segment is defined by [`RangeBounds`] for columns.
+ ///
+ /// ```rust,no_run
+ /// # use tabled::settings::Extract;
+ /// Extract::columns(1..3);
+ /// ```
+ ///
+ /// # Range
+ ///
+ /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`]
+ ///
+ /// If a [`RangeBounds`] argument is malformed or too large the thread will panic
+ ///
+ /// ```text
+ /// // Empty Full Out of bounds
+ /// Extract::columns(0..0) Extract::columns(..) Extract::columns(0..4)
+ /// []. . . [O O O [O O O X
+ /// . . . O O O O O O X
+ /// . . . O O O] O O O X] // ERROR
+ /// ```
+ ///
+ /// [`Table`]: crate::Table
+ pub fn columns(columns: C) -> Self {
+ Extract { rows: .., columns }
+ }
+}
+
+impl<R, C, RR, D, Cfg> TableOption<RR, D, Cfg> for Extract<R, C>
+where
+ R: RangeBounds<usize> + Clone,
+ C: RangeBounds<usize> + Clone,
+ RR: Records + ExactRecords + Resizable,
+{
+ fn change(self, records: &mut RR, _: &mut Cfg, _: &mut D) {
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ let mut rows = bounds_to_usize(self.rows.start_bound(), self.rows.end_bound(), count_rows);
+ let mut cols = bounds_to_usize(
+ self.columns.start_bound(),
+ self.columns.end_bound(),
+ count_columns,
+ );
+
+ // Cleanup table in case if boundaries are exceeded.
+ //
+ // todo: can be optimized by adding a clear() method to Resizable
+ rows.0 = min(rows.0, count_rows);
+ cols.0 = min(cols.0, count_columns);
+
+ extract(records, (count_rows, count_columns), rows, cols);
+ }
+}
+
+/// Returns a new [`Grid`] that reflects a segment of the referenced [`Grid`].
+///
+/// # Example
+///
+/// ```text
+/// grid
+/// +---+---+---+
+/// |0-0|0-1|0-2|
+/// +---+---+---+
+/// |1-0|1-1|1-2|
+/// +---+---+---+
+/// |2-0|2-1|2-2|
+/// +---+---+---+
+///
+/// let rows = ..;
+/// let columns = ..1;
+/// grid.extract(rows, columns)
+///
+/// grid
+/// +---+
+/// |0-0|
+/// +---+
+/// |1-0|
+/// +---+
+/// |2-0|
+/// +---+
+/// ```
+fn extract<R>(
+ records: &mut R,
+ (count_rows, count_cols): (usize, usize),
+ (start_row, end_row): (usize, usize),
+ (start_col, end_col): (usize, usize),
+) where
+ R: Resizable,
+{
+ for (i, row) in (0..start_row).enumerate() {
+ let row = row - i;
+ records.remove_row(row);
+ }
+
+ let count_rows = count_rows - start_row;
+ let end_row = end_row - start_row;
+ for (i, row) in (end_row..count_rows).enumerate() {
+ let row = row - i;
+ records.remove_row(row);
+ }
+
+ for (i, col) in (0..start_col).enumerate() {
+ let col = col - i;
+ records.remove_column(col);
+ }
+
+ let count_cols = count_cols - start_col;
+ let end_col = end_col - start_col;
+ for (i, col) in (end_col..count_cols).enumerate() {
+ let col = col - i;
+ records.remove_column(col);
+ }
+}
+
+fn bounds_to_usize(
+ left: Bound<&usize>,
+ right: Bound<&usize>,
+ count_elements: usize,
+) -> (usize, usize) {
+ match (left, right) {
+ (Bound::Included(x), Bound::Included(y)) => (*x, y + 1),
+ (Bound::Included(x), Bound::Excluded(y)) => (*x, *y),
+ (Bound::Included(x), Bound::Unbounded) => (*x, count_elements),
+ (Bound::Unbounded, Bound::Unbounded) => (0, count_elements),
+ (Bound::Unbounded, Bound::Included(y)) => (0, y + 1),
+ (Bound::Unbounded, Bound::Excluded(y)) => (0, *y),
+ (Bound::Excluded(_), Bound::Unbounded)
+ | (Bound::Excluded(_), Bound::Included(_))
+ | (Bound::Excluded(_), Bound::Excluded(_)) => {
+ unreachable!("A start bound can't be excluded")
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/format/format_config.rs b/vendor/tabled/src/settings/format/format_config.rs
new file mode 100644
index 000000000..247ead344
--- /dev/null
+++ b/vendor/tabled/src/settings/format/format_config.rs
@@ -0,0 +1,14 @@
+use crate::settings::TableOption;
+
+/// This is a struct wrapper for a lambda which changes config.
+#[derive(Debug)]
+pub struct FormatConfig<F>(pub(crate) F);
+
+impl<F, R, D, C> TableOption<R, D, C> for FormatConfig<F>
+where
+ F: FnMut(&mut C),
+{
+ fn change(mut self, _: &mut R, cfg: &mut C, _: &mut D) {
+ (self.0)(cfg);
+ }
+}
diff --git a/vendor/tabled/src/settings/format/format_content.rs b/vendor/tabled/src/settings/format/format_content.rs
new file mode 100644
index 000000000..c14eee64f
--- /dev/null
+++ b/vendor/tabled/src/settings/format/format_content.rs
@@ -0,0 +1,86 @@
+use crate::{
+ grid::config::Entity,
+ grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut},
+ settings::{CellOption, TableOption},
+};
+
+/// A lambda which formats cell content.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct FormatContent<F> {
+ f: F,
+ multiline: bool,
+}
+
+impl<F> FormatContent<F> {
+ pub(crate) fn new(f: F) -> Self {
+ Self {
+ f,
+ multiline: false,
+ }
+ }
+}
+
+impl<F> FormatContent<F> {
+ /// Multiline a helper function for changing multiline content of cell.
+ /// Using this formatting applied for all rows not to a string as a whole.
+ ///
+ /// ```rust,no_run
+ /// use tabled::{Table, settings::{Format, object::Segment, Modify}};
+ ///
+ /// let data: Vec<&'static str> = Vec::new();
+ /// let table = Table::new(&data)
+ /// .with(Modify::new(Segment::all()).with(Format::content(|s| s.to_string()).multiline()))
+ /// .to_string();
+ /// ```
+ pub fn multiline(mut self) -> Self {
+ self.multiline = true;
+ self
+ }
+}
+
+impl<F, R, D, C> TableOption<R, D, C> for FormatContent<F>
+where
+ F: FnMut(&str) -> String + Clone,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+{
+ fn change(self, records: &mut R, cfg: &mut C, _: &mut D) {
+ CellOption::change(self, records, cfg, Entity::Global);
+ }
+}
+
+impl<F, R, C> CellOption<R, C> for FormatContent<F>
+where
+ F: FnMut(&str) -> String + Clone,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+{
+ fn change(mut self, records: &mut R, _: &mut C, entity: Entity) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ for pos in entity.iter(count_rows, count_cols) {
+ let is_valid_pos = pos.0 < count_rows && pos.1 < count_cols;
+ if !is_valid_pos {
+ continue;
+ }
+
+ let content = records.get_text(pos);
+ let content = if self.multiline {
+ multiline(self.f.clone())(content)
+ } else {
+ (self.f)(content)
+ };
+ records.set(pos, content);
+ }
+ }
+}
+
+fn multiline<F: FnMut(&str) -> String>(mut f: F) -> impl FnMut(&str) -> String {
+ move |s: &str| {
+ let mut v = Vec::new();
+ for line in s.lines() {
+ v.push(f(line));
+ }
+
+ v.join("\n")
+ }
+}
diff --git a/vendor/tabled/src/settings/format/format_positioned.rs b/vendor/tabled/src/settings/format/format_positioned.rs
new file mode 100644
index 000000000..e121f007e
--- /dev/null
+++ b/vendor/tabled/src/settings/format/format_positioned.rs
@@ -0,0 +1,51 @@
+use crate::{
+ grid::config::Entity,
+ grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut},
+ settings::{CellOption, TableOption},
+};
+
+/// [`FormatContentPositioned`] is like a [`FormatContent`] an abstraction over a function you can use against a cell.
+///
+/// It different from [`FormatContent`] that it provides a row and column index.
+///
+/// [`FormatContent`]: crate::settings::format::FormatContent
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct FormatContentPositioned<F>(F);
+
+impl<F> FormatContentPositioned<F> {
+ pub(crate) fn new(f: F) -> Self {
+ Self(f)
+ }
+}
+
+impl<F, R, D, C> TableOption<R, D, C> for FormatContentPositioned<F>
+where
+ F: FnMut(&str, (usize, usize)) -> String,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+{
+ fn change(self, records: &mut R, cfg: &mut C, _: &mut D) {
+ CellOption::change(self, records, cfg, Entity::Global);
+ }
+}
+
+impl<F, R, C> CellOption<R, C> for FormatContentPositioned<F>
+where
+ F: FnMut(&str, (usize, usize)) -> String,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+{
+ fn change(mut self, records: &mut R, _: &mut C, entity: Entity) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ for pos in entity.iter(count_rows, count_cols) {
+ let is_valid_pos = pos.0 < count_rows && pos.1 < count_cols;
+ if !is_valid_pos {
+ continue;
+ }
+
+ let content = records.get_text(pos);
+ let content = (self.0)(content, pos);
+ records.set(pos, content);
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/format/mod.rs b/vendor/tabled/src/settings/format/mod.rs
new file mode 100644
index 000000000..e8221ae32
--- /dev/null
+++ b/vendor/tabled/src/settings/format/mod.rs
@@ -0,0 +1,144 @@
+//! This module contains a list of primitives to help to modify a [`Table`].
+//!
+//! [`Table`]: crate::Table
+
+mod format_config;
+mod format_content;
+mod format_positioned;
+
+pub use format_config::FormatConfig;
+pub use format_content::FormatContent;
+pub use format_positioned::FormatContentPositioned;
+
+/// A formatting function of particular cells on a [`Table`].
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct Format;
+
+impl Format {
+ /// This function creates a new [`Format`] instance, so
+ /// it can be used as a grid setting.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use tabled::{Table, settings::{Format, object::Rows, Modify}};
+ ///
+ /// let data = vec![
+ /// (0, "Grodno", true),
+ /// (1, "Minsk", true),
+ /// (2, "Hamburg", false),
+ /// (3, "Brest", true),
+ /// ];
+ ///
+ /// let table = Table::new(&data)
+ /// .with(Modify::new(Rows::new(1..)).with(Format::content(|s| format!(": {} :", s))))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "+-------+-------------+-----------+\n\
+ /// | i32 | &str | bool |\n\
+ /// +-------+-------------+-----------+\n\
+ /// | : 0 : | : Grodno : | : true : |\n\
+ /// +-------+-------------+-----------+\n\
+ /// | : 1 : | : Minsk : | : true : |\n\
+ /// +-------+-------------+-----------+\n\
+ /// | : 2 : | : Hamburg : | : false : |\n\
+ /// +-------+-------------+-----------+\n\
+ /// | : 3 : | : Brest : | : true : |\n\
+ /// +-------+-------------+-----------+"
+ /// );
+ /// ```
+ pub fn content<F>(f: F) -> FormatContent<F>
+ where
+ F: FnMut(&str) -> String,
+ {
+ FormatContent::new(f)
+ }
+
+ /// This function creates a new [`FormatContentPositioned`], so
+ /// it can be used as a grid setting.
+ ///
+ /// It's different from [`Format::content`] as it also provides a row and column index.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use tabled::{Table, settings::{Format, object::Rows, Modify}};
+ ///
+ /// let data = vec![
+ /// (0, "Grodno", true),
+ /// (1, "Minsk", true),
+ /// (2, "Hamburg", false),
+ /// (3, "Brest", true),
+ /// ];
+ ///
+ /// let table = Table::new(&data)
+ /// .with(Modify::new(Rows::single(0)).with(Format::positioned(|_, (_, col)| col.to_string())))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "+---+---------+-------+\n\
+ /// | 0 | 1 | 2 |\n\
+ /// +---+---------+-------+\n\
+ /// | 0 | Grodno | true |\n\
+ /// +---+---------+-------+\n\
+ /// | 1 | Minsk | true |\n\
+ /// +---+---------+-------+\n\
+ /// | 2 | Hamburg | false |\n\
+ /// +---+---------+-------+\n\
+ /// | 3 | Brest | true |\n\
+ /// +---+---------+-------+"
+ /// );
+ /// ```
+ pub fn positioned<F>(f: F) -> FormatContentPositioned<F>
+ where
+ F: FnMut(&str, (usize, usize)) -> String,
+ {
+ FormatContentPositioned::new(f)
+ }
+
+ /// This function creates [`FormatConfig`] function to modify a table config.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use tabled::{
+ /// Table,
+ /// settings::{Format, object::Rows, Modify},
+ /// grid::config::ColoredConfig,
+ /// };
+ ///
+ /// let data = vec![
+ /// (0, "Grodno", true),
+ /// (1, "Minsk", true),
+ /// (2, "Hamburg", false),
+ /// (3, "Brest", true),
+ /// ];
+ ///
+ /// let table = Table::new(&data)
+ /// .with(Format::config(|cfg: &mut ColoredConfig| cfg.set_justification((0,1).into(), '.')))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "+-----+---------+-------+\n\
+ /// | i32 | &str... | bool |\n\
+ /// +-----+---------+-------+\n\
+ /// | 0 | Grodno | true |\n\
+ /// +-----+---------+-------+\n\
+ /// | 1 | Minsk | true |\n\
+ /// +-----+---------+-------+\n\
+ /// | 2 | Hamburg | false |\n\
+ /// +-----+---------+-------+\n\
+ /// | 3 | Brest | true |\n\
+ /// +-----+---------+-------+"
+ /// );
+ /// ```
+ pub fn config<F>(f: F) -> FormatConfig<F> {
+ FormatConfig(f)
+ }
+}
diff --git a/vendor/tabled/src/settings/formatting/alignment_strategy.rs b/vendor/tabled/src/settings/formatting/alignment_strategy.rs
new file mode 100644
index 000000000..c95facc5c
--- /dev/null
+++ b/vendor/tabled/src/settings/formatting/alignment_strategy.rs
@@ -0,0 +1,172 @@
+use crate::{
+ grid::config::{ColoredConfig, CompactMultilineConfig, Entity},
+ settings::{CellOption, TableOption},
+};
+
+/// `AlignmentStrategy` is a responsible for a flow how we apply an alignment.
+/// It mostly matters for multiline strings.
+///
+/// # Examples
+///
+/// ```
+/// use tabled::{
+/// Table,
+/// settings::{
+/// Style, Modify, Alignment, object::Segment,
+/// formatting::{AlignmentStrategy, TrimStrategy}
+/// }
+/// };
+///
+/// // sample_from: https://opensource.adobe.com/Spry/samples/data_region/JSONDataSetSample.html
+/// let json = r#"
+/// {
+/// "id": "0001",
+/// "type": "donut",
+/// "name": "Cake",
+/// "ppu": 0.55,
+/// "batters": {
+/// "batter": [
+/// { "id": "1001", "type": "Regular" },
+/// { "id": "1002", "type": "Chocolate" },
+/// ]
+/// },
+/// "topping": [
+/// { "id": "5001", "type": "None" },
+/// { "id": "5006", "type": "Chocolate with Sprinkles" },
+/// { "id": "5003", "type": "Chocolate" },
+/// { "id": "5004", "type": "Maple" }
+/// ]
+/// }"#;
+///
+/// let mut table = Table::new(&[json]);
+/// table
+/// .with(Style::modern())
+/// .with(Modify::new(Segment::all()).with(Alignment::right()))
+/// .with(Modify::new(Segment::all()).with(TrimStrategy::None));
+///
+/// println!("{}", table);
+///
+/// assert_eq!(
+/// format!("\n{}", table),
+/// r#"
+/// ┌───────────────────────────────────────────────────────────────┐
+/// │ &str │
+/// ├───────────────────────────────────────────────────────────────┤
+/// │ │
+/// │ { │
+/// │ "id": "0001", │
+/// │ "type": "donut", │
+/// │ "name": "Cake", │
+/// │ "ppu": 0.55, │
+/// │ "batters": { │
+/// │ "batter": [ │
+/// │ { "id": "1001", "type": "Regular" }, │
+/// │ { "id": "1002", "type": "Chocolate" }, │
+/// │ ] │
+/// │ }, │
+/// │ "topping": [ │
+/// │ { "id": "5001", "type": "None" }, │
+/// │ { "id": "5006", "type": "Chocolate with Sprinkles" }, │
+/// │ { "id": "5003", "type": "Chocolate" }, │
+/// │ { "id": "5004", "type": "Maple" } │
+/// │ ] │
+/// │ } │
+/// └───────────────────────────────────────────────────────────────┘"#);
+///
+/// table
+/// .with(Modify::new(Segment::all()).with(AlignmentStrategy::PerCell))
+/// .with(Modify::new(Segment::all()).with(TrimStrategy::Horizontal));
+///
+/// assert_eq!(
+/// format!("\n{}", table),
+/// r#"
+/// ┌───────────────────────────────────────────────────────────────┐
+/// │ &str │
+/// ├───────────────────────────────────────────────────────────────┤
+/// │ │
+/// │ { │
+/// │ "id": "0001", │
+/// │ "type": "donut", │
+/// │ "name": "Cake", │
+/// │ "ppu": 0.55, │
+/// │ "batters": { │
+/// │ "batter": [ │
+/// │ { "id": "1001", "type": "Regular" }, │
+/// │ { "id": "1002", "type": "Chocolate" }, │
+/// │ ] │
+/// │ }, │
+/// │ "topping": [ │
+/// │ { "id": "5001", "type": "None" }, │
+/// │ { "id": "5006", "type": "Chocolate with Sprinkles" }, │
+/// │ { "id": "5003", "type": "Chocolate" }, │
+/// │ { "id": "5004", "type": "Maple" } │
+/// │ ] │
+/// │ } │
+/// └───────────────────────────────────────────────────────────────┘"#);
+///
+/// table.with(Modify::new(Segment::all()).with(AlignmentStrategy::PerLine));
+///
+/// assert_eq!(
+/// format!("\n{}", table),
+/// r#"
+/// ┌───────────────────────────────────────────────────────────────┐
+/// │ &str │
+/// ├───────────────────────────────────────────────────────────────┤
+/// │ │
+/// │ { │
+/// │ "id": "0001", │
+/// │ "type": "donut", │
+/// │ "name": "Cake", │
+/// │ "ppu": 0.55, │
+/// │ "batters": { │
+/// │ "batter": [ │
+/// │ { "id": "1001", "type": "Regular" }, │
+/// │ { "id": "1002", "type": "Chocolate" }, │
+/// │ ] │
+/// │ }, │
+/// │ "topping": [ │
+/// │ { "id": "5001", "type": "None" }, │
+/// │ { "id": "5006", "type": "Chocolate with Sprinkles" }, │
+/// │ { "id": "5003", "type": "Chocolate" }, │
+/// │ { "id": "5004", "type": "Maple" } │
+/// │ ] │
+/// │ } │
+/// └───────────────────────────────────────────────────────────────┘"#);
+/// ```
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum AlignmentStrategy {
+ /// Apply alignment for cell content as a whole.
+ PerCell,
+ /// Apply alignment for each line of a cell content as a whole.
+ PerLine,
+}
+
+impl<R> CellOption<R, ColoredConfig> for AlignmentStrategy {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let mut formatting = *cfg.get_formatting(entity);
+ match &self {
+ AlignmentStrategy::PerCell => formatting.allow_lines_alignment = false,
+ AlignmentStrategy::PerLine => formatting.allow_lines_alignment = true,
+ }
+
+ cfg.set_formatting(entity, formatting);
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for AlignmentStrategy {
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ <Self as CellOption<R, ColoredConfig>>::change(self, records, cfg, Entity::Global)
+ }
+}
+
+impl<R, D> TableOption<R, D, CompactMultilineConfig> for AlignmentStrategy {
+ fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) {
+ let mut f = cfg.get_formatting();
+ match &self {
+ AlignmentStrategy::PerCell => f.allow_lines_alignment = false,
+ AlignmentStrategy::PerLine => f.allow_lines_alignment = true,
+ }
+
+ *cfg = cfg.set_formatting(f);
+ }
+}
diff --git a/vendor/tabled/src/settings/formatting/charset.rs b/vendor/tabled/src/settings/formatting/charset.rs
new file mode 100644
index 000000000..c58effcd8
--- /dev/null
+++ b/vendor/tabled/src/settings/formatting/charset.rs
@@ -0,0 +1,108 @@
+use papergrid::{
+ config::Entity,
+ records::{ExactRecords, PeekableRecords},
+};
+
+use crate::{
+ grid::records::{Records, RecordsMut},
+ settings::{CellOption, TableOption},
+};
+
+/// A structure to handle special chars.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Charset;
+
+impl Charset {
+ /// Returns [`CleanCharset`] which removes all `\t` and `\r` occurences.
+ ///
+ /// Notice that tab is just removed rather then being replaced with spaces.
+ /// You might be better call [`TabSize`] first if you not expect such behavior.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use tabled::{Table, settings::formatting::Charset};
+ ///
+ /// let text = "Some\ttext\t\twith \\tabs";
+ ///
+ /// let mut table = Table::new([text]);
+ /// table.with(Charset::clean());
+ ///
+ /// assert_eq!(
+ /// table.to_string(),
+ /// "+--------------------+\n\
+ /// | &str |\n\
+ /// +--------------------+\n\
+ /// | Sometextwith \\tabs |\n\
+ /// +--------------------+"
+ /// )
+ /// ```
+ ///
+ /// [`TabSize`]: crate::settings::formatting::TabSize
+ pub fn clean() -> CleanCharset {
+ CleanCharset
+ }
+}
+
+/// [`CleanCharset`] removes all `\t` and `\r` occurences.
+///
+/// # Example
+///
+/// ```
+/// use tabled::{Table, settings::formatting::Charset};
+///
+/// let text = "Some text which was created on windows \r\n yes they use this \\r\\n";
+///
+/// let mut builder = Table::builder([text]);
+/// builder.set_header(["win. text"]);
+///
+/// let mut table = builder.build();
+/// table.with(Charset::clean());
+///
+/// assert_eq!(
+/// table.to_string(),
+/// "+-----------------------------------------+\n\
+/// | win. text |\n\
+/// +-----------------------------------------+\n\
+/// | Some text which was created on windows |\n\
+/// | yes they use this \\r\\n |\n\
+/// +-----------------------------------------+"
+/// )
+/// ```
+#[derive(Debug, Default, Clone)]
+pub struct CleanCharset;
+
+impl<R, D, C> TableOption<R, D, C> for CleanCharset
+where
+ for<'a> &'a R: Records,
+ R: RecordsMut<String>,
+{
+ fn change(self, records: &mut R, _: &mut C, _: &mut D) {
+ let mut list = vec![];
+ for (row, cells) in records.iter_rows().into_iter().enumerate() {
+ for (col, text) in cells.into_iter().enumerate() {
+ let text = text.as_ref().replace(['\t', '\r'], "");
+ list.push(((row, col), text));
+ }
+ }
+
+ for (pos, text) in list {
+ records.set(pos, text);
+ }
+ }
+}
+
+impl<R, C> CellOption<R, C> for CleanCharset
+where
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+{
+ fn change(self, records: &mut R, _: &mut C, entity: Entity) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+ for pos in entity.iter(count_rows, count_cols) {
+ let text = records.get_text(pos);
+ let text = text.replace(['\t', '\r'], "");
+ records.set(pos, text);
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/formatting/justification.rs b/vendor/tabled/src/settings/formatting/justification.rs
new file mode 100644
index 000000000..a427eb95e
--- /dev/null
+++ b/vendor/tabled/src/settings/formatting/justification.rs
@@ -0,0 +1,127 @@
+use crate::{
+ grid::{
+ color::AnsiColor,
+ config::{ColoredConfig, Entity},
+ },
+ settings::{CellOption, Color, TableOption},
+};
+
+/// Set a justification character and a color.
+///
+/// Default value is `' '` (`<space>`) with no color.
+///
+/// # Examples
+///
+/// Setting a justification character.
+///
+/// ```
+/// use tabled::{
+/// Table,
+/// settings::formatting::Justification,
+/// };
+///
+/// let mut table = Table::new(&[("Hello", ""), ("", "World")]);
+/// table.with(Justification::new('#'));
+///
+/// assert_eq!(
+/// table.to_string(),
+/// "+-------+-------+\n\
+/// | &str# | &str# |\n\
+/// +-------+-------+\n\
+/// | Hello | ##### |\n\
+/// +-------+-------+\n\
+/// | ##### | World |\n\
+/// +-------+-------+"
+/// );
+/// ```
+///
+/// Setting a justification color.
+///
+/// ```
+/// use tabled::{
+/// Table,
+/// settings::{formatting::Justification, Color},
+/// };
+///
+/// let mut table = Table::new(&[("Hello", ""), ("", "World")]);
+/// table.with(Justification::default().color(Color::BG_BRIGHT_RED));
+///
+/// assert_eq!(
+/// table.to_string(),
+/// "+-------+-------+\n\
+/// | &str\u{1b}[101m \u{1b}[49m | &str\u{1b}[101m \u{1b}[49m |\n\
+/// +-------+-------+\n\
+/// | Hello | \u{1b}[101m \u{1b}[49m |\n\
+/// +-------+-------+\n\
+/// | \u{1b}[101m \u{1b}[49m | World |\n\
+/// +-------+-------+"
+/// );
+/// ```
+///
+/// Use different justification for different columns.
+///
+/// ```
+/// use tabled::{
+/// Table,
+/// settings::{Modify, object::Columns, formatting::Justification},
+/// };
+///
+/// let mut table = Table::new(&[("Hello", ""), ("", "World")]);
+/// table.with(Modify::new(Columns::single(0)).with(Justification::new('#')));
+/// table.with(Modify::new(Columns::single(1)).with(Justification::new('@')));
+///
+/// assert_eq!(
+/// table.to_string(),
+/// "+-------+-------+\n\
+/// | &str# | &str@ |\n\
+/// +-------+-------+\n\
+/// | Hello | @@@@@ |\n\
+/// +-------+-------+\n\
+/// | ##### | World |\n\
+/// +-------+-------+"
+/// );
+/// ```
+///
+#[derive(Debug, Default, Clone)]
+pub struct Justification {
+ c: Option<char>,
+ color: Option<AnsiColor<'static>>,
+}
+
+impl Justification {
+ /// Creates new [`Justification`] object.
+ pub fn new(c: char) -> Self {
+ Self {
+ c: Some(c),
+ color: None,
+ }
+ }
+
+ /// Sets a color for a justification.
+ pub fn color(self, color: Color) -> Self {
+ Self {
+ c: self.c,
+ color: Some(color.into()),
+ }
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for Justification {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let c = self.c.unwrap_or(' ');
+ let color = self.color;
+
+ cfg.set_justification(Entity::Global, c);
+ cfg.set_justification_color(Entity::Global, color);
+ }
+}
+
+impl<R> CellOption<R, ColoredConfig> for Justification {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let c = self.c.unwrap_or(' ');
+ let color = self.color;
+
+ cfg.set_justification(entity, c);
+ cfg.set_justification_color(entity, color);
+ }
+}
diff --git a/vendor/tabled/src/settings/formatting/mod.rs b/vendor/tabled/src/settings/formatting/mod.rs
new file mode 100644
index 000000000..e3a56092f
--- /dev/null
+++ b/vendor/tabled/src/settings/formatting/mod.rs
@@ -0,0 +1,20 @@
+//! This module contains settings for render strategy of papergrid.
+//!
+//! - [`TrimStrategy`] and [`AlignmentStrategy`] allows to set [`Alignment`] settings.
+//! - [`TabSize`] sets a default tab size.
+//! - [`Charset`] responsible for special char treatment.
+//! - [`Justification`] responsible for justification space of content.
+//!
+//! [`Alignment`]: crate::settings::Alignment
+
+mod alignment_strategy;
+mod charset;
+mod justification;
+mod tab_size;
+mod trim_strategy;
+
+pub use alignment_strategy::AlignmentStrategy;
+pub use charset::{Charset, CleanCharset};
+pub use justification::Justification;
+pub use tab_size::TabSize;
+pub use trim_strategy::TrimStrategy;
diff --git a/vendor/tabled/src/settings/formatting/tab_size.rs b/vendor/tabled/src/settings/formatting/tab_size.rs
new file mode 100644
index 000000000..677d0d613
--- /dev/null
+++ b/vendor/tabled/src/settings/formatting/tab_size.rs
@@ -0,0 +1,62 @@
+use crate::{
+ grid::records::{Records, RecordsMut},
+ settings::TableOption,
+};
+
+/// Set a tab size.
+///
+/// The size is used in order to calculate width correctly.
+///
+/// Default value is 4 (basically 1 '\t' equals 4 spaces).
+///
+/// IMPORTANT: The tab character might be not present in output,
+/// it might be replaced by spaces.
+///
+/// # Example
+///
+/// ```
+/// use tabled::{Table, settings::formatting::TabSize};
+///
+/// let text = "Some\ttext\t\twith \\tabs";
+///
+/// let mut table = Table::new([text]);
+/// table.with(TabSize::new(4));
+///
+/// assert_eq!(
+/// table.to_string(),
+/// "+--------------------------------+\n\
+/// | &str |\n\
+/// +--------------------------------+\n\
+/// | Some text with \\tabs |\n\
+/// +--------------------------------+"
+/// )
+/// ```
+#[derive(Debug, Default, Clone)]
+pub struct TabSize(usize);
+
+impl TabSize {
+ /// Creates new [`TabSize`] object.
+ pub fn new(size: usize) -> Self {
+ Self(size)
+ }
+}
+
+impl<R, D, C> TableOption<R, D, C> for TabSize
+where
+ for<'a> &'a R: Records,
+ R: RecordsMut<String>,
+{
+ fn change(self, records: &mut R, _: &mut C, _: &mut D) {
+ let mut list = vec![];
+ for (row, cells) in records.iter_rows().into_iter().enumerate() {
+ for (col, text) in cells.into_iter().enumerate() {
+ let text = text.as_ref().replace('\t', &" ".repeat(self.0));
+ list.push(((row, col), text));
+ }
+ }
+
+ for (pos, text) in list {
+ records.set(pos, text);
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/formatting/trim_strategy.rs b/vendor/tabled/src/settings/formatting/trim_strategy.rs
new file mode 100644
index 000000000..64ec7e10a
--- /dev/null
+++ b/vendor/tabled/src/settings/formatting/trim_strategy.rs
@@ -0,0 +1,118 @@
+use crate::{
+ grid::config::ColoredConfig,
+ grid::config::Entity,
+ settings::{CellOption, TableOption},
+};
+
+/// `TrimStrategy` determines if it's allowed to use empty space while doing [`Alignment`].
+///
+/// # Examples
+///
+/// ```
+/// use tabled::{
+/// Table,
+/// settings::{
+/// Style, Modify, Alignment, object::Segment,
+/// formatting::{TrimStrategy, AlignmentStrategy}
+/// }
+/// };
+///
+/// let mut table = Table::new(&[" Hello World"]);
+/// table
+/// .with(Style::modern())
+/// .with(
+/// Modify::new(Segment::all())
+/// .with(Alignment::left())
+/// .with(TrimStrategy::Horizontal)
+/// );
+///
+/// // Note that nothing was changed exactly.
+///
+/// assert_eq!(
+/// table.to_string(),
+/// "┌────────────────┐\n\
+/// │ &str │\n\
+/// ├────────────────┤\n\
+/// │ Hello World │\n\
+/// └────────────────┘"
+/// );
+///
+/// // To trim lines you would need also set [`AlignmentStrategy`].
+/// table.with(Modify::new(Segment::all()).with(AlignmentStrategy::PerLine));
+///
+/// assert_eq!(
+/// table.to_string(),
+/// "┌────────────────┐\n\
+/// │ &str │\n\
+/// ├────────────────┤\n\
+/// │ Hello World │\n\
+/// └────────────────┘"
+/// );
+///
+/// let mut table = Table::new(&[" \n\n\n Hello World"]);
+/// table
+/// .with(Style::modern())
+/// .with(
+/// Modify::new(Segment::all())
+/// .with(Alignment::center())
+/// .with(Alignment::top())
+/// .with(TrimStrategy::Vertical)
+/// );
+///
+/// assert_eq!(
+/// table.to_string(),
+/// "┌─────────────────┐\n\
+/// │ &str │\n\
+/// ├─────────────────┤\n\
+/// │ Hello World │\n\
+/// │ │\n\
+/// │ │\n\
+/// │ │\n\
+/// └─────────────────┘"
+/// );
+/// ```
+///
+/// [`Alignment`]: crate::settings::Alignment
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum TrimStrategy {
+ /// Allow vertical trim.
+ Vertical,
+ /// Allow horizontal trim.
+ Horizontal,
+ /// Allow horizontal and vertical trim.
+ Both,
+ /// Doesn't allow any trim.
+ None,
+}
+
+impl<R> CellOption<R, ColoredConfig> for TrimStrategy {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let mut formatting = *cfg.get_formatting(entity);
+
+ // todo: could be changed to be a struct an enum like consts in `impl` block.
+ match self {
+ TrimStrategy::Vertical => {
+ formatting.vertical_trim = true;
+ }
+ TrimStrategy::Horizontal => {
+ formatting.horizontal_trim = true;
+ }
+ TrimStrategy::Both => {
+ formatting.vertical_trim = true;
+ formatting.horizontal_trim = true;
+ }
+ TrimStrategy::None => {
+ formatting.vertical_trim = false;
+ formatting.horizontal_trim = false;
+ }
+ }
+
+ cfg.set_formatting(entity, formatting);
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for TrimStrategy {
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ <Self as CellOption<_, _>>::change(self, records, cfg, Entity::Global)
+ }
+}
diff --git a/vendor/tabled/src/settings/height/cell_height_increase.rs b/vendor/tabled/src/settings/height/cell_height_increase.rs
new file mode 100644
index 000000000..39887f9f6
--- /dev/null
+++ b/vendor/tabled/src/settings/height/cell_height_increase.rs
@@ -0,0 +1,98 @@
+use crate::{
+ grid::config::ColoredConfig,
+ grid::config::Entity,
+ grid::dimension::CompleteDimensionVecRecords,
+ grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut},
+ grid::util::string::count_lines,
+ settings::{measurement::Measurement, peaker::Peaker, CellOption, Height, TableOption},
+};
+
+use super::TableHeightIncrease;
+
+/// A modification for cell/table to increase its height.
+///
+/// If used for a [`Table`] [`PriorityNone`] is used.
+///
+/// [`PriorityNone`]: crate::settings::peaker::PriorityNone
+/// [`Table`]: crate::Table
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CellHeightIncrease<W = usize> {
+ height: W,
+}
+
+impl<W> CellHeightIncrease<W> {
+ /// Creates a new object of the structure.
+ pub fn new(height: W) -> Self
+ where
+ W: Measurement<Height>,
+ {
+ Self { height }
+ }
+
+ /// The priority makes scence only for table, so the function
+ /// converts it to [`TableHeightIncrease`] with a given priority.
+ pub fn priority<P>(self) -> TableHeightIncrease<W, P>
+ where
+ P: Peaker,
+ W: Measurement<Height>,
+ {
+ TableHeightIncrease::new(self.height).priority::<P>()
+ }
+}
+
+impl<W, R> CellOption<R, ColoredConfig> for CellHeightIncrease<W>
+where
+ W: Measurement<Height>,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+ for<'a> &'a R: Records,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let height = self.height.measure(&*records, cfg);
+
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ for pos in entity.iter(count_rows, count_columns) {
+ let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns;
+ if !is_valid_pos {
+ continue;
+ }
+
+ let text = records.get_text(pos);
+
+ let cell_height = count_lines(text);
+ if cell_height >= height {
+ continue;
+ }
+
+ let content = add_lines(text, height - cell_height);
+ records.set(pos, content);
+ }
+ }
+}
+
+impl<R, W> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig>
+ for CellHeightIncrease<W>
+where
+ W: Measurement<Height>,
+ R: Records + ExactRecords + PeekableRecords,
+ for<'a> &'a R: Records,
+{
+ fn change(
+ self,
+ records: &mut R,
+ cfg: &mut ColoredConfig,
+ dims: &mut CompleteDimensionVecRecords<'static>,
+ ) {
+ let height = self.height.measure(&*records, cfg);
+ TableHeightIncrease::new(height).change(records, cfg, dims)
+ }
+}
+
+fn add_lines(s: &str, n: usize) -> String {
+ let mut text = String::with_capacity(s.len() + n);
+ text.push_str(s);
+ text.extend(std::iter::repeat('\n').take(n));
+
+ text
+}
diff --git a/vendor/tabled/src/settings/height/cell_height_limit.rs b/vendor/tabled/src/settings/height/cell_height_limit.rs
new file mode 100644
index 000000000..788f0300c
--- /dev/null
+++ b/vendor/tabled/src/settings/height/cell_height_limit.rs
@@ -0,0 +1,103 @@
+use crate::{
+ grid::{
+ config::{ColoredConfig, Entity},
+ dimension::CompleteDimensionVecRecords,
+ records::{ExactRecords, PeekableRecords, Records, RecordsMut},
+ util::string::{count_lines, get_lines},
+ },
+ settings::{measurement::Measurement, peaker::Peaker, CellOption, Height, TableOption},
+};
+
+use super::table_height_limit::TableHeightLimit;
+
+/// A modification for cell/table to increase its height.
+///
+/// If used for a [`Table`] [`PriorityNone`] is used.
+///
+/// [`PriorityNone`]: crate::settings::peaker::PriorityNone
+/// [`Table`]: crate::Table
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CellHeightLimit<W = usize> {
+ height: W,
+}
+
+impl<W> CellHeightLimit<W> {
+ /// Constructs a new object.
+ pub fn new(height: W) -> Self
+ where
+ W: Measurement<Height>,
+ {
+ Self { height }
+ }
+
+ /// Set's a priority by which the limit logic will be applied.
+ pub fn priority<P>(self) -> TableHeightLimit<W, P>
+ where
+ P: Peaker,
+ W: Measurement<Height>,
+ {
+ TableHeightLimit::new(self.height).priority::<P>()
+ }
+}
+
+impl<W, R> CellOption<R, ColoredConfig> for CellHeightLimit<W>
+where
+ W: Measurement<Height>,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+ for<'a> &'a R: Records,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let height = self.height.measure(&*records, cfg);
+
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ for pos in entity.iter(count_rows, count_columns) {
+ let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns;
+ if !is_valid_pos {
+ continue;
+ }
+
+ let text = records.get_text(pos);
+ let count_lines = count_lines(text);
+
+ if count_lines <= height {
+ continue;
+ }
+
+ let content = limit_lines(text, height);
+ records.set(pos, content);
+ }
+ }
+}
+
+impl<R, W> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig>
+ for CellHeightLimit<W>
+where
+ W: Measurement<Height>,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+ for<'a> &'a R: Records,
+{
+ fn change(
+ self,
+ records: &mut R,
+ cfg: &mut ColoredConfig,
+ dims: &mut CompleteDimensionVecRecords<'static>,
+ ) {
+ let height = self.height.measure(&*records, cfg);
+ TableHeightLimit::new(height).change(records, cfg, dims)
+ }
+}
+
+fn limit_lines(s: &str, n: usize) -> String {
+ let mut text = String::new();
+ for (i, line) in get_lines(s).take(n).enumerate() {
+ if i > 0 {
+ text.push('\n');
+ }
+
+ text.push_str(&line);
+ }
+
+ text
+}
diff --git a/vendor/tabled/src/settings/height/height_list.rs b/vendor/tabled/src/settings/height/height_list.rs
new file mode 100644
index 000000000..7861dd4d2
--- /dev/null
+++ b/vendor/tabled/src/settings/height/height_list.rs
@@ -0,0 +1,47 @@
+use std::iter::FromIterator;
+
+use crate::{
+ grid::dimension::CompleteDimensionVecRecords,
+ grid::records::{ExactRecords, Records},
+ settings::TableOption,
+};
+
+/// A structure used to set [`Table`] height via a list of rows heights.
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct HeightList {
+ list: Vec<usize>,
+}
+
+impl HeightList {
+ /// Creates a new object.
+ pub fn new(list: Vec<usize>) -> Self {
+ Self { list }
+ }
+}
+
+impl From<Vec<usize>> for HeightList {
+ fn from(list: Vec<usize>) -> Self {
+ Self::new(list)
+ }
+}
+
+impl FromIterator<usize> for HeightList {
+ fn from_iter<T: IntoIterator<Item = usize>>(iter: T) -> Self {
+ Self::new(iter.into_iter().collect())
+ }
+}
+
+impl<R, C> TableOption<R, CompleteDimensionVecRecords<'static>, C> for HeightList
+where
+ R: ExactRecords + Records,
+{
+ fn change(self, records: &mut R, _: &mut C, dims: &mut CompleteDimensionVecRecords<'static>) {
+ if self.list.len() < records.count_rows() {
+ return;
+ }
+
+ let _ = dims.set_heights(self.list);
+ }
+}
diff --git a/vendor/tabled/src/settings/height/mod.rs b/vendor/tabled/src/settings/height/mod.rs
new file mode 100644
index 000000000..ad9caff77
--- /dev/null
+++ b/vendor/tabled/src/settings/height/mod.rs
@@ -0,0 +1,228 @@
+//! The module contains [`Height`] structure which is responsible for a table and cell height.
+
+mod cell_height_increase;
+mod cell_height_limit;
+mod height_list;
+mod table_height_increase;
+mod table_height_limit;
+mod util;
+
+use crate::settings::measurement::Measurement;
+
+pub use cell_height_increase::CellHeightIncrease;
+pub use cell_height_limit::CellHeightLimit;
+pub use height_list::HeightList;
+pub use table_height_increase::TableHeightIncrease;
+pub use table_height_limit::TableHeightLimit;
+
+/// Height is a abstract factory for height settings.
+///
+/// # Example
+///
+/// ```
+/// use tabled::{Table, settings::{Height, Settings}};
+///
+/// let data = vec![
+/// ("Some data", "here", "and here"),
+/// ("Some data on a next", "line", "right here"),
+/// ];
+///
+/// let table = Table::new(data)
+/// .with(Settings::new(Height::limit(10), Height::increase(10)))
+/// .to_string();
+///
+/// assert_eq!(
+/// table,
+/// "+---------------------+------+------------+\n\
+/// | &str | &str | &str |\n\
+/// | | | |\n\
+/// +---------------------+------+------------+\n\
+/// | Some data | here | and here |\n\
+/// | | | |\n\
+/// +---------------------+------+------------+\n\
+/// | Some data on a next | line | right here |\n\
+/// | | | |\n\
+/// +---------------------+------+------------+",
+/// )
+/// ```
+#[derive(Debug)]
+pub struct Height;
+
+impl Height {
+ /// Create [`CellHeightIncrease`] to set a table/cell height.
+ ///
+ /// # Example
+ ///
+ /// ## Cell height
+ ///
+ /// ```
+ /// use tabled::{Table, settings::{Height, Modify, object::Columns}};
+ ///
+ /// let data = vec![
+ /// ("Some data", "here", "and here"),
+ /// ("Some data on a next", "line", "right here"),
+ /// ];
+ ///
+ /// let table = Table::new(data)
+ /// .with(Modify::new(Columns::first()).with(Height::increase(5)))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "+---------------------+------+------------+\n\
+ /// | &str | &str | &str |\n\
+ /// | | | |\n\
+ /// | | | |\n\
+ /// | | | |\n\
+ /// | | | |\n\
+ /// +---------------------+------+------------+\n\
+ /// | Some data | here | and here |\n\
+ /// | | | |\n\
+ /// | | | |\n\
+ /// | | | |\n\
+ /// | | | |\n\
+ /// +---------------------+------+------------+\n\
+ /// | Some data on a next | line | right here |\n\
+ /// | | | |\n\
+ /// | | | |\n\
+ /// | | | |\n\
+ /// | | | |\n\
+ /// +---------------------+------+------------+"
+ /// )
+ /// ```
+ ///
+ /// ## Table height
+ ///
+ /// ```
+ /// use tabled::{Table, settings::Height};
+ ///
+ /// let data = vec![
+ /// ("Some data", "here", "and here"),
+ /// ("Some data on a next", "line", "right here"),
+ /// ];
+ ///
+ /// let table = Table::new(data)
+ /// .with(Height::increase(10))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "+---------------------+------+------------+\n\
+ /// | &str | &str | &str |\n\
+ /// | | | |\n\
+ /// +---------------------+------+------------+\n\
+ /// | Some data | here | and here |\n\
+ /// | | | |\n\
+ /// +---------------------+------+------------+\n\
+ /// | Some data on a next | line | right here |\n\
+ /// | | | |\n\
+ /// +---------------------+------+------------+",
+ /// )
+ /// ```
+ pub fn increase<W: Measurement<Height>>(width: W) -> CellHeightIncrease<W> {
+ CellHeightIncrease::new(width)
+ }
+
+ /// Create [`CellHeightLimit`] to set a table/cell height.
+ ///
+ /// # Example
+ ///
+ /// ## Cell height
+ ///
+ /// ```
+ /// use tabled::{Table, settings::{Height, Modify, object::Columns}};
+ ///
+ /// let data = vec![
+ /// ("Some\ndata", "here", "and here"),
+ /// ("Some\ndata on a next", "line", "right here"),
+ /// ];
+ ///
+ /// let table = Table::new(data)
+ /// .with(Modify::new(Columns::first()).with(Height::limit(1)))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "+------+------+------------+\n\
+ /// | &str | &str | &str |\n\
+ /// +------+------+------------+\n\
+ /// | Some | here | and here |\n\
+ /// +------+------+------------+\n\
+ /// | Some | line | right here |\n\
+ /// +------+------+------------+"
+ /// )
+ /// ```
+ ///
+ /// ## Table height
+ ///
+ /// ```
+ /// use tabled::{Table, settings::Height};
+ ///
+ /// let data = vec![
+ /// ("Some\ndata", "here", "and here"),
+ /// ("Some\ndata on a next", "line", "right here"),
+ /// ];
+ ///
+ /// let table = Table::new(&data)
+ /// .with(Height::limit(6))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "+------+------+------------+\n\
+ /// +------+------+------------+\n\
+ /// | Some | here | and here |\n\
+ /// +------+------+------------+\n\
+ /// | Some | line | right here |\n\
+ /// +------+------+------------+",
+ /// );
+ ///
+ /// let table = Table::new(&data)
+ /// .with(Height::limit(1))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "+--+--+--+\n\
+ /// +--+--+--+\n\
+ /// +--+--+--+\n\
+ /// +--+--+--+",
+ /// );
+ /// ```
+ pub fn limit<W: Measurement<Height>>(width: W) -> CellHeightLimit<W> {
+ CellHeightLimit::new(width)
+ }
+
+ /// Create [`HeightList`] to set a table height to a constant list of row heights.
+ ///
+ /// Notice if you provide a list with `.len()` less than `Table::count_rows` then it will have no affect.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use tabled::{Table, settings::{Height, Modify, object::Columns}};
+ ///
+ /// let data = vec![
+ /// ("Some\ndata", "here", "and here"),
+ /// ("Some\ndata on a next", "line", "right here"),
+ /// ];
+ ///
+ /// let table = Table::new(data)
+ /// .with(Height::list([1, 0, 2]))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "+----------------+------+------------+\n\
+ /// | &str | &str | &str |\n\
+ /// +----------------+------+------------+\n\
+ /// +----------------+------+------------+\n\
+ /// | Some | line | right here |\n\
+ /// | data on a next | | |\n\
+ /// +----------------+------+------------+",
+ /// )
+ /// ```
+ pub fn list<I: IntoIterator<Item = usize>>(rows: I) -> HeightList {
+ HeightList::new(rows.into_iter().collect())
+ }
+}
diff --git a/vendor/tabled/src/settings/height/table_height_increase.rs b/vendor/tabled/src/settings/height/table_height_increase.rs
new file mode 100644
index 000000000..f727bf041
--- /dev/null
+++ b/vendor/tabled/src/settings/height/table_height_increase.rs
@@ -0,0 +1,90 @@
+use crate::{
+ grid::{
+ config::ColoredConfig,
+ dimension::CompleteDimensionVecRecords,
+ records::{ExactRecords, PeekableRecords, Records},
+ },
+ settings::{
+ measurement::Measurement,
+ peaker::{Peaker, PriorityNone},
+ Height, TableOption,
+ },
+};
+
+use super::util::get_table_height;
+
+/// A modification of a table to increase the table height.
+#[derive(Debug, Clone)]
+pub struct TableHeightIncrease<W = usize, P = PriorityNone> {
+ height: W,
+ priority: P,
+}
+
+impl<W> TableHeightIncrease<W, PriorityNone> {
+ /// Creates a new object.
+ pub fn new(height: W) -> Self
+ where
+ W: Measurement<Height>,
+ {
+ Self {
+ height,
+ priority: PriorityNone::default(),
+ }
+ }
+
+ /// Sets a different priority logic.
+ pub fn priority<P>(self) -> TableHeightIncrease<W, P>
+ where
+ P: Peaker,
+ {
+ TableHeightIncrease {
+ priority: P::create(),
+ height: self.height,
+ }
+ }
+}
+
+impl<R, W, P> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig>
+ for TableHeightIncrease<W, P>
+where
+ W: Measurement<Height>,
+ P: Peaker + Clone,
+ R: Records + ExactRecords + PeekableRecords,
+ for<'a> &'a R: Records,
+{
+ fn change(
+ self,
+ records: &mut R,
+ cfg: &mut ColoredConfig,
+ dims: &mut CompleteDimensionVecRecords<'static>,
+ ) {
+ if records.count_rows() == 0 || records.count_columns() == 0 {
+ return;
+ }
+
+ let height = self.height.measure(&*records, cfg);
+ let (total, mut heights) = get_table_height(&*records, cfg);
+ if total >= height {
+ return;
+ }
+
+ get_increase_list(&mut heights, height, total, self.priority);
+
+ let _ = dims.set_heights(heights);
+ }
+}
+
+fn get_increase_list<P>(list: &mut [usize], total: usize, mut current: usize, mut peaker: P)
+where
+ P: Peaker,
+{
+ while current != total {
+ let col = match peaker.peak(&[], list) {
+ Some(col) => col,
+ None => break,
+ };
+
+ list[col] += 1;
+ current += 1;
+ }
+}
diff --git a/vendor/tabled/src/settings/height/table_height_limit.rs b/vendor/tabled/src/settings/height/table_height_limit.rs
new file mode 100644
index 000000000..2a3d19bb7
--- /dev/null
+++ b/vendor/tabled/src/settings/height/table_height_limit.rs
@@ -0,0 +1,123 @@
+use crate::{
+ grid::{
+ config::ColoredConfig,
+ dimension::CompleteDimensionVecRecords,
+ records::{ExactRecords, PeekableRecords, Records, RecordsMut},
+ util::string::{count_lines, get_lines},
+ },
+ settings::{
+ measurement::Measurement,
+ peaker::{Peaker, PriorityNone},
+ Height, TableOption,
+ },
+};
+
+use super::util::get_table_height;
+
+/// A modification of a table to decrease the table height.
+#[derive(Debug)]
+pub struct TableHeightLimit<W = usize, P = PriorityNone> {
+ height: W,
+ priority: P,
+}
+
+impl<W> TableHeightLimit<W, PriorityNone> {
+ /// Creates a new object.
+ pub fn new(height: W) -> Self
+ where
+ W: Measurement<Height>,
+ {
+ Self {
+ height,
+ priority: PriorityNone::default(),
+ }
+ }
+
+ /// Sets a different priority logic.
+ pub fn priority<P>(self) -> TableHeightLimit<W, P>
+ where
+ P: Peaker,
+ {
+ TableHeightLimit {
+ priority: P::create(),
+ height: self.height,
+ }
+ }
+}
+
+impl<R, W, P> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig>
+ for TableHeightLimit<W, P>
+where
+ W: Measurement<Height>,
+ P: Peaker + Clone,
+ R: ExactRecords + PeekableRecords + RecordsMut<String>,
+ for<'a> &'a R: Records,
+{
+ fn change(
+ self,
+ records: &mut R,
+ cfg: &mut ColoredConfig,
+ dims: &mut CompleteDimensionVecRecords<'static>,
+ ) {
+ let count_rows = records.count_rows();
+ let count_cols = (&*records).count_columns();
+
+ if count_rows == 0 || count_cols == 0 {
+ return;
+ }
+
+ let height = self.height.measure(&*records, cfg);
+ let (total, mut heights) = get_table_height(&*records, cfg);
+ if total <= height {
+ return;
+ }
+
+ decrease_list(&mut heights, total, height, self.priority);
+
+ for (row, &height) in heights.iter().enumerate() {
+ for col in 0..count_cols {
+ let text = records.get_text((row, col));
+ let count_lines = count_lines(text);
+
+ if count_lines <= height {
+ continue;
+ }
+
+ let text = limit_lines(text, height);
+
+ records.set((row, col), text);
+ }
+ }
+
+ let _ = dims.set_heights(heights);
+ }
+}
+
+fn decrease_list<P>(list: &mut [usize], total: usize, mut value: usize, mut peaker: P)
+where
+ P: Peaker,
+{
+ while value != total {
+ let p = peaker.peak(&[], list);
+ let row = match p {
+ Some(row) => row,
+ None => break,
+ };
+
+ list[row] -= 1;
+ value += 1;
+ }
+}
+
+fn limit_lines(s: &str, n: usize) -> String {
+ let mut text = String::new();
+ for (i, line) in get_lines(s).take(n).enumerate() {
+ if i > 0 {
+ text.push('\n');
+ }
+
+ text.push_str(&line);
+ }
+
+ text
+}
diff --git a/vendor/tabled/src/settings/height/util.rs b/vendor/tabled/src/settings/height/util.rs
new file mode 100644
index 000000000..c6917d925
--- /dev/null
+++ b/vendor/tabled/src/settings/height/util.rs
@@ -0,0 +1,22 @@
+use crate::grid::{
+ config::SpannedConfig,
+ dimension::SpannedGridDimension,
+ records::{ExactRecords, Records},
+};
+
+pub(crate) fn get_table_height<R: Records + ExactRecords>(
+ records: R,
+ cfg: &SpannedConfig,
+) -> (usize, Vec<usize>) {
+ let count_horizontals = cfg.count_horizontal(records.count_rows());
+
+ let margin = cfg.get_margin();
+ let margin_size = margin.top.size + margin.bottom.size;
+
+ let list = SpannedGridDimension::height(records, cfg);
+ let total = list.iter().sum::<usize>();
+
+ let total = total + count_horizontals + margin_size;
+
+ (total, list)
+}
diff --git a/vendor/tabled/src/settings/highlight/mod.rs b/vendor/tabled/src/settings/highlight/mod.rs
new file mode 100644
index 000000000..57df867b3
--- /dev/null
+++ b/vendor/tabled/src/settings/highlight/mod.rs
@@ -0,0 +1,452 @@
+//! This module contains a [`Highlight`] primitive, which helps
+//! changing a [`Border`] style of any segment on a [`Table`].
+//!
+//! [`Table`]: crate::Table
+
+use std::collections::HashSet;
+
+use crate::{
+ grid::{
+ config::{Border as GridBorder, ColoredConfig, Entity, Position, SpannedConfig},
+ records::{ExactRecords, Records},
+ },
+ settings::{object::Object, style::BorderColor, Border, TableOption},
+};
+
+/// Highlight modifies a table style by changing a border of a target [`Table`] segment.
+///
+/// # Example
+///
+/// ```
+/// use tabled::{
+/// Table,
+/// settings::{Highlight, Border, Style, object::Segment}
+/// };
+///
+/// let data = [
+/// ("ELF", "Extensible Linking Format", true),
+/// ("DWARF", "", true),
+/// ("PE", "Portable Executable", false),
+/// ];
+///
+/// let table = Table::new(data.iter().enumerate())
+/// .with(Style::markdown())
+/// .with(Highlight::new(Segment::all(), Border::default().top('^').bottom('v')))
+/// .to_string();
+///
+/// assert_eq!(
+/// table,
+/// concat!(
+/// " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \n",
+/// "| usize | &str | &str | bool |\n",
+/// "|-------|-------|---------------------------|-------|\n",
+/// "| 0 | ELF | Extensible Linking Format | true |\n",
+/// "| 1 | DWARF | | true |\n",
+/// "| 2 | PE | Portable Executable | false |\n",
+/// " vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ",
+/// ),
+/// );
+/// ```
+///
+/// It's possible to use [`Highlight`] for many kinds of figures.
+///
+/// ```
+/// use tabled::{
+/// Table,
+/// settings::{
+/// Highlight, Border, Style,
+/// object::{Segment, Object}
+/// }
+/// };
+///
+/// let data = [
+/// ("ELF", "Extensible Linking Format", true),
+/// ("DWARF", "", true),
+/// ("PE", "Portable Executable", false),
+/// ];
+///
+/// let table = Table::new(data.iter().enumerate())
+/// .with(Style::markdown())
+/// .with(Highlight::new(Segment::all().not((0,0).and((1, 0)).and((0, 1)).and((0, 3))), Border::filled('*')))
+/// .to_string();
+///
+/// println!("{}", table);
+///
+/// assert_eq!(
+/// table,
+/// concat!(
+/// " ***************************** \n",
+/// "| usize | &str * &str * bool |\n",
+/// "|-------*********---------------------------*********\n",
+/// "| 0 * ELF | Extensible Linking Format | true *\n",
+/// "********* *\n",
+/// "* 1 | DWARF | | true *\n",
+/// "* *\n",
+/// "* 2 | PE | Portable Executable | false *\n",
+/// "*****************************************************",
+/// ),
+/// );
+/// ```
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct Highlight<O> {
+ target: O,
+ border: Border,
+}
+
+// todo: Add BorderColor.
+
+impl<O> Highlight<O> {
+ /// Build a new instance of [`Highlight`]
+ ///
+ /// BE AWARE: if target exceeds boundaries it may panic.
+ pub fn new(target: O, border: Border) -> Self {
+ Self { target, border }
+ }
+}
+
+impl<O> Highlight<O> {
+ /// Build a new instance of [`HighlightColored`]
+ pub fn colored(target: O, border: BorderColor) -> HighlightColored<O> {
+ HighlightColored { target, border }
+ }
+}
+
+impl<O, R, D> TableOption<R, D, ColoredConfig> for Highlight<O>
+where
+ O: Object<R>,
+ R: Records + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ let cells = self.target.cells(records);
+ let segments = split_segments(cells, count_rows, count_cols);
+
+ for sector in segments {
+ set_border(cfg, &sector, self.border);
+ }
+ }
+}
+
+/// A [`Highlight`] object which works with a [`BorderColored`]
+///
+/// [`BorderColored`]: crate::settings::style::BorderColor
+#[derive(Debug)]
+pub struct HighlightColored<O> {
+ target: O,
+ border: BorderColor,
+}
+
+impl<O, R, D> TableOption<R, D, ColoredConfig> for HighlightColored<O>
+where
+ O: Object<R>,
+ R: Records + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ let cells = self.target.cells(records);
+ let segments = split_segments(cells, count_rows, count_cols);
+
+ for sector in segments {
+ set_border_color(cfg, sector, &self.border);
+ }
+ }
+}
+
+fn set_border_color(
+ cfg: &mut SpannedConfig,
+ sector: HashSet<(usize, usize)>,
+ border: &BorderColor,
+) {
+ if sector.is_empty() {
+ return;
+ }
+ let color = border.clone().into();
+ for &(row, col) in &sector {
+ let border = build_cell_border(&sector, (row, col), &color);
+ cfg.set_border_color((row, col), border);
+ }
+}
+
+fn split_segments(
+ cells: impl Iterator<Item = Entity>,
+ count_rows: usize,
+ count_cols: usize,
+) -> Vec<HashSet<(usize, usize)>> {
+ let mut segments: Vec<HashSet<(usize, usize)>> = Vec::new();
+ for entity in cells {
+ for cell in entity.iter(count_rows, count_cols) {
+ let found_segment = segments
+ .iter_mut()
+ .find(|s| s.iter().any(|&c| is_cell_connected(cell, c)));
+
+ match found_segment {
+ Some(segment) => {
+ let _ = segment.insert(cell);
+ }
+ None => {
+ let mut segment = HashSet::new();
+ let _ = segment.insert(cell);
+ segments.push(segment);
+ }
+ }
+ }
+ }
+
+ let mut squashed_segments: Vec<HashSet<(usize, usize)>> = Vec::new();
+ while !segments.is_empty() {
+ let mut segment = segments.remove(0);
+
+ let mut i = 0;
+ while i < segments.len() {
+ if is_segment_connected(&segment, &segments[i]) {
+ segment.extend(&segments[i]);
+ let _ = segments.remove(i);
+ } else {
+ i += 1;
+ }
+ }
+
+ squashed_segments.push(segment);
+ }
+
+ squashed_segments
+}
+
+fn is_cell_connected((row1, col1): (usize, usize), (row2, col2): (usize, usize)) -> bool {
+ if col1 == col2 && row1 == row2 + 1 {
+ return true;
+ }
+
+ if col1 == col2 && (row2 > 0 && row1 == row2 - 1) {
+ return true;
+ }
+
+ if row1 == row2 && col1 == col2 + 1 {
+ return true;
+ }
+
+ if row1 == row2 && (col2 > 0 && col1 == col2 - 1) {
+ return true;
+ }
+
+ false
+}
+
+fn is_segment_connected(
+ segment1: &HashSet<(usize, usize)>,
+ segment2: &HashSet<(usize, usize)>,
+) -> bool {
+ for &cell1 in segment1.iter() {
+ for &cell2 in segment2.iter() {
+ if is_cell_connected(cell1, cell2) {
+ return true;
+ }
+ }
+ }
+
+ false
+}
+
+fn set_border(cfg: &mut SpannedConfig, sector: &HashSet<(usize, usize)>, border: Border) {
+ if sector.is_empty() {
+ return;
+ }
+
+ let border = border.into();
+ for &pos in sector {
+ let border = build_cell_border(sector, pos, &border);
+ cfg.set_border(pos, border);
+ }
+}
+
+fn build_cell_border<T>(
+ sector: &HashSet<(usize, usize)>,
+ (row, col): Position,
+ border: &GridBorder<T>,
+) -> GridBorder<T>
+where
+ T: Default + Clone,
+{
+ let cell_has_top_neighbor = cell_has_top_neighbor(sector, row, col);
+ let cell_has_bottom_neighbor = cell_has_bottom_neighbor(sector, row, col);
+ let cell_has_left_neighbor = cell_has_left_neighbor(sector, row, col);
+ let cell_has_right_neighbor = cell_has_right_neighbor(sector, row, col);
+
+ let this_has_left_top_neighbor = is_there_left_top_cell(sector, row, col);
+ let this_has_right_top_neighbor = is_there_right_top_cell(sector, row, col);
+ let this_has_left_bottom_neighbor = is_there_left_bottom_cell(sector, row, col);
+ let this_has_right_bottom_neighbor = is_there_right_bottom_cell(sector, row, col);
+
+ let mut cell_border = GridBorder::default();
+ if let Some(c) = border.top.clone() {
+ if !cell_has_top_neighbor {
+ cell_border.top = Some(c.clone());
+
+ if cell_has_right_neighbor && !this_has_right_top_neighbor {
+ cell_border.right_top_corner = Some(c);
+ }
+ }
+ }
+ if let Some(c) = border.bottom.clone() {
+ if !cell_has_bottom_neighbor {
+ cell_border.bottom = Some(c.clone());
+
+ if cell_has_right_neighbor && !this_has_right_bottom_neighbor {
+ cell_border.right_bottom_corner = Some(c);
+ }
+ }
+ }
+ if let Some(c) = border.left.clone() {
+ if !cell_has_left_neighbor {
+ cell_border.left = Some(c.clone());
+
+ if cell_has_bottom_neighbor && !this_has_left_bottom_neighbor {
+ cell_border.left_bottom_corner = Some(c);
+ }
+ }
+ }
+ if let Some(c) = border.right.clone() {
+ if !cell_has_right_neighbor {
+ cell_border.right = Some(c.clone());
+
+ if cell_has_bottom_neighbor && !this_has_right_bottom_neighbor {
+ cell_border.right_bottom_corner = Some(c);
+ }
+ }
+ }
+ if let Some(c) = border.left_top_corner.clone() {
+ if !cell_has_left_neighbor && !cell_has_top_neighbor {
+ cell_border.left_top_corner = Some(c);
+ }
+ }
+ if let Some(c) = border.left_bottom_corner.clone() {
+ if !cell_has_left_neighbor && !cell_has_bottom_neighbor {
+ cell_border.left_bottom_corner = Some(c);
+ }
+ }
+ if let Some(c) = border.right_top_corner.clone() {
+ if !cell_has_right_neighbor && !cell_has_top_neighbor {
+ cell_border.right_top_corner = Some(c);
+ }
+ }
+ if let Some(c) = border.right_bottom_corner.clone() {
+ if !cell_has_right_neighbor && !cell_has_bottom_neighbor {
+ cell_border.right_bottom_corner = Some(c);
+ }
+ }
+ {
+ if !cell_has_bottom_neighbor {
+ if !cell_has_left_neighbor && this_has_left_top_neighbor {
+ if let Some(c) = border.right_top_corner.clone() {
+ cell_border.left_top_corner = Some(c);
+ }
+ }
+
+ if cell_has_left_neighbor && this_has_left_bottom_neighbor {
+ if let Some(c) = border.left_top_corner.clone() {
+ cell_border.left_bottom_corner = Some(c);
+ }
+ }
+
+ if !cell_has_right_neighbor && this_has_right_top_neighbor {
+ if let Some(c) = border.left_top_corner.clone() {
+ cell_border.right_top_corner = Some(c);
+ }
+ }
+
+ if cell_has_right_neighbor && this_has_right_bottom_neighbor {
+ if let Some(c) = border.right_top_corner.clone() {
+ cell_border.right_bottom_corner = Some(c);
+ }
+ }
+ }
+
+ if !cell_has_top_neighbor {
+ if !cell_has_left_neighbor && this_has_left_bottom_neighbor {
+ if let Some(c) = border.right_bottom_corner.clone() {
+ cell_border.left_bottom_corner = Some(c);
+ }
+ }
+
+ if cell_has_left_neighbor && this_has_left_top_neighbor {
+ if let Some(c) = border.left_bottom_corner.clone() {
+ cell_border.left_top_corner = Some(c);
+ }
+ }
+
+ if !cell_has_right_neighbor && this_has_right_bottom_neighbor {
+ if let Some(c) = border.left_bottom_corner.clone() {
+ cell_border.right_bottom_corner = Some(c);
+ }
+ }
+
+ if cell_has_right_neighbor && this_has_right_top_neighbor {
+ if let Some(c) = border.right_bottom_corner.clone() {
+ cell_border.right_top_corner = Some(c);
+ }
+ }
+ }
+ }
+
+ cell_border
+}
+
+fn cell_has_top_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
+ row > 0 && sector.contains(&(row - 1, col))
+}
+
+fn cell_has_bottom_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
+ sector.contains(&(row + 1, col))
+}
+
+fn cell_has_left_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
+ col > 0 && sector.contains(&(row, col - 1))
+}
+
+fn cell_has_right_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
+ sector.contains(&(row, col + 1))
+}
+
+fn is_there_left_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
+ row > 0 && col > 0 && sector.contains(&(row - 1, col - 1))
+}
+
+fn is_there_right_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
+ row > 0 && sector.contains(&(row - 1, col + 1))
+}
+
+fn is_there_left_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
+ col > 0 && sector.contains(&(row + 1, col - 1))
+}
+
+fn is_there_right_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
+ sector.contains(&(row + 1, col + 1))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_is_connected() {
+ assert!(is_cell_connected((0, 0), (0, 1)));
+ assert!(is_cell_connected((0, 0), (1, 0)));
+ assert!(!is_cell_connected((0, 0), (1, 1)));
+
+ assert!(is_cell_connected((0, 1), (0, 0)));
+ assert!(is_cell_connected((1, 0), (0, 0)));
+ assert!(!is_cell_connected((1, 1), (0, 0)));
+
+ assert!(is_cell_connected((1, 1), (0, 1)));
+ assert!(is_cell_connected((1, 1), (1, 0)));
+ assert!(is_cell_connected((1, 1), (2, 1)));
+ assert!(is_cell_connected((1, 1), (1, 2)));
+ assert!(!is_cell_connected((1, 1), (1, 1)));
+ }
+}
diff --git a/vendor/tabled/src/settings/locator/mod.rs b/vendor/tabled/src/settings/locator/mod.rs
new file mode 100644
index 000000000..b48391bb7
--- /dev/null
+++ b/vendor/tabled/src/settings/locator/mod.rs
@@ -0,0 +1,202 @@
+//! The module contains a [`Locator`] trait and implementations for it.
+
+use core::ops::Bound;
+use std::{
+ iter::{self, Once},
+ ops::{Range, RangeBounds},
+};
+
+use crate::{
+ grid::config::Entity,
+ grid::records::{ExactRecords, PeekableRecords, Records},
+ settings::object::{
+ Column, Columns, FirstColumn, FirstRow, LastColumn, LastRow, Object, Row, Rows,
+ },
+};
+
+/// Locator is an interface which searches for a particular thing in the [`Records`],
+/// and returns coordinate of the foundings if any.
+pub trait Locator<Records> {
+ /// A coordinate of the finding.
+ type Coordinate;
+ /// An iterator of the coordinates.
+ /// If it's empty it's considered that nothing is found.
+ type IntoIter: IntoIterator<Item = Self::Coordinate>;
+
+ /// Search for the thing in [`Records`], returning a list of coordinates.
+ fn locate(&mut self, records: Records) -> Self::IntoIter;
+}
+
+impl<B, R> Locator<R> for Columns<B>
+where
+ B: RangeBounds<usize>,
+ R: Records,
+{
+ type Coordinate = usize;
+ type IntoIter = Range<usize>;
+
+ fn locate(&mut self, records: R) -> Self::IntoIter {
+ let range = self.get_range();
+ let max = records.count_columns();
+ let (from, to) = bounds_to_usize(range.start_bound(), range.end_bound(), max);
+
+ from..to
+ }
+}
+
+impl<R> Locator<R> for Column {
+ type Coordinate = usize;
+ type IntoIter = Once<usize>;
+
+ fn locate(&mut self, _: R) -> Self::IntoIter {
+ iter::once((*self).into())
+ }
+}
+
+impl<R> Locator<R> for FirstColumn {
+ type Coordinate = usize;
+ type IntoIter = Once<usize>;
+
+ fn locate(&mut self, _: R) -> Self::IntoIter {
+ iter::once(0)
+ }
+}
+
+impl<R> Locator<R> for LastColumn
+where
+ R: Records,
+{
+ type Coordinate = usize;
+ type IntoIter = Once<usize>;
+
+ fn locate(&mut self, records: R) -> Self::IntoIter {
+ if records.count_columns() > 0 {
+ iter::once(records.count_columns() - 1)
+ } else {
+ iter::once(0)
+ }
+ }
+}
+
+impl<B, R> Locator<R> for Rows<B>
+where
+ R: Records,
+ B: RangeBounds<usize>,
+{
+ type Coordinate = usize;
+ type IntoIter = Range<usize>;
+
+ fn locate(&mut self, records: R) -> Self::IntoIter {
+ let (from, to) = bounds_to_usize(
+ self.get_range().start_bound(),
+ self.get_range().end_bound(),
+ records.count_columns(),
+ );
+
+ from..to
+ }
+}
+
+impl<R> Locator<R> for Row {
+ type Coordinate = usize;
+ type IntoIter = Once<usize>;
+
+ fn locate(&mut self, _: R) -> Self::IntoIter {
+ iter::once((*self).into())
+ }
+}
+
+impl<R> Locator<R> for FirstRow {
+ type Coordinate = usize;
+ type IntoIter = Once<usize>;
+
+ fn locate(&mut self, _: R) -> Self::IntoIter {
+ iter::once(0)
+ }
+}
+
+impl<R> Locator<R> for LastRow
+where
+ R: ExactRecords,
+{
+ type Coordinate = usize;
+ type IntoIter = Once<usize>;
+
+ fn locate(&mut self, records: R) -> Self::IntoIter {
+ if records.count_rows() > 0 {
+ iter::once(records.count_rows() - 1)
+ } else {
+ iter::once(0)
+ }
+ }
+}
+
+/// The structure is an implementation of [`Locator`] to search for a column by it's name.
+/// A name is considered be a value in a first row.
+///
+/// So even if in reality there's no header, the first row will be considered to be one.
+#[derive(Debug, Clone, Copy)]
+pub struct ByColumnName<S>(S);
+
+impl<S> ByColumnName<S> {
+ /// Constructs a new object of the structure.
+ pub fn new(text: S) -> Self
+ where
+ S: AsRef<str>,
+ {
+ Self(text)
+ }
+}
+
+impl<R, S> Locator<R> for ByColumnName<S>
+where
+ S: AsRef<str>,
+ R: Records + ExactRecords + PeekableRecords,
+{
+ type Coordinate = usize;
+ type IntoIter = Vec<usize>;
+
+ fn locate(&mut self, records: R) -> Self::IntoIter {
+ // todo: can be optimized by creating Iterator
+ (0..records.count_columns())
+ .filter(|col| records.get_text((0, *col)) == self.0.as_ref())
+ .collect::<Vec<_>>()
+ }
+}
+
+impl<S, R> Object<R> for ByColumnName<S>
+where
+ S: AsRef<str>,
+ R: Records + PeekableRecords + ExactRecords,
+{
+ type Iter = std::vec::IntoIter<Entity>;
+
+ fn cells(&self, records: &R) -> Self::Iter {
+ // todo: can be optimized by creating Iterator
+ (0..records.count_columns())
+ .filter(|col| records.get_text((0, *col)) == self.0.as_ref())
+ .map(Entity::Column)
+ .collect::<Vec<_>>()
+ .into_iter()
+ }
+}
+
+fn bounds_to_usize(
+ left: Bound<&usize>,
+ right: Bound<&usize>,
+ count_elements: usize,
+) -> (usize, usize) {
+ match (left, right) {
+ (Bound::Included(x), Bound::Included(y)) => (*x, y + 1),
+ (Bound::Included(x), Bound::Excluded(y)) => (*x, *y),
+ (Bound::Included(x), Bound::Unbounded) => (*x, count_elements),
+ (Bound::Unbounded, Bound::Unbounded) => (0, count_elements),
+ (Bound::Unbounded, Bound::Included(y)) => (0, y + 1),
+ (Bound::Unbounded, Bound::Excluded(y)) => (0, *y),
+ (Bound::Excluded(_), Bound::Unbounded)
+ | (Bound::Excluded(_), Bound::Included(_))
+ | (Bound::Excluded(_), Bound::Excluded(_)) => {
+ unreachable!("A start bound can't be excluded")
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/margin/mod.rs b/vendor/tabled/src/settings/margin/mod.rs
new file mode 100644
index 000000000..b86a1d3e2
--- /dev/null
+++ b/vendor/tabled/src/settings/margin/mod.rs
@@ -0,0 +1,137 @@
+//! This module contains a Margin settings of a [`Table`].
+//!
+//! # Example
+//!
+#![cfg_attr(feature = "std", doc = "```")]
+#![cfg_attr(not(feature = "std"), doc = "```ignore")]
+//! use tabled::{settings::{Margin, Style}, Table};
+//!
+//! let data = vec!["Hello", "World", "!"];
+//!
+//! let mut table = Table::new(data);
+//! table.with(Style::markdown()).with(Margin::new(3, 3, 1, 0));
+//!
+//! assert_eq!(
+//! table.to_string(),
+//! concat!(
+//! " \n",
+//! " | &str | \n",
+//! " |-------| \n",
+//! " | Hello | \n",
+//! " | World | \n",
+//! " | ! | ",
+//! )
+//! );
+//! ```
+//!
+//! [`Table`]: crate::Table
+
+use crate::{
+ grid::{
+ color::StaticColor,
+ config::{CompactConfig, CompactMultilineConfig},
+ config::{Indent, Sides},
+ },
+ settings::TableOption,
+};
+
+#[cfg(feature = "std")]
+use crate::grid::{color::AnsiColor, config::ColoredConfig};
+
+/// Margin is responsible for a left/right/top/bottom outer indent of a grid.
+///
+#[cfg_attr(feature = "std", doc = "```")]
+#[cfg_attr(not(feature = "std"), doc = "```ignore")]
+/// # use tabled::{settings::Margin, Table};
+/// # let data: Vec<&'static str> = Vec::new();
+/// let table = Table::new(&data)
+/// .with(Margin::new(1, 1, 1, 1).fill('>', '<', 'V', '^'));
+/// ```
+#[derive(Debug, Clone)]
+pub struct Margin<C = StaticColor> {
+ indent: Sides<Indent>,
+ colors: Option<Sides<C>>,
+}
+
+impl Margin {
+ /// Construct's an Margin object.
+ ///
+ /// It uses space(' ') as a default fill character.
+ /// To set a custom character you can use [`Margin::fill`] function.
+ pub const fn new(left: usize, right: usize, top: usize, bottom: usize) -> Self {
+ Self {
+ indent: Sides::new(
+ Indent::spaced(left),
+ Indent::spaced(right),
+ Indent::spaced(top),
+ Indent::spaced(bottom),
+ ),
+ colors: None,
+ }
+ }
+}
+
+impl<Color> Margin<Color> {
+ /// The function, sets a characters for the margin on an each side.
+ pub const fn fill(mut self, left: char, right: char, top: char, bottom: char) -> Self {
+ self.indent.left.fill = left;
+ self.indent.right.fill = right;
+ self.indent.top.fill = top;
+ self.indent.bottom.fill = bottom;
+ self
+ }
+
+ /// The function, sets a characters for the margin on an each side.
+ pub fn colorize<C>(self, left: C, right: C, top: C, bottom: C) -> Margin<C> {
+ Margin {
+ indent: self.indent,
+ colors: Some(Sides::new(left, right, top, bottom)),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl<R, D, C> TableOption<R, D, ColoredConfig> for Margin<C>
+where
+ C: Into<AnsiColor<'static>> + Clone,
+{
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let indent = self.indent;
+ let margin = Sides::new(indent.left, indent.right, indent.top, indent.bottom);
+ cfg.set_margin(margin);
+
+ if let Some(colors) = &self.colors {
+ let margin = Sides::new(
+ Some(colors.left.clone().into()),
+ Some(colors.right.clone().into()),
+ Some(colors.top.clone().into()),
+ Some(colors.bottom.clone().into()),
+ );
+ cfg.set_margin_color(margin);
+ }
+ }
+}
+
+impl<R, D, C> TableOption<R, D, CompactConfig> for Margin<C>
+where
+ C: Into<StaticColor> + Clone,
+{
+ fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) {
+ *cfg = cfg.set_margin(self.indent);
+
+ if let Some(c) = self.colors {
+ // todo: make a new method (BECAUSE INTO doesn't work) try_into();
+ let colors = Sides::new(c.left.into(), c.right.into(), c.top.into(), c.bottom.into());
+ *cfg = cfg.set_margin_color(colors);
+ }
+ }
+}
+
+impl<R, D, C> TableOption<R, D, CompactMultilineConfig> for Margin<C>
+where
+ C: Into<StaticColor> + Clone,
+{
+ fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) {
+ self.change(records, cfg.as_mut(), dimension)
+ }
+}
diff --git a/vendor/tabled/src/settings/measurement/mod.rs b/vendor/tabled/src/settings/measurement/mod.rs
new file mode 100644
index 000000000..f93cc6b4e
--- /dev/null
+++ b/vendor/tabled/src/settings/measurement/mod.rs
@@ -0,0 +1,161 @@
+//! The module contains [`Measurement`] trait and its implementations to be used in [`Height`] and [`Width`].;
+
+use crate::{
+ grid::config::SpannedConfig,
+ grid::dimension::SpannedGridDimension,
+ grid::records::{ExactRecords, PeekableRecords, Records},
+ grid::util::string::{self, string_width_multiline},
+ settings::{Height, Width},
+};
+
+/// A width value which can be obtained on behalf of [`Table`].
+///
+/// [`Table`]: crate::Table
+pub trait Measurement<Attribute> {
+ /// Returns a measurement value.
+ fn measure<R: Records + ExactRecords + PeekableRecords>(
+ &self,
+ records: R,
+ cfg: &SpannedConfig,
+ ) -> usize;
+}
+
+impl<T> Measurement<T> for usize {
+ fn measure<R>(&self, _: R, _: &SpannedConfig) -> usize {
+ *self
+ }
+}
+
+/// Max width value.
+#[derive(Debug)]
+pub struct Max;
+
+impl Measurement<Width> for Max {
+ fn measure<R: Records + ExactRecords + PeekableRecords>(
+ &self,
+ records: R,
+ _: &SpannedConfig,
+ ) -> usize {
+ grid_widths(&records)
+ .map(|r| r.max().unwrap_or(0))
+ .max()
+ .unwrap_or(0)
+ }
+}
+
+impl Measurement<Height> for Max {
+ fn measure<R: Records + ExactRecords + PeekableRecords>(
+ &self,
+ records: R,
+ _: &SpannedConfig,
+ ) -> usize {
+ records_heights(&records)
+ .map(|r| r.max().unwrap_or(0))
+ .max()
+ .unwrap_or(0)
+ }
+}
+
+/// Min width value.
+#[derive(Debug)]
+pub struct Min;
+
+impl Measurement<Width> for Min {
+ fn measure<R: Records + ExactRecords + PeekableRecords>(
+ &self,
+ records: R,
+ _: &SpannedConfig,
+ ) -> usize {
+ grid_widths(&records)
+ .map(|r| r.min().unwrap_or(0))
+ .max()
+ .unwrap_or(0)
+ }
+}
+
+impl Measurement<Height> for Min {
+ fn measure<R: Records + ExactRecords + PeekableRecords>(
+ &self,
+ records: R,
+ _: &SpannedConfig,
+ ) -> usize {
+ records_heights(&records)
+ .map(|r| r.max().unwrap_or(0))
+ .min()
+ .unwrap_or(0)
+ }
+}
+
+/// Percent from a total table width.
+#[derive(Debug)]
+pub struct Percent(pub usize);
+
+impl Measurement<Width> for Percent {
+ fn measure<R>(&self, records: R, cfg: &SpannedConfig) -> usize
+ where
+ R: Records,
+ {
+ let (_, total) = get_table_widths_with_total(records, cfg);
+ (total * self.0) / 100
+ }
+}
+
+impl Measurement<Height> for Percent {
+ fn measure<R>(&self, records: R, cfg: &SpannedConfig) -> usize
+ where
+ R: Records + ExactRecords,
+ {
+ let (_, total) = get_table_heights_width_total(records, cfg);
+ (total * self.0) / 100
+ }
+}
+
+fn grid_widths<R: Records + ExactRecords + PeekableRecords>(
+ records: &R,
+) -> impl Iterator<Item = impl Iterator<Item = usize> + '_> + '_ {
+ let (count_rows, count_cols) = (records.count_rows(), records.count_columns());
+ (0..count_rows).map(move |row| {
+ (0..count_cols).map(move |col| string_width_multiline(records.get_text((row, col))))
+ })
+}
+
+fn get_table_widths_with_total<R>(records: R, cfg: &SpannedConfig) -> (Vec<usize>, usize)
+where
+ R: Records,
+{
+ let widths = SpannedGridDimension::width(records, cfg);
+ let total_width = get_table_total_width(&widths, cfg);
+ (widths, total_width)
+}
+
+fn get_table_total_width(list: &[usize], cfg: &SpannedConfig) -> usize {
+ let total = list.iter().sum::<usize>();
+
+ total + cfg.count_vertical(list.len())
+}
+
+fn records_heights<R>(records: &R) -> impl Iterator<Item = impl Iterator<Item = usize> + '_> + '_
+where
+ R: Records + ExactRecords + PeekableRecords,
+{
+ (0..records.count_rows()).map(move |row| {
+ (0..records.count_columns())
+ .map(move |col| string::count_lines(records.get_text((row, col))))
+ })
+}
+
+fn get_table_heights_width_total<R>(records: R, cfg: &SpannedConfig) -> (Vec<usize>, usize)
+where
+ R: Records,
+{
+ let list = SpannedGridDimension::height(records, cfg);
+ let total = get_table_total_height(&list, cfg);
+ (list, total)
+}
+
+fn get_table_total_height(list: &[usize], cfg: &SpannedConfig) -> usize {
+ let total = list.iter().sum::<usize>();
+ let counth = cfg.count_horizontal(list.len());
+
+ total + counth
+}
diff --git a/vendor/tabled/src/settings/merge/mod.rs b/vendor/tabled/src/settings/merge/mod.rs
new file mode 100644
index 000000000..51fa2bbf6
--- /dev/null
+++ b/vendor/tabled/src/settings/merge/mod.rs
@@ -0,0 +1,196 @@
+//! The module contains a set of methods to merge cells together via [`Span`]s.
+//!
+//! [`Span`]: crate::settings::span::Span
+
+use crate::{
+ grid::config::ColoredConfig,
+ grid::records::{ExactRecords, PeekableRecords, Records},
+ settings::TableOption,
+};
+
+/// Merge to combine duplicates together, using [`Span`].
+///
+/// [`Span`]: crate::settings::span::Span
+#[derive(Debug)]
+pub struct Merge;
+
+impl Merge {
+ /// Vertical merge.
+ pub fn vertical() -> MergeDuplicatesVertical {
+ MergeDuplicatesVertical
+ }
+
+ /// Horizontal merge.
+ pub fn horizontal() -> MergeDuplicatesHorizontal {
+ MergeDuplicatesHorizontal
+ }
+}
+
+/// A modificator for [`Table`] which looks up for duplicates in columns and
+/// in case of duplicate merges the cells together using [`Span`].
+///
+/// [`Table`]: crate::Table
+/// [`Span`]: crate::settings::span::Span
+#[derive(Debug)]
+pub struct MergeDuplicatesVertical;
+
+impl<R, D> TableOption<R, D, ColoredConfig> for MergeDuplicatesVertical
+where
+ R: Records + PeekableRecords + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ if count_rows == 0 || count_cols == 0 {
+ return;
+ }
+
+ for column in 0..count_cols {
+ let mut repeat_length = 0;
+ let mut repeat_value = String::new();
+ let mut repeat_is_set = false;
+ let mut last_is_row_span = false;
+ for row in (0..count_rows).rev() {
+ if last_is_row_span {
+ last_is_row_span = false;
+ continue;
+ }
+
+ // we need to mitigate messing existing spans
+ let is_cell_visible = cfg.is_cell_visible((row, column));
+ let is_row_span_cell = cfg.get_column_span((row, column)).is_some();
+
+ if !repeat_is_set {
+ if !is_cell_visible {
+ continue;
+ }
+
+ if is_row_span_cell {
+ continue;
+ }
+
+ repeat_length = 1;
+ repeat_value = records.get_text((row, column)).to_owned();
+ repeat_is_set = true;
+ continue;
+ }
+
+ if is_row_span_cell {
+ repeat_is_set = false;
+ last_is_row_span = true;
+ continue;
+ }
+
+ if !is_cell_visible {
+ repeat_is_set = false;
+ continue;
+ }
+
+ let text = records.get_text((row, column));
+ let is_duplicate = text == repeat_value;
+
+ if is_duplicate {
+ repeat_length += 1;
+ continue;
+ }
+
+ if repeat_length > 1 {
+ cfg.set_row_span((row + 1, column), repeat_length);
+ }
+
+ repeat_length = 1;
+ repeat_value = records.get_text((row, column)).to_owned();
+ }
+
+ if repeat_length > 1 {
+ cfg.set_row_span((0, column), repeat_length);
+ }
+ }
+ }
+}
+
+/// A modificator for [`Table`] which looks up for duplicates in rows and
+/// in case of duplicate merges the cells together using [`Span`].
+///
+/// [`Table`]: crate::Table
+/// [`Span`]: crate::settings::span::Span
+#[derive(Debug)]
+pub struct MergeDuplicatesHorizontal;
+
+impl<R, D> TableOption<R, D, ColoredConfig> for MergeDuplicatesHorizontal
+where
+ R: Records + PeekableRecords + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ if count_rows == 0 || count_cols == 0 {
+ return;
+ }
+
+ for row in 0..count_rows {
+ let mut repeat_length = 0;
+ let mut repeat_value = String::new();
+ let mut repeat_is_set = false;
+ let mut last_is_col_span = false;
+
+ for column in (0..count_cols).rev() {
+ if last_is_col_span {
+ last_is_col_span = false;
+ continue;
+ }
+
+ // we need to mitigate messing existing spans
+ let is_cell_visible = cfg.is_cell_visible((row, column));
+ let is_col_span_cell = cfg.get_row_span((row, column)).is_some();
+
+ if !repeat_is_set {
+ if !is_cell_visible {
+ continue;
+ }
+
+ if is_col_span_cell {
+ continue;
+ }
+
+ repeat_length = 1;
+ repeat_value = records.get_text((row, column)).to_owned();
+ repeat_is_set = true;
+ continue;
+ }
+
+ if is_col_span_cell {
+ repeat_is_set = false;
+ last_is_col_span = true;
+ continue;
+ }
+
+ if !is_cell_visible {
+ repeat_is_set = false;
+ continue;
+ }
+
+ let text = records.get_text((row, column));
+ let is_duplicate = text == repeat_value;
+
+ if is_duplicate {
+ repeat_length += 1;
+ continue;
+ }
+
+ if repeat_length > 1 {
+ cfg.set_column_span((row, column + 1), repeat_length);
+ }
+
+ repeat_length = 1;
+ repeat_value = records.get_text((row, column)).to_owned();
+ }
+
+ if repeat_length > 1 {
+ cfg.set_column_span((row, 0), repeat_length);
+ }
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/mod.rs b/vendor/tabled/src/settings/mod.rs
new file mode 100644
index 000000000..a89037cbd
--- /dev/null
+++ b/vendor/tabled/src/settings/mod.rs
@@ -0,0 +1,135 @@
+//! Module contains various table configuration settings.
+//!
+//! There 2 types of settings;
+//!
+//! - [`CellOption`] which can modify only a cell.
+//! - [`TableOption`] which can modify table as a whole.
+//!
+//! [`CellOption`] works on behave of [`Modify`] which is actually a [`TableOption`].
+//!
+//! Notice that it's possble to combine settings together by the help of [`Settings`].
+//!
+#![cfg_attr(feature = "std", doc = "```")]
+#![cfg_attr(not(feature = "std"), doc = "```ignore")]
+//! use tabled::{Table, settings::{Settings, Style, Padding}};
+//!
+//! let table_config = Settings::default()
+//! .with(Padding::new(2, 2, 1, 1))
+//! .with(Style::rounded());
+//!
+//! let data = [[2023;9]; 3];
+//!
+//! let table = Table::new(data).with(table_config).to_string();
+//!
+//! assert_eq!(
+//! table,
+//! "╭────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────╮\n\
+//! │ │ │ │ │ │ │ │ │ │\n\
+//! │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │\n\
+//! │ │ │ │ │ │ │ │ │ │\n\
+//! ├────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┤\n\
+//! │ │ │ │ │ │ │ │ │ │\n\
+//! │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │\n\
+//! │ │ │ │ │ │ │ │ │ │\n\
+//! │ │ │ │ │ │ │ │ │ │\n\
+//! │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │\n\
+//! │ │ │ │ │ │ │ │ │ │\n\
+//! │ │ │ │ │ │ │ │ │ │\n\
+//! │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │\n\
+//! │ │ │ │ │ │ │ │ │ │\n\
+//! ╰────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────╯"
+//! )
+//! ```
+
+mod cell_option;
+mod settings_list;
+mod table_option;
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod object;
+
+#[cfg(feature = "std")]
+mod modify;
+
+mod alignment;
+mod extract;
+mod margin;
+mod padding;
+mod rotate;
+
+#[cfg(feature = "std")]
+mod color;
+#[cfg(feature = "std")]
+mod concat;
+#[cfg(feature = "std")]
+mod duplicate;
+
+pub mod style;
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod disable;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod format;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod formatting;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod height;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod highlight;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod locator;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod measurement;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod merge;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod panel;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod peaker;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+mod shadow;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod span;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod split;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod themes;
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod width;
+
+pub use cell_option::CellOption;
+pub use settings_list::{EmptySettings, Settings};
+pub use table_option::TableOption;
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub use modify::{Modify, ModifyList};
+
+pub use self::{
+ alignment::Alignment, extract::Extract, margin::Margin, padding::Padding, rotate::Rotate,
+ style::Style,
+};
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub use self::{
+ color::Color, concat::Concat, disable::Disable, duplicate::Dup, format::Format, height::Height,
+ highlight::Highlight, merge::Merge, panel::Panel, shadow::Shadow, span::Span, style::Border,
+ width::Width,
+};
diff --git a/vendor/tabled/src/settings/modify.rs b/vendor/tabled/src/settings/modify.rs
new file mode 100644
index 000000000..c712a255e
--- /dev/null
+++ b/vendor/tabled/src/settings/modify.rs
@@ -0,0 +1,81 @@
+use crate::{
+ grid::records::{ExactRecords, Records},
+ settings::{object::Object, CellOption, Settings, TableOption},
+};
+
+/// Modify structure provide an abstraction, to be able to apply
+/// a set of [`CellOption`]s to the same object.
+///
+/// Be aware that the settings are applied all to a cell at a time.
+/// So sometimes you may need to make a several calls of [`Modify`] in order to achieve the desired affect.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Modify<O> {
+ obj: O,
+}
+
+impl<O> Modify<O> {
+ /// Creates a new [`Modify`] without any options.
+ pub const fn new(obj: O) -> Self {
+ Self { obj }
+ }
+
+ /// A function which combines together [`Modify::new`] and [`Modify::with`] calls.
+ pub const fn list<M>(obj: O, next: M) -> ModifyList<O, M> {
+ ModifyList {
+ obj,
+ modifiers: next,
+ }
+ }
+
+ /// It's a generic function which stores a [`CellOption`].
+ ///
+ /// IMPORTANT:
+ /// The function *doesn't* changes a [`Table`].
+ /// [`Table`] will be changed only after passing [`Modify`] object to [`Table::with`].
+ ///
+ /// [`Table`]: crate::Table
+ /// [`Table::with`]: crate::Table::with
+ pub fn with<M>(self, next: M) -> ModifyList<O, M> {
+ ModifyList {
+ obj: self.obj,
+ modifiers: next,
+ }
+ }
+}
+
+/// This is a container of [`CellOption`]s which are applied to a set [`Object`].
+#[derive(Debug)]
+pub struct ModifyList<O, S> {
+ obj: O,
+ modifiers: S,
+}
+
+impl<O, M1> ModifyList<O, M1> {
+ /// With a generic function which stores a [`CellOption`].
+ ///
+ /// IMPORTANT:
+ /// The function *doesn't* changes a [`Table`].
+ /// [`Table`] will be changed only after passing [`Modify`] object to [`Table::with`].
+ ///
+ /// [`Table`]: crate::Table
+ /// [`Table::with`]: crate::Table::with
+ pub fn with<M2>(self, next: M2) -> ModifyList<O, Settings<M1, M2>> {
+ ModifyList {
+ obj: self.obj,
+ modifiers: Settings::new(self.modifiers, next),
+ }
+ }
+}
+
+impl<O, M, R, D, C> TableOption<R, D, C> for ModifyList<O, M>
+where
+ O: Object<R>,
+ M: CellOption<R, C> + Clone,
+ R: Records + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut C, _: &mut D) {
+ for entity in self.obj.cells(records) {
+ self.modifiers.clone().change(records, cfg, entity);
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/object/cell.rs b/vendor/tabled/src/settings/object/cell.rs
new file mode 100644
index 000000000..0b0463c9a
--- /dev/null
+++ b/vendor/tabled/src/settings/object/cell.rs
@@ -0,0 +1,70 @@
+use crate::{
+ grid::config::{Entity, Position},
+ settings::object::Object,
+};
+
+/// Cell denotes a particular cell on a [`Table`].
+///
+/// For example such table has 4 cells.
+/// Which indexes are (0, 0), (0, 1), (1, 0), (1, 1).
+///
+/// ```text
+/// ┌───┬───┐
+/// │ 0 │ 1 │
+/// ├───┼───┤
+/// │ 1 │ 2 │
+/// └───┴───┘
+/// ```
+///
+/// [`Table`]: crate::Table
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Cell(usize, usize);
+
+impl Cell {
+ /// Create new cell structure.
+ pub fn new(row: usize, col: usize) -> Self {
+ Self(row, col)
+ }
+}
+
+impl From<Position> for Cell {
+ fn from((row, col): Position) -> Self {
+ Self(row, col)
+ }
+}
+
+impl<I> Object<I> for Cell {
+ type Iter = EntityOnce;
+
+ fn cells(&self, _: &I) -> Self::Iter {
+ EntityOnce::new(Some(Entity::Cell(self.0, self.1)))
+ }
+}
+
+impl<I> Object<I> for Position {
+ type Iter = EntityOnce;
+
+ fn cells(&self, _: &I) -> Self::Iter {
+ EntityOnce::new(Some(Entity::Cell(self.0, self.1)))
+ }
+}
+
+/// An [`Iterator`] which returns an entity once.
+#[derive(Debug)]
+pub struct EntityOnce {
+ entity: Option<Entity>,
+}
+
+impl EntityOnce {
+ pub(crate) const fn new(entity: Option<Entity>) -> Self {
+ Self { entity }
+ }
+}
+
+impl Iterator for EntityOnce {
+ type Item = Entity;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.entity.take()
+ }
+}
diff --git a/vendor/tabled/src/settings/object/columns.rs b/vendor/tabled/src/settings/object/columns.rs
new file mode 100644
index 000000000..bcbe2acf5
--- /dev/null
+++ b/vendor/tabled/src/settings/object/columns.rs
@@ -0,0 +1,209 @@
+use std::ops::{Add, RangeBounds, Sub};
+
+use crate::{
+ grid::config::Entity,
+ grid::records::{ExactRecords, Records},
+ settings::object::{cell::EntityOnce, Object},
+};
+
+use super::util::bounds_to_usize;
+
+/// Column denotes a set of cells on given columns on a [`Table`].
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct Columns<R> {
+ range: R,
+}
+
+impl<R> Columns<R> {
+ /// Returns a new instance of [`Columns`] for a range of columns.
+ ///
+ /// If the boundaries are exceeded it may panic.
+ pub fn new(range: R) -> Self
+ where
+ R: RangeBounds<usize>,
+ {
+ Self { range }
+ }
+
+ pub(crate) fn get_range(&self) -> &R {
+ &self.range
+ }
+}
+
+impl Columns<()> {
+ /// Returns a new instance of [`Columns`] for a single column.
+ ///
+ /// If the boundaries are exceeded it may panic.
+ pub fn single(index: usize) -> Column {
+ Column(index)
+ }
+
+ /// Returns a new instance of [`Columns`] for a first column.
+ ///
+ /// If the boundaries are exceeded the object will produce no cells.
+ pub fn first() -> FirstColumn {
+ FirstColumn
+ }
+
+ /// Returns a new instance of [`Columns`] for a last column.
+ ///
+ /// If the boundaries are exceeded the object will produce no cells.
+ pub fn last() -> LastColumn {
+ LastColumn
+ }
+}
+
+impl<I, R> Object<I> for Columns<R>
+where
+ R: RangeBounds<usize>,
+ I: Records,
+{
+ type Iter = ColumnsIter;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ let max = records.count_columns();
+ let start = self.range.start_bound();
+ let end = self.range.end_bound();
+ let (x, y) = bounds_to_usize(start, end, max);
+
+ ColumnsIter::new(x, y)
+ }
+}
+
+/// `FirstColumn` represents the first column on a grid.
+#[derive(Debug)]
+pub struct FirstColumn;
+
+impl<I> Object<I> for FirstColumn
+where
+ I: Records + ExactRecords,
+{
+ type Iter = EntityOnce;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ if records.count_rows() == 0 || records.count_columns() == 0 {
+ return EntityOnce::new(None);
+ }
+
+ EntityOnce::new(Some(Entity::Column(0)))
+ }
+}
+
+impl Add<usize> for FirstColumn {
+ type Output = Column;
+
+ fn add(self, rhs: usize) -> Self::Output {
+ Column(rhs)
+ }
+}
+
+/// `LastColumn` represents the last column on a grid.
+#[derive(Debug)]
+pub struct LastColumn;
+
+impl<I> Object<I> for LastColumn
+where
+ I: Records + ExactRecords,
+{
+ type Iter = EntityOnce;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ if records.count_rows() == 0 || records.count_columns() == 0 {
+ return EntityOnce::new(None);
+ }
+
+ let col = records.count_columns().saturating_sub(1);
+ EntityOnce::new(Some(Entity::Column(col)))
+ }
+}
+
+impl Sub<usize> for LastColumn {
+ type Output = LastColumnOffset;
+
+ fn sub(self, rhs: usize) -> Self::Output {
+ LastColumnOffset { offset: rhs }
+ }
+}
+
+/// Column represents a single column on a grid.
+#[derive(Debug, Clone, Copy)]
+pub struct Column(usize);
+
+impl<I> Object<I> for Column {
+ type Iter = EntityOnce;
+
+ fn cells(&self, _: &I) -> Self::Iter {
+ EntityOnce::new(Some(Entity::Column(self.0)))
+ }
+}
+
+impl From<usize> for Column {
+ fn from(i: usize) -> Self {
+ Self(i)
+ }
+}
+
+impl From<Column> for usize {
+ fn from(val: Column) -> Self {
+ val.0
+ }
+}
+
+/// `LastColumnOffset` represents a single column on a grid indexed via offset from the last column.
+#[derive(Debug)]
+pub struct LastColumnOffset {
+ offset: usize,
+}
+
+impl<I> Object<I> for LastColumnOffset
+where
+ I: Records + ExactRecords,
+{
+ type Iter = EntityOnce;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ if records.count_rows() == 0 || records.count_columns() == 0 {
+ return EntityOnce::new(None);
+ }
+
+ let col = records.count_columns().saturating_sub(1);
+ if self.offset > col {
+ return EntityOnce::new(None);
+ }
+
+ let col = col - self.offset;
+ EntityOnce::new(Some(Entity::Column(col)))
+ }
+}
+
+/// An [`Iterator`] which goes goes over columns of a [`Table`].
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct ColumnsIter {
+ start: usize,
+ end: usize,
+}
+
+impl ColumnsIter {
+ const fn new(start: usize, end: usize) -> Self {
+ Self { start, end }
+ }
+}
+
+impl Iterator for ColumnsIter {
+ type Item = Entity;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.start >= self.end {
+ return None;
+ }
+
+ let col = self.start;
+ self.start += 1;
+
+ Some(Entity::Column(col))
+ }
+}
diff --git a/vendor/tabled/src/settings/object/frame.rs b/vendor/tabled/src/settings/object/frame.rs
new file mode 100644
index 000000000..caeb10640
--- /dev/null
+++ b/vendor/tabled/src/settings/object/frame.rs
@@ -0,0 +1,69 @@
+use crate::{
+ grid::config::Entity,
+ grid::records::{ExactRecords, Records},
+ settings::object::Object,
+};
+
+/// Frame includes cells which are on the edges of each side.
+/// Therefore it's [`Object`] implementation returns a subset of cells which are present in frame.
+#[derive(Debug)]
+pub struct Frame;
+
+impl<I> Object<I> for Frame
+where
+ I: Records + ExactRecords,
+{
+ type Iter = FrameIter;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ FrameIter::new(records.count_rows(), records.count_columns())
+ }
+}
+
+/// An [`Iterator`] which goes goes over all cell on a frame of a [`Table`].
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct FrameIter {
+ rows: usize,
+ cols: usize,
+ row: usize,
+ col: usize,
+}
+
+impl FrameIter {
+ const fn new(count_rows: usize, count_columns: usize) -> Self {
+ Self {
+ rows: count_rows,
+ cols: count_columns,
+ row: 0,
+ col: 0,
+ }
+ }
+}
+
+impl Iterator for FrameIter {
+ type Item = Entity;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.cols == 0 || self.rows == 0 {
+ return None;
+ }
+
+ if self.row == self.rows {
+ return None;
+ }
+
+ let row = self.row;
+ let col = self.col;
+
+ self.col += 1;
+
+ if self.col == self.cols {
+ self.row += 1;
+ self.col = 0;
+ }
+
+ Some(Entity::Cell(row, col))
+ }
+}
diff --git a/vendor/tabled/src/settings/object/mod.rs b/vendor/tabled/src/settings/object/mod.rs
new file mode 100644
index 000000000..46893c71d
--- /dev/null
+++ b/vendor/tabled/src/settings/object/mod.rs
@@ -0,0 +1,765 @@
+//! This module contains a list of primitives that implement a [`Object`] trait.
+//! They help to locate a necessary segment on a [`Table`].
+//!
+//! [`Table`]: crate::Table
+
+mod cell;
+mod columns;
+mod frame;
+mod rows;
+mod segment;
+pub(crate) mod util;
+
+use std::{collections::HashSet, marker::PhantomData};
+
+use self::segment::SectorCellsIter;
+
+use crate::{
+ grid::config::{Entity, EntityIterator},
+ grid::records::{ExactRecords, Records},
+};
+
+pub use cell::{Cell, EntityOnce};
+pub use columns::{Column, Columns, ColumnsIter, FirstColumn, LastColumn, LastColumnOffset};
+pub use frame::{Frame, FrameIter};
+pub use rows::{FirstRow, LastRow, LastRowOffset, Row, Rows, RowsIter};
+pub use segment::{SectorIter, Segment, SegmentAll};
+
+/// Object helps to locate a necessary part of a [`Table`].
+///
+/// [`Table`]: crate::Table
+pub trait Object<R> {
+ /// An [`Iterator`] which returns a list of cells.
+ type Iter: Iterator<Item = Entity>;
+
+ /// Cells returns a set of coordinates of cells.
+ fn cells(&self, records: &R) -> Self::Iter;
+
+ /// Combines cells.
+ /// It doesn't repeat cells.
+ fn and<O>(self, rhs: O) -> UnionCombination<Self, O, R>
+ where
+ Self: Sized,
+ {
+ UnionCombination::new(self, rhs)
+ }
+
+ /// Excludes rhs cells from this cells.
+ fn not<O>(self, rhs: O) -> DiffCombination<Self, O, R>
+ where
+ Self: Sized,
+ {
+ DiffCombination::new(self, rhs)
+ }
+
+ /// Returns cells which are present in both [`Object`]s only.
+ fn intersect<O>(self, rhs: O) -> IntersectionCombination<Self, O, R>
+ where
+ Self: Sized,
+ {
+ IntersectionCombination::new(self, rhs)
+ }
+
+ /// Returns cells which are not present in target [`Object`].
+ fn inverse(self) -> InversionCombination<Self, R>
+ where
+ Self: Sized,
+ {
+ InversionCombination::new(self)
+ }
+}
+
+/// Combination struct used for chaining [`Object`]'s.
+///
+/// Combines 2 sets of cells into one.
+///
+/// Duplicates are removed from the output set.
+#[derive(Debug)]
+pub struct UnionCombination<L, R, I> {
+ lhs: L,
+ rhs: R,
+ _records: PhantomData<I>,
+}
+
+impl<L, R, I> UnionCombination<L, R, I> {
+ fn new(lhs: L, rhs: R) -> Self {
+ Self {
+ lhs,
+ rhs,
+ _records: PhantomData,
+ }
+ }
+}
+
+impl<I, L, R> Object<I> for UnionCombination<L, R, I>
+where
+ L: Object<I>,
+ R: Object<I>,
+ I: Records + ExactRecords,
+{
+ type Iter = UnionIter<L::Iter, R::Iter>;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ let lhs = self.lhs.cells(records);
+ let rhs = self.rhs.cells(records);
+
+ UnionIter::new(lhs, rhs, records.count_rows(), records.count_columns())
+ }
+}
+
+/// Difference struct used for chaining [`Object`]'s.
+///
+/// Returns cells from 1st set with removed ones from the 2nd set.
+#[derive(Debug)]
+pub struct DiffCombination<L, R, I> {
+ lhs: L,
+ rhs: R,
+ _records: PhantomData<I>,
+}
+
+impl<L, R, I> DiffCombination<L, R, I> {
+ fn new(lhs: L, rhs: R) -> Self {
+ Self {
+ lhs,
+ rhs,
+ _records: PhantomData,
+ }
+ }
+}
+
+impl<I, L, R> Object<I> for DiffCombination<L, R, I>
+where
+ L: Object<I>,
+ R: Object<I>,
+ I: Records + ExactRecords,
+{
+ type Iter = DiffIter<L::Iter>;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ let lhs = self.lhs.cells(records);
+ let rhs = self.rhs.cells(records);
+
+ DiffIter::new(lhs, rhs, records.count_rows(), records.count_columns())
+ }
+}
+
+/// Intersection struct used for chaining [`Object`]'s.
+///
+/// Returns cells which are present in 2 sets.
+/// But not in one of them
+#[derive(Debug)]
+pub struct IntersectionCombination<L, R, I> {
+ lhs: L,
+ rhs: R,
+ _records: PhantomData<I>,
+}
+
+impl<L, R, I> IntersectionCombination<L, R, I> {
+ fn new(lhs: L, rhs: R) -> Self {
+ Self {
+ lhs,
+ rhs,
+ _records: PhantomData,
+ }
+ }
+}
+
+impl<I, L, R> Object<I> for IntersectionCombination<L, R, I>
+where
+ L: Object<I>,
+ R: Object<I>,
+ I: Records + ExactRecords,
+{
+ type Iter = IntersectIter<L::Iter>;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ let lhs = self.lhs.cells(records);
+ let rhs = self.rhs.cells(records);
+
+ IntersectIter::new(lhs, rhs, records.count_rows(), records.count_columns())
+ }
+}
+
+/// Inversion struct used for chaining [`Object`]'s.
+///
+/// Returns cells which are present in 2 sets.
+/// But not in one of them
+#[derive(Debug)]
+pub struct InversionCombination<O, I> {
+ obj: O,
+ _records: PhantomData<I>,
+}
+
+impl<O, I> InversionCombination<O, I> {
+ fn new(obj: O) -> Self {
+ Self {
+ obj,
+ _records: PhantomData,
+ }
+ }
+}
+
+impl<I, O> Object<I> for InversionCombination<O, I>
+where
+ O: Object<I>,
+ I: Records + ExactRecords,
+{
+ type Iter = InversionIter;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ let obj = self.obj.cells(records);
+
+ InversionIter::new(obj, records.count_rows(), records.count_columns())
+ }
+}
+
+/// An [`Iterator`] which goes over a combination [`Object::Iter`].
+#[derive(Debug)]
+pub struct UnionIter<L, R> {
+ lhs: Option<L>,
+ rhs: R,
+ seen: HashSet<(usize, usize)>,
+ current: Option<EntityIterator>,
+ count_rows: usize,
+ count_cols: usize,
+}
+
+impl<L, R> UnionIter<L, R>
+where
+ L: Iterator<Item = Entity>,
+ R: Iterator<Item = Entity>,
+{
+ fn new(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self {
+ let size = match lhs.size_hint() {
+ (s1, Some(s2)) if s1 == s2 => s1,
+ _ => 0,
+ };
+
+ Self {
+ lhs: Some(lhs),
+ rhs,
+ seen: HashSet::with_capacity(size),
+ current: None,
+ count_rows,
+ count_cols,
+ }
+ }
+}
+
+impl<L, R> Iterator for UnionIter<L, R>
+where
+ L: Iterator<Item = Entity>,
+ R: Iterator<Item = Entity>,
+{
+ type Item = Entity;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Some(iter) = self.current.as_mut() {
+ for p in iter.by_ref() {
+ if self.lhs.is_none() && self.seen.contains(&p) {
+ continue;
+ }
+
+ let _ = self.seen.insert(p);
+ return Some(Entity::Cell(p.0, p.1));
+ }
+ }
+
+ if let Some(lhs) = self.lhs.as_mut() {
+ for entity in lhs.by_ref() {
+ let mut iter = entity.iter(self.count_rows, self.count_cols);
+ if let Some(p) = iter.by_ref().next() {
+ let _ = self.seen.insert(p);
+ self.current = Some(iter);
+ return Some(Entity::Cell(p.0, p.1));
+ }
+ }
+
+ self.lhs = None;
+ }
+
+ for entity in self.rhs.by_ref() {
+ let mut iter = entity.iter(self.count_rows, self.count_cols);
+
+ for p in iter.by_ref() {
+ if !self.seen.contains(&p) {
+ let _ = self.seen.insert(p);
+ self.current = Some(iter);
+ return Some(Entity::Cell(p.0, p.1));
+ }
+ }
+ }
+
+ None
+ }
+}
+
+/// An [`Iterator`] which goes over only cells which are present in first [`Object::Iter`] but not second.
+#[derive(Debug)]
+pub struct DiffIter<L> {
+ lhs: L,
+ seen: HashSet<(usize, usize)>,
+ count_rows: usize,
+ count_cols: usize,
+ current: Option<EntityIterator>,
+}
+
+impl<L> DiffIter<L>
+where
+ L: Iterator<Item = Entity>,
+{
+ fn new<R>(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self
+ where
+ R: Iterator<Item = Entity>,
+ {
+ let size = match rhs.size_hint() {
+ (s1, Some(s2)) if s1 == s2 => s1,
+ _ => 0,
+ };
+
+ let mut seen = HashSet::with_capacity(size);
+ for entity in rhs {
+ seen.extend(entity.iter(count_rows, count_cols));
+ }
+
+ Self {
+ lhs,
+ seen,
+ count_rows,
+ count_cols,
+ current: None,
+ }
+ }
+}
+
+impl<L> Iterator for DiffIter<L>
+where
+ L: Iterator<Item = Entity>,
+{
+ type Item = Entity;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Some(iter) = self.current.as_mut() {
+ for p in iter.by_ref() {
+ if !self.seen.contains(&p) {
+ return Some(Entity::Cell(p.0, p.1));
+ }
+ }
+ }
+
+ for entity in self.lhs.by_ref() {
+ let mut iter = entity.iter(self.count_rows, self.count_cols);
+
+ for p in iter.by_ref() {
+ if !self.seen.contains(&p) {
+ self.current = Some(iter);
+ return Some(Entity::Cell(p.0, p.1));
+ }
+ }
+ }
+
+ None
+ }
+}
+
+/// An [`Iterator`] which goes goes over cells which are present in both [`Object::Iter`]ators.
+#[derive(Debug)]
+pub struct IntersectIter<L> {
+ lhs: L,
+ seen: HashSet<(usize, usize)>,
+ count_rows: usize,
+ count_cols: usize,
+ current: Option<EntityIterator>,
+}
+
+impl<L> IntersectIter<L>
+where
+ L: Iterator<Item = Entity>,
+{
+ fn new<R>(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self
+ where
+ R: Iterator<Item = Entity>,
+ {
+ let size = match rhs.size_hint() {
+ (s1, Some(s2)) if s1 == s2 => s1,
+ _ => 0,
+ };
+
+ let mut seen = HashSet::with_capacity(size);
+ for entity in rhs {
+ seen.extend(entity.iter(count_rows, count_cols));
+ }
+
+ Self {
+ lhs,
+ seen,
+ count_rows,
+ count_cols,
+ current: None,
+ }
+ }
+}
+
+impl<L> Iterator for IntersectIter<L>
+where
+ L: Iterator<Item = Entity>,
+{
+ type Item = Entity;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Some(iter) = self.current.as_mut() {
+ for p in iter.by_ref() {
+ if self.seen.contains(&p) {
+ return Some(Entity::Cell(p.0, p.1));
+ }
+ }
+ }
+
+ for entity in self.lhs.by_ref() {
+ let mut iter = entity.iter(self.count_rows, self.count_cols);
+
+ for p in iter.by_ref() {
+ if self.seen.contains(&p) {
+ self.current = Some(iter);
+ return Some(Entity::Cell(p.0, p.1));
+ }
+ }
+ }
+
+ None
+ }
+}
+
+/// An [`Iterator`] which goes goes over cells which are not present an [`Object::Iter`]ator.
+#[derive(Debug)]
+pub struct InversionIter {
+ all: SectorCellsIter,
+ seen: HashSet<(usize, usize)>,
+}
+
+impl InversionIter {
+ fn new<O>(obj: O, count_rows: usize, count_columns: usize) -> Self
+ where
+ O: Iterator<Item = Entity>,
+ {
+ let size = match obj.size_hint() {
+ (s1, Some(s2)) if s1 == s2 => s1,
+ _ => 0,
+ };
+
+ let mut seen = HashSet::with_capacity(size);
+ for entity in obj {
+ seen.extend(entity.iter(count_rows, count_columns));
+ }
+
+ let all = SectorCellsIter::new(0, count_rows, 0, count_columns);
+
+ Self { all, seen }
+ }
+}
+
+impl Iterator for InversionIter {
+ type Item = Entity;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ for p in self.all.by_ref() {
+ if !self.seen.contains(&p) {
+ return Some(Entity::Cell(p.0, p.1));
+ }
+ }
+
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::grid::records::vec_records::VecRecords;
+
+ use super::*;
+
+ #[test]
+ fn cell_test() {
+ assert_eq!(vec_cells((0, 0), 2, 3), [Entity::Cell(0, 0)]);
+ assert_eq!(vec_cells((1, 1), 2, 3), [Entity::Cell(1, 1)]);
+ assert_eq!(vec_cells((1, 1), 0, 0), [Entity::Cell(1, 1)]);
+ assert_eq!(vec_cells((1, 100), 2, 3), [Entity::Cell(1, 100)]);
+ assert_eq!(vec_cells((100, 1), 2, 3), [Entity::Cell(100, 1)]);
+ }
+
+ #[test]
+ fn columns_test() {
+ assert_eq!(
+ vec_cells(Columns::new(..), 2, 3),
+ [Entity::Column(0), Entity::Column(1), Entity::Column(2)]
+ );
+ assert_eq!(
+ vec_cells(Columns::new(1..), 2, 3),
+ [Entity::Column(1), Entity::Column(2)]
+ );
+ assert_eq!(vec_cells(Columns::new(2..), 2, 3), [Entity::Column(2)]);
+ assert_eq!(vec_cells(Columns::new(3..), 2, 3), []);
+ assert_eq!(vec_cells(Columns::new(3..), 0, 0), []);
+ assert_eq!(vec_cells(Columns::new(0..1), 2, 3), [Entity::Column(0)]);
+ assert_eq!(vec_cells(Columns::new(1..2), 2, 3), [Entity::Column(1)]);
+ assert_eq!(vec_cells(Columns::new(2..3), 2, 3), [Entity::Column(2)]);
+ assert_eq!(vec_cells(Columns::new(..), 0, 0), []);
+ assert_eq!(vec_cells(Columns::new(..), 2, 0), []);
+ assert_eq!(vec_cells(Columns::new(..), 0, 3), []);
+ }
+
+ #[test]
+ fn first_column_test() {
+ assert_eq!(vec_cells(Columns::first(), 5, 2), [Entity::Column(0)]);
+ assert_eq!(vec_cells(Columns::first(), 0, 0), []);
+ assert_eq!(vec_cells(Columns::first(), 10, 0), []);
+ assert_eq!(vec_cells(Columns::first(), 0, 10), []);
+ }
+
+ #[test]
+ fn last_column_test() {
+ assert_eq!(vec_cells(Columns::last(), 5, 2), [Entity::Column(1)]);
+ assert_eq!(vec_cells(Columns::last(), 5, 29), [Entity::Column(28)]);
+ assert_eq!(vec_cells(Columns::last(), 0, 0), []);
+ assert_eq!(vec_cells(Columns::last(), 10, 0), []);
+ assert_eq!(vec_cells(Columns::last(), 0, 10), []);
+ }
+
+ #[test]
+ fn last_column_sub_test() {
+ assert_eq!(vec_cells(Columns::last(), 5, 2), [Entity::Column(1)]);
+ assert_eq!(vec_cells(Columns::last() - 0, 5, 2), [Entity::Column(1)]);
+ assert_eq!(vec_cells(Columns::last() - 1, 5, 2), [Entity::Column(0)]);
+ assert_eq!(vec_cells(Columns::last() - 2, 5, 2), []);
+ assert_eq!(vec_cells(Columns::last() - 100, 5, 2), []);
+ }
+
+ #[test]
+ fn first_column_add_test() {
+ assert_eq!(vec_cells(Columns::first(), 5, 2), [Entity::Column(0)]);
+ assert_eq!(vec_cells(Columns::first() + 0, 5, 2), [Entity::Column(0)]);
+ assert_eq!(vec_cells(Columns::first() + 1, 5, 2), [Entity::Column(1)]);
+ assert_eq!(vec_cells(Columns::first() + 2, 5, 2), [Entity::Column(2)]);
+ assert_eq!(
+ vec_cells(Columns::first() + 100, 5, 2),
+ [Entity::Column(100)]
+ );
+ }
+
+ #[test]
+ fn rows_test() {
+ assert_eq!(
+ vec_cells(Rows::new(..), 2, 3),
+ [Entity::Row(0), Entity::Row(1)]
+ );
+ assert_eq!(vec_cells(Rows::new(1..), 2, 3), [Entity::Row(1)]);
+ assert_eq!(vec_cells(Rows::new(2..), 2, 3), []);
+ assert_eq!(vec_cells(Rows::new(2..), 0, 0), []);
+ assert_eq!(vec_cells(Rows::new(0..1), 2, 3), [Entity::Row(0)],);
+ assert_eq!(vec_cells(Rows::new(1..2), 2, 3), [Entity::Row(1)],);
+ assert_eq!(vec_cells(Rows::new(..), 0, 0), []);
+ assert_eq!(vec_cells(Rows::new(..), 0, 3), []);
+ assert_eq!(
+ vec_cells(Rows::new(..), 2, 0),
+ [Entity::Row(0), Entity::Row(1)]
+ );
+ }
+
+ #[test]
+ fn last_row_test() {
+ assert_eq!(vec_cells(Rows::last(), 5, 2), [Entity::Row(4)]);
+ assert_eq!(vec_cells(Rows::last(), 100, 2), [Entity::Row(99)]);
+ assert_eq!(vec_cells(Rows::last(), 0, 0), []);
+ assert_eq!(vec_cells(Rows::last(), 5, 0), []);
+ assert_eq!(vec_cells(Rows::last(), 0, 2), []);
+ }
+
+ #[test]
+ fn first_row_test() {
+ assert_eq!(vec_cells(Rows::first(), 5, 2), [Entity::Row(0)]);
+ assert_eq!(vec_cells(Rows::first(), 100, 2), [Entity::Row(0)]);
+ assert_eq!(vec_cells(Rows::first(), 0, 0), []);
+ assert_eq!(vec_cells(Rows::first(), 5, 0), []);
+ assert_eq!(vec_cells(Rows::first(), 0, 2), []);
+ }
+
+ #[test]
+ fn last_row_sub_test() {
+ assert_eq!(vec_cells(Rows::last(), 5, 2), [Entity::Row(4)]);
+ assert_eq!(vec_cells(Rows::last() - 0, 5, 2), [Entity::Row(4)]);
+ assert_eq!(vec_cells(Rows::last() - 1, 5, 2), [Entity::Row(3)]);
+ assert_eq!(vec_cells(Rows::last() - 2, 5, 2), [Entity::Row(2)]);
+ assert_eq!(vec_cells(Rows::last() - 3, 5, 2), [Entity::Row(1)]);
+ assert_eq!(vec_cells(Rows::last() - 4, 5, 2), [Entity::Row(0)]);
+ assert_eq!(vec_cells(Rows::last() - 5, 5, 2), []);
+ assert_eq!(vec_cells(Rows::last() - 100, 5, 2), []);
+ assert_eq!(vec_cells(Rows::last() - 1, 0, 0), []);
+ assert_eq!(vec_cells(Rows::last() - 1, 5, 0), []);
+ assert_eq!(vec_cells(Rows::last() - 1, 0, 2), []);
+ }
+
+ #[test]
+ fn first_row_add_test() {
+ assert_eq!(vec_cells(Rows::first(), 5, 2), [Entity::Row(0)]);
+ assert_eq!(vec_cells(Rows::first() + 0, 5, 2), [Entity::Row(0)]);
+ assert_eq!(vec_cells(Rows::first() + 1, 5, 2), [Entity::Row(1)]);
+ assert_eq!(vec_cells(Rows::first() + 2, 5, 2), [Entity::Row(2)]);
+ assert_eq!(vec_cells(Rows::first() + 3, 5, 2), [Entity::Row(3)]);
+ assert_eq!(vec_cells(Rows::first() + 4, 5, 2), [Entity::Row(4)]);
+ assert_eq!(vec_cells(Rows::first() + 5, 5, 2), [Entity::Row(5)]);
+ assert_eq!(vec_cells(Rows::first() + 100, 5, 2), [Entity::Row(100)]);
+ assert_eq!(vec_cells(Rows::first() + 1, 0, 0), [Entity::Row(1)]);
+ assert_eq!(vec_cells(Rows::first() + 1, 5, 0), [Entity::Row(1)]);
+ assert_eq!(vec_cells(Rows::first() + 1, 0, 2), [Entity::Row(1)]);
+ }
+
+ #[test]
+ fn frame_test() {
+ assert_eq!(
+ vec_cells(Frame, 2, 3),
+ [
+ Entity::Cell(0, 0),
+ Entity::Cell(0, 1),
+ Entity::Cell(0, 2),
+ Entity::Cell(1, 0),
+ Entity::Cell(1, 1),
+ Entity::Cell(1, 2)
+ ]
+ );
+ assert_eq!(vec_cells(Frame, 0, 0), []);
+ assert_eq!(vec_cells(Frame, 2, 0), []);
+ assert_eq!(vec_cells(Frame, 0, 2), []);
+ }
+
+ #[test]
+ fn segment_test() {
+ assert_eq!(
+ vec_cells(Segment::new(.., ..), 2, 3),
+ [
+ Entity::Cell(0, 0),
+ Entity::Cell(0, 1),
+ Entity::Cell(0, 2),
+ Entity::Cell(1, 0),
+ Entity::Cell(1, 1),
+ Entity::Cell(1, 2)
+ ]
+ );
+ assert_eq!(
+ vec_cells(Segment::new(1.., ..), 2, 3),
+ [Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2)]
+ );
+ assert_eq!(vec_cells(Segment::new(2.., ..), 2, 3), []);
+
+ assert_eq!(
+ vec_cells(Segment::new(.., 1..), 2, 3),
+ [
+ Entity::Cell(0, 1),
+ Entity::Cell(0, 2),
+ Entity::Cell(1, 1),
+ Entity::Cell(1, 2)
+ ]
+ );
+ assert_eq!(
+ vec_cells(Segment::new(.., 2..), 2, 3),
+ [Entity::Cell(0, 2), Entity::Cell(1, 2)]
+ );
+ assert_eq!(vec_cells(Segment::new(.., 3..), 2, 3), []);
+
+ assert_eq!(
+ vec_cells(Segment::new(1.., 1..), 2, 3),
+ [Entity::Cell(1, 1), Entity::Cell(1, 2)]
+ );
+ assert_eq!(
+ vec_cells(Segment::new(1..2, 1..2), 2, 3),
+ [Entity::Cell(1, 1)]
+ );
+
+ assert_eq!(vec_cells(Segment::new(5.., 5..), 2, 3), []);
+ }
+
+ #[test]
+ fn object_and_test() {
+ assert_eq!(
+ vec_cells(Cell::new(0, 0).and(Cell::new(0, 0)), 2, 3),
+ [Entity::Cell(0, 0)]
+ );
+ assert_eq!(
+ vec_cells(Cell::new(0, 0).and(Cell::new(1, 2)), 2, 3),
+ [Entity::Cell(0, 0), Entity::Cell(1, 2)]
+ );
+ assert_eq!(vec_cells(Cell::new(0, 0).and(Cell::new(1, 2)), 0, 0), []);
+ }
+
+ #[test]
+ fn object_not_test() {
+ assert_eq!(vec_cells(Rows::first().not(Cell::new(0, 0)), 0, 0), []);
+ assert_eq!(vec_cells(Cell::new(0, 0).not(Cell::new(0, 0)), 2, 3), []);
+ assert_eq!(
+ vec_cells(Rows::first().not(Cell::new(0, 0)), 2, 3),
+ [Entity::Cell(0, 1), Entity::Cell(0, 2)]
+ );
+ assert_eq!(
+ vec_cells(Columns::single(1).not(Rows::single(1)), 3, 3),
+ [Entity::Cell(0, 1), Entity::Cell(2, 1)]
+ );
+ assert_eq!(
+ vec_cells(Rows::single(1).not(Columns::single(1)), 3, 3),
+ [Entity::Cell(1, 0), Entity::Cell(1, 2)]
+ );
+ }
+
+ #[test]
+ fn object_intersect_test() {
+ assert_eq!(
+ vec_cells(Rows::first().intersect(Cell::new(0, 0)), 0, 0),
+ []
+ );
+ assert_eq!(
+ vec_cells(Segment::all().intersect(Rows::single(1)), 2, 3),
+ [Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2)]
+ );
+ assert_eq!(
+ vec_cells(Cell::new(0, 0).intersect(Cell::new(0, 0)), 2, 3),
+ [Entity::Cell(0, 0)]
+ );
+ assert_eq!(
+ vec_cells(Rows::first().intersect(Cell::new(0, 0)), 2, 3),
+ [Entity::Cell(0, 0)]
+ );
+ // maybe we somehow shall not limit the rows/columns by the max count?
+ assert_eq!(
+ vec_cells(Rows::single(1).intersect(Columns::single(1)), 2, 1),
+ []
+ );
+ }
+
+ #[test]
+ fn object_inverse_test() {
+ assert_eq!(vec_cells(Segment::all().inverse(), 2, 3), []);
+ assert_eq!(
+ vec_cells(Cell::new(0, 0).inverse(), 2, 3),
+ [
+ Entity::Cell(0, 1),
+ Entity::Cell(0, 2),
+ Entity::Cell(1, 0),
+ Entity::Cell(1, 1),
+ Entity::Cell(1, 2)
+ ]
+ );
+ assert_eq!(
+ vec_cells(Rows::first().inverse(), 2, 3),
+ [Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2)]
+ );
+ assert_eq!(vec_cells(Rows::first().inverse(), 0, 0), []);
+ }
+
+ fn vec_cells<O: Object<VecRecords<String>>>(
+ o: O,
+ count_rows: usize,
+ count_cols: usize,
+ ) -> Vec<Entity> {
+ let data = vec![vec![String::default(); count_cols]; count_rows];
+ let records = VecRecords::new(data);
+ o.cells(&records).collect::<Vec<_>>()
+ }
+}
diff --git a/vendor/tabled/src/settings/object/rows.rs b/vendor/tabled/src/settings/object/rows.rs
new file mode 100644
index 000000000..b32e79ad5
--- /dev/null
+++ b/vendor/tabled/src/settings/object/rows.rs
@@ -0,0 +1,213 @@
+use std::ops::{Add, RangeBounds, Sub};
+
+use crate::{
+ grid::config::Entity,
+ grid::records::{ExactRecords, Records},
+ settings::object::{cell::EntityOnce, Object},
+};
+
+use super::util::bounds_to_usize;
+
+/// Row denotes a set of cells on given rows on a [`Table`].
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct Rows<R> {
+ range: R,
+}
+
+impl<R> Rows<R> {
+ /// Returns a new instance of [`Rows`] for a range of rows.
+ ///
+ /// If the boundaries are exceeded it may panic.
+ pub fn new(range: R) -> Self
+ where
+ R: RangeBounds<usize>,
+ {
+ Self { range }
+ }
+
+ pub(crate) const fn get_range(&self) -> &R {
+ &self.range
+ }
+}
+
+impl Rows<()> {
+ /// Returns a new instance of [`Rows`] with a single row.
+ ///
+ /// If the boundaries are exceeded it may panic.
+ pub const fn single(index: usize) -> Row {
+ Row { index }
+ }
+
+ /// Returns a first row [`Object`].
+ ///
+ /// If the table has 0 rows returns an empty set of cells.
+ pub const fn first() -> FirstRow {
+ FirstRow
+ }
+
+ /// Returns a last row [`Object`].
+ ///
+ /// If the table has 0 rows returns an empty set of cells.
+ pub const fn last() -> LastRow {
+ LastRow
+ }
+}
+
+impl<I, R> Object<I> for Rows<R>
+where
+ R: RangeBounds<usize>,
+ I: ExactRecords,
+{
+ type Iter = RowsIter;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ let start = self.range.start_bound();
+ let end = self.range.end_bound();
+ let max = records.count_rows();
+ let (x, y) = bounds_to_usize(start, end, max);
+
+ RowsIter::new(x, y)
+ }
+}
+
+/// A row which is located by an offset from the first row.
+#[derive(Debug, Clone, Copy)]
+pub struct Row {
+ index: usize,
+}
+
+impl<I> Object<I> for Row {
+ type Iter = EntityOnce;
+
+ fn cells(&self, _: &I) -> Self::Iter {
+ EntityOnce::new(Some(Entity::Row(self.index)))
+ }
+}
+
+impl From<Row> for usize {
+ fn from(val: Row) -> Self {
+ val.index
+ }
+}
+
+/// This structure represents the first row of a [`Table`].
+/// It's often contains headers data.
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct FirstRow;
+
+impl<I> Object<I> for FirstRow
+where
+ I: Records + ExactRecords,
+{
+ type Iter = EntityOnce;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ if records.count_columns() == 0 || records.count_rows() == 0 {
+ return EntityOnce::new(None);
+ }
+
+ EntityOnce::new(Some(Entity::Row(0)))
+ }
+}
+
+impl Add<usize> for FirstRow {
+ type Output = Row;
+
+ fn add(self, rhs: usize) -> Self::Output {
+ Row { index: rhs }
+ }
+}
+
+/// This structure represents the last row of a [`Table`].
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct LastRow;
+
+impl<I> Object<I> for LastRow
+where
+ I: Records + ExactRecords,
+{
+ type Iter = EntityOnce;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ let count_rows = records.count_rows();
+ if records.count_columns() == 0 || count_rows == 0 {
+ return EntityOnce::new(None);
+ }
+
+ let row = if count_rows == 0 { 0 } else { count_rows - 1 };
+
+ EntityOnce::new(Some(Entity::Row(row)))
+ }
+}
+
+impl Sub<usize> for LastRow {
+ type Output = LastRowOffset;
+
+ fn sub(self, rhs: usize) -> Self::Output {
+ LastRowOffset { offset: rhs }
+ }
+}
+
+/// A row which is located by an offset from the last row.
+#[derive(Debug)]
+pub struct LastRowOffset {
+ offset: usize,
+}
+
+impl<I> Object<I> for LastRowOffset
+where
+ I: Records + ExactRecords,
+{
+ type Iter = EntityOnce;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ let count_rows = records.count_rows();
+ if records.count_columns() == 0 || count_rows == 0 {
+ return EntityOnce::new(None);
+ }
+
+ let row = if count_rows == 0 { 0 } else { count_rows - 1 };
+ if self.offset > row {
+ return EntityOnce::new(None);
+ }
+
+ let row = row - self.offset;
+ EntityOnce::new(Some(Entity::Row(row)))
+ }
+}
+
+/// An [`Iterator`] which goes goes over all rows of a [`Table`].
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct RowsIter {
+ start: usize,
+ end: usize,
+}
+
+impl RowsIter {
+ const fn new(start: usize, end: usize) -> Self {
+ Self { start, end }
+ }
+}
+
+impl Iterator for RowsIter {
+ type Item = Entity;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.start >= self.end {
+ return None;
+ }
+
+ let col = self.start;
+ self.start += 1;
+
+ Some(Entity::Row(col))
+ }
+}
diff --git a/vendor/tabled/src/settings/object/segment.rs b/vendor/tabled/src/settings/object/segment.rs
new file mode 100644
index 000000000..9b59ada47
--- /dev/null
+++ b/vendor/tabled/src/settings/object/segment.rs
@@ -0,0 +1,151 @@
+use std::ops::{RangeBounds, RangeFull};
+
+use crate::{
+ grid::config::Entity,
+ grid::records::{ExactRecords, Records},
+ settings::object::{cell::EntityOnce, Object},
+};
+
+use super::util::bounds_to_usize;
+
+/// This structure represents a sub table of [`Table`].
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct Segment<C, R> {
+ columns: C,
+ rows: R,
+}
+
+impl Segment<RangeFull, RangeFull> {
+ /// Returns a table segment on which are present all cells.
+ pub fn all() -> SegmentAll {
+ SegmentAll
+ }
+}
+
+impl<C, R> Segment<C, R>
+where
+ C: RangeBounds<usize>,
+ R: RangeBounds<usize>,
+{
+ /// This function builds a [`Segment`].
+ pub fn new(rows: R, columns: C) -> Self {
+ Self { columns, rows }
+ }
+}
+
+impl<I, C, R> Object<I> for Segment<C, R>
+where
+ C: RangeBounds<usize>,
+ R: RangeBounds<usize>,
+ I: Records + ExactRecords,
+{
+ type Iter = SectorIter;
+
+ fn cells(&self, records: &I) -> Self::Iter {
+ let start = self.rows.start_bound();
+ let end = self.rows.end_bound();
+ let max = records.count_rows();
+ let (rows_start, rows_end) = bounds_to_usize(start, end, max);
+
+ let start = self.columns.start_bound();
+ let end = self.columns.end_bound();
+ let max = records.count_columns();
+ let (cols_start, cols_end) = bounds_to_usize(start, end, max);
+
+ SectorIter::new(rows_start, rows_end, cols_start, cols_end)
+ }
+}
+
+/// This is a segment which contains all cells on the table.
+///
+/// Can be created from [`Segment::all`].
+#[derive(Debug)]
+pub struct SegmentAll;
+
+impl<I> Object<I> for SegmentAll {
+ type Iter = EntityOnce;
+
+ fn cells(&self, _: &I) -> Self::Iter {
+ EntityOnce::new(Some(Entity::Global))
+ }
+}
+
+/// An [`Iterator`] which goes goes over all cell in a sector in a [`Table`].
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct SectorIter {
+ iter: SectorCellsIter,
+}
+
+impl SectorIter {
+ const fn new(rows_start: usize, rows_end: usize, cols_start: usize, cols_end: usize) -> Self {
+ Self {
+ iter: SectorCellsIter::new(rows_start, rows_end, cols_start, cols_end),
+ }
+ }
+}
+
+impl Iterator for SectorIter {
+ type Item = Entity;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let (row, col) = self.iter.next()?;
+ Some(Entity::Cell(row, col))
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct SectorCellsIter {
+ rows_end: usize,
+ cols_start: usize,
+ cols_end: usize,
+ row: usize,
+ col: usize,
+}
+
+impl SectorCellsIter {
+ /// Create an iterator from 1st row to last from 1st col to last.
+ pub(crate) const fn new(
+ rows_start: usize,
+ rows_end: usize,
+ cols_start: usize,
+ cols_end: usize,
+ ) -> Self {
+ Self {
+ rows_end,
+ cols_start,
+ cols_end,
+ row: rows_start,
+ col: cols_start,
+ }
+ }
+}
+
+impl Iterator for SectorCellsIter {
+ type Item = (usize, usize);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.row >= self.rows_end {
+ return None;
+ }
+
+ if self.col >= self.cols_end {
+ return None;
+ }
+
+ let row = self.row;
+ let col = self.col;
+
+ self.col += 1;
+
+ if self.col == self.cols_end {
+ self.row += 1;
+ self.col = self.cols_start;
+ }
+
+ Some((row, col))
+ }
+}
diff --git a/vendor/tabled/src/settings/object/util.rs b/vendor/tabled/src/settings/object/util.rs
new file mode 100644
index 000000000..94ca20e86
--- /dev/null
+++ b/vendor/tabled/src/settings/object/util.rs
@@ -0,0 +1,22 @@
+use std::ops::Bound;
+
+/// Converts a range bound to its indexes.
+pub(super) fn bounds_to_usize(
+ left: Bound<&usize>,
+ right: Bound<&usize>,
+ count_elements: usize,
+) -> (usize, usize) {
+ match (left, right) {
+ (Bound::Included(x), Bound::Included(y)) => (*x, y + 1),
+ (Bound::Included(x), Bound::Excluded(y)) => (*x, *y),
+ (Bound::Included(x), Bound::Unbounded) => (*x, count_elements),
+ (Bound::Unbounded, Bound::Unbounded) => (0, count_elements),
+ (Bound::Unbounded, Bound::Included(y)) => (0, y + 1),
+ (Bound::Unbounded, Bound::Excluded(y)) => (0, *y),
+ (Bound::Excluded(_), Bound::Unbounded)
+ | (Bound::Excluded(_), Bound::Included(_))
+ | (Bound::Excluded(_), Bound::Excluded(_)) => {
+ unreachable!("A start bound can't be excluded")
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/padding/mod.rs b/vendor/tabled/src/settings/padding/mod.rs
new file mode 100644
index 000000000..af343b3cb
--- /dev/null
+++ b/vendor/tabled/src/settings/padding/mod.rs
@@ -0,0 +1,164 @@
+//! This module contains a [`Padding`] setting of a cell on a [`Table`].
+//!
+//! # Example
+//!
+#![cfg_attr(feature = "std", doc = "```")]
+#![cfg_attr(not(feature = "std"), doc = "```ignore")]
+//! use tabled::{Table, settings::{Padding, Style, Modify, object::Cell}};
+//!
+//! let table = Table::new("2022".chars())
+//! .with(Style::modern())
+//! .with(Modify::new((2, 0)).with(Padding::new(1, 1, 2, 2)))
+//! .to_string();
+//!
+//! assert_eq!(
+//! table,
+//! concat!(
+//! "┌──────┐\n",
+//! "│ char │\n",
+//! "├──────┤\n",
+//! "│ 2 │\n",
+//! "├──────┤\n",
+//! "│ │\n",
+//! "│ │\n",
+//! "│ 0 │\n",
+//! "│ │\n",
+//! "│ │\n",
+//! "├──────┤\n",
+//! "│ 2 │\n",
+//! "├──────┤\n",
+//! "│ 2 │\n",
+//! "└──────┘",
+//! ),
+//! );
+//! ```
+//!
+//! [`Table`]: crate::Table
+
+use crate::{
+ grid::{
+ color::StaticColor,
+ config::{CompactConfig, CompactMultilineConfig},
+ config::{Indent, Sides},
+ },
+ settings::TableOption,
+};
+
+#[cfg(feature = "std")]
+use crate::grid::{color::AnsiColor, config::ColoredConfig, config::Entity};
+#[cfg(feature = "std")]
+use crate::settings::CellOption;
+
+/// Padding is responsible for a left/right/top/bottom inner indent of a particular cell.
+///
+#[cfg_attr(feature = "std", doc = "```")]
+#[cfg_attr(not(feature = "std"), doc = "```ignore")]
+/// # use tabled::{settings::{Style, Padding, object::Rows, Modify}, Table};
+/// # let data: Vec<&'static str> = Vec::new();
+/// let table = Table::new(&data).with(Modify::new(Rows::single(0)).with(Padding::new(0, 0, 1, 1).fill('>', '<', '^', 'V')));
+/// ```
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Padding<C = StaticColor> {
+ indent: Sides<Indent>,
+ colors: Option<Sides<C>>,
+}
+
+impl Padding {
+ /// Construct's an Padding object.
+ ///
+ /// It uses space(' ') as a default fill character.
+ /// To set a custom character you can use [`Padding::fill`] function.
+ pub const fn new(left: usize, right: usize, top: usize, bottom: usize) -> Self {
+ Self {
+ indent: Sides::new(
+ Indent::spaced(left),
+ Indent::spaced(right),
+ Indent::spaced(top),
+ Indent::spaced(bottom),
+ ),
+ colors: None,
+ }
+ }
+
+ /// Construct's an Padding object with all sides set to 0.
+ ///
+ /// It uses space(' ') as a default fill character.
+ /// To set a custom character you can use [`Padding::fill`] function.
+ pub const fn zero() -> Self {
+ Self::new(0, 0, 0, 0)
+ }
+}
+
+impl<Color> Padding<Color> {
+ /// The function, sets a characters for the padding on an each side.
+ pub const fn fill(mut self, left: char, right: char, top: char, bottom: char) -> Self {
+ self.indent.left.fill = left;
+ self.indent.right.fill = right;
+ self.indent.top.fill = top;
+ self.indent.bottom.fill = bottom;
+ self
+ }
+
+ /// The function, sets a characters for the padding on an each side.
+ pub fn colorize<C>(self, left: C, right: C, top: C, bottom: C) -> Padding<C> {
+ Padding {
+ indent: self.indent,
+ colors: Some(Sides::new(left, right, top, bottom)),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl<R, C> CellOption<R, ColoredConfig> for Padding<C>
+where
+ C: Into<AnsiColor<'static>> + Clone,
+{
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let indent = self.indent;
+ let pad = Sides::new(indent.left, indent.right, indent.top, indent.bottom);
+ cfg.set_padding(entity, pad);
+
+ if let Some(colors) = &self.colors {
+ let pad = Sides::new(
+ Some(colors.left.clone().into()),
+ Some(colors.right.clone().into()),
+ Some(colors.top.clone().into()),
+ Some(colors.bottom.clone().into()),
+ );
+ cfg.set_padding_color(entity, pad);
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl<R, D, C> TableOption<R, D, ColoredConfig> for Padding<C>
+where
+ C: Into<AnsiColor<'static>> + Clone,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ <Self as CellOption<R, ColoredConfig>>::change(self, records, cfg, Entity::Global)
+ }
+}
+
+impl<R, D, C> TableOption<R, D, CompactConfig> for Padding<C>
+where
+ C: Into<StaticColor> + Clone,
+{
+ fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) {
+ *cfg = cfg.set_padding(self.indent);
+
+ if let Some(c) = self.colors {
+ let colors = Sides::new(c.left.into(), c.right.into(), c.top.into(), c.bottom.into());
+ *cfg = cfg.set_padding_color(colors);
+ }
+ }
+}
+
+impl<R, D, C> TableOption<R, D, CompactMultilineConfig> for Padding<C>
+where
+ C: Into<StaticColor> + Clone,
+{
+ fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) {
+ self.change(records, cfg.as_mut(), dimension)
+ }
+}
diff --git a/vendor/tabled/src/settings/panel/footer.rs b/vendor/tabled/src/settings/panel/footer.rs
new file mode 100644
index 000000000..8d16481cf
--- /dev/null
+++ b/vendor/tabled/src/settings/panel/footer.rs
@@ -0,0 +1,32 @@
+use crate::{
+ grid::config::ColoredConfig,
+ grid::records::{ExactRecords, Records, RecordsMut, Resizable},
+ settings::TableOption,
+};
+
+use super::Panel;
+
+/// Footer renders a [`Panel`] at the bottom.
+/// See [`Panel`].
+#[derive(Debug)]
+pub struct Footer<S>(S);
+
+impl<S> Footer<S> {
+ /// Creates a new object.
+ pub fn new(text: S) -> Self
+ where
+ S: AsRef<str>,
+ {
+ Self(text)
+ }
+}
+
+impl<S, R, D> TableOption<R, D, ColoredConfig> for Footer<S>
+where
+ S: AsRef<str>,
+ R: Records + ExactRecords + Resizable + RecordsMut<String>,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) {
+ Panel::horizontal(records.count_rows(), self.0.as_ref()).change(records, cfg, dimension);
+ }
+}
diff --git a/vendor/tabled/src/settings/panel/header.rs b/vendor/tabled/src/settings/panel/header.rs
new file mode 100644
index 000000000..e9d398cb5
--- /dev/null
+++ b/vendor/tabled/src/settings/panel/header.rs
@@ -0,0 +1,32 @@
+use crate::{
+ grid::config::ColoredConfig,
+ grid::records::{ExactRecords, Records, RecordsMut, Resizable},
+ settings::TableOption,
+};
+
+use super::Panel;
+
+/// Header inserts a [`Panel`] at the top.
+/// See [`Panel`].
+#[derive(Debug)]
+pub struct Header<S>(S);
+
+impl<S> Header<S> {
+ /// Creates a new object.
+ pub fn new(text: S) -> Self
+ where
+ S: AsRef<str>,
+ {
+ Self(text)
+ }
+}
+
+impl<S, R, D> TableOption<R, D, ColoredConfig> for Header<S>
+where
+ S: AsRef<str>,
+ R: Records + ExactRecords + Resizable + RecordsMut<String>,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) {
+ Panel::horizontal(0, self.0.as_ref()).change(records, cfg, dimension);
+ }
+}
diff --git a/vendor/tabled/src/settings/panel/horizontal_panel.rs b/vendor/tabled/src/settings/panel/horizontal_panel.rs
new file mode 100644
index 000000000..d5871d720
--- /dev/null
+++ b/vendor/tabled/src/settings/panel/horizontal_panel.rs
@@ -0,0 +1,80 @@
+use crate::{
+ grid::config::{ColoredConfig, SpannedConfig},
+ grid::records::{ExactRecords, Records, RecordsMut, Resizable},
+ settings::TableOption,
+};
+
+/// A horizontal/column span from 0 to a count rows.
+#[derive(Debug)]
+pub struct HorizontalPanel<S> {
+ text: S,
+ row: usize,
+}
+
+impl<S> HorizontalPanel<S> {
+ /// Creates a new horizontal panel.
+ pub fn new(row: usize, text: S) -> Self {
+ Self { row, text }
+ }
+}
+
+impl<S, R, D> TableOption<R, D, ColoredConfig> for HorizontalPanel<S>
+where
+ S: AsRef<str>,
+ R: Records + ExactRecords + Resizable + RecordsMut<String>,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ if self.row > count_rows {
+ return;
+ }
+
+ let is_intersect_vertical_span = (0..records.count_columns())
+ .any(|col| cfg.is_cell_covered_by_row_span((self.row, col)));
+ if is_intersect_vertical_span {
+ return;
+ }
+
+ move_rows_aside(records, self.row);
+ move_row_spans(cfg, self.row);
+
+ let text = self.text.as_ref().to_owned();
+ records.set((self.row, 0), text);
+
+ cfg.set_column_span((self.row, 0), count_cols);
+ }
+}
+
+fn move_rows_aside<R: ExactRecords + Resizable>(records: &mut R, row: usize) {
+ records.push_row();
+
+ let count_rows = records.count_rows();
+
+ let shift_count = count_rows - row;
+ for i in 1..shift_count {
+ let row = count_rows - i;
+ records.swap_row(row, row - 1);
+ }
+}
+
+fn move_row_spans(cfg: &mut SpannedConfig, target_row: usize) {
+ for ((row, col), span) in cfg.get_column_spans() {
+ if row < target_row {
+ continue;
+ }
+
+ cfg.set_column_span((row, col), 1);
+ cfg.set_column_span((row + 1, col), span);
+ }
+
+ for ((row, col), span) in cfg.get_row_spans() {
+ if row < target_row {
+ continue;
+ }
+
+ cfg.set_row_span((row, col), 1);
+ cfg.set_row_span((row + 1, col), span);
+ }
+}
diff --git a/vendor/tabled/src/settings/panel/mod.rs b/vendor/tabled/src/settings/panel/mod.rs
new file mode 100644
index 000000000..e4e819b6c
--- /dev/null
+++ b/vendor/tabled/src/settings/panel/mod.rs
@@ -0,0 +1,127 @@
+//! This module contains primitives to create a spread row.
+//! Ultimately it is a cell with a span set to a number of columns on the [`Table`].
+//!
+//! You can use a [`Span`] to set a custom span.
+//!
+//! # Example
+//!
+//! ```
+//! use tabled::{Table, settings::Panel};
+//!
+//! let data = [[1, 2, 3], [4, 5, 6]];
+//!
+//! let table = Table::new(data)
+//! .with(Panel::vertical(1, "S\np\nl\ni\nt"))
+//! .with(Panel::header("Numbers"))
+//! .to_string();
+//!
+//! println!("{}", table);
+//!
+//! assert_eq!(
+//! table,
+//! concat!(
+//! "+---+---+---+---+\n",
+//! "| Numbers |\n",
+//! "+---+---+---+---+\n",
+//! "| 0 | S | 1 | 2 |\n",
+//! "+---+ p +---+---+\n",
+//! "| 1 | l | 2 | 3 |\n",
+//! "+---+ i +---+---+\n",
+//! "| 4 | t | 5 | 6 |\n",
+//! "+---+---+---+---+",
+//! )
+//! )
+//! ```
+//!
+//! [`Table`]: crate::Table
+//! [`Span`]: crate::settings::span::Span
+
+mod footer;
+mod header;
+mod horizontal_panel;
+mod vertical_panel;
+
+pub use footer::Footer;
+pub use header::Header;
+pub use horizontal_panel::HorizontalPanel;
+pub use vertical_panel::VerticalPanel;
+
+/// Panel allows to add a Row which has 1 continues Cell to a [`Table`].
+///
+/// See `examples/panel.rs`.
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct Panel;
+
+impl Panel {
+ /// Creates an empty vertical row at given index.
+ ///
+ /// ```
+ /// use tabled::{settings::Panel, Table};
+ ///
+ /// let data = [[1, 2, 3], [4, 5, 6]];
+ ///
+ /// let table = Table::new(data)
+ /// .with(Panel::vertical(1, "Tabled Releases"))
+ /// .to_string();
+ ///
+ /// println!("{}", table);
+ ///
+ /// assert_eq!(
+ /// table,
+ /// concat!(
+ /// "+---+-----------------+---+---+\n",
+ /// "| 0 | Tabled Releases | 1 | 2 |\n",
+ /// "+---+ +---+---+\n",
+ /// "| 1 | | 2 | 3 |\n",
+ /// "+---+ +---+---+\n",
+ /// "| 4 | | 5 | 6 |\n",
+ /// "+---+-----------------+---+---+",
+ /// )
+ /// )
+ /// ```
+ pub fn vertical<S: AsRef<str>>(column: usize, text: S) -> VerticalPanel<S> {
+ VerticalPanel::new(column, text)
+ }
+
+ /// Creates an empty horizontal row at given index.
+ ///
+ /// ```
+ /// use tabled::{Table, settings::Panel};
+ ///
+ /// let data = [[1, 2, 3], [4, 5, 6]];
+ ///
+ /// let table = Table::new(data)
+ /// .with(Panel::vertical(1, ""))
+ /// .to_string();
+ ///
+ /// println!("{}", table);
+ ///
+ /// assert_eq!(
+ /// table,
+ /// concat!(
+ /// "+---+--+---+---+\n",
+ /// "| 0 | | 1 | 2 |\n",
+ /// "+---+ +---+---+\n",
+ /// "| 1 | | 2 | 3 |\n",
+ /// "+---+ +---+---+\n",
+ /// "| 4 | | 5 | 6 |\n",
+ /// "+---+--+---+---+",
+ /// )
+ /// )
+ /// ```
+ pub fn horizontal<S: AsRef<str>>(row: usize, text: S) -> HorizontalPanel<S> {
+ HorizontalPanel::new(row, text)
+ }
+
+ /// Creates an horizontal row at first row.
+ pub fn header<S: AsRef<str>>(text: S) -> Header<S> {
+ Header::new(text)
+ }
+
+ /// Creates an horizontal row at last row.
+ pub fn footer<S: AsRef<str>>(text: S) -> Footer<S> {
+ Footer::new(text)
+ }
+}
diff --git a/vendor/tabled/src/settings/panel/vertical_panel.rs b/vendor/tabled/src/settings/panel/vertical_panel.rs
new file mode 100644
index 000000000..ddc0a562b
--- /dev/null
+++ b/vendor/tabled/src/settings/panel/vertical_panel.rs
@@ -0,0 +1,83 @@
+use crate::{
+ grid::config::{ColoredConfig, SpannedConfig},
+ grid::records::{ExactRecords, Records, RecordsMut, Resizable},
+ settings::TableOption,
+};
+
+/// A vertical/row span from 0 to a count columns.
+#[derive(Debug)]
+pub struct VerticalPanel<S> {
+ text: S,
+ col: usize,
+}
+
+impl<S> VerticalPanel<S> {
+ /// Creates a new vertical panel.
+ pub fn new(col: usize, text: S) -> Self
+ where
+ S: AsRef<str>,
+ {
+ Self { text, col }
+ }
+}
+
+impl<S, R, D> TableOption<R, D, ColoredConfig> for VerticalPanel<S>
+where
+ S: AsRef<str>,
+ R: Records + ExactRecords + Resizable + RecordsMut<String>,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ if self.col > count_cols {
+ return;
+ }
+
+ let is_intersect_horizontal_span = (0..=records.count_rows())
+ .any(|row| cfg.is_cell_covered_by_column_span((row, self.col)));
+
+ if is_intersect_horizontal_span {
+ return;
+ }
+
+ move_columns_aside(records, self.col);
+ move_column_spans(cfg, self.col);
+
+ let text = self.text.as_ref().to_owned();
+ records.set((0, self.col), text);
+
+ cfg.set_row_span((0, self.col), count_rows);
+ }
+}
+
+fn move_columns_aside<R: Records + Resizable>(records: &mut R, column: usize) {
+ records.push_column();
+
+ let count_columns = records.count_columns();
+ let shift_count = count_columns - column;
+ for i in 1..shift_count {
+ let col = count_columns - i;
+ records.swap_column(col, col - 1);
+ }
+}
+
+fn move_column_spans(cfg: &mut SpannedConfig, target_column: usize) {
+ for ((row, col), span) in cfg.get_column_spans() {
+ if col < target_column {
+ continue;
+ }
+
+ cfg.set_column_span((row, col), 1);
+ cfg.set_column_span((row, col + 1), span);
+ }
+
+ for ((row, col), span) in cfg.get_row_spans() {
+ if col < target_column {
+ continue;
+ }
+
+ cfg.set_row_span((row, col), 1);
+ cfg.set_row_span((row, col + 1), span);
+ }
+}
diff --git a/vendor/tabled/src/settings/peaker/mod.rs b/vendor/tabled/src/settings/peaker/mod.rs
new file mode 100644
index 000000000..a49796b02
--- /dev/null
+++ b/vendor/tabled/src/settings/peaker/mod.rs
@@ -0,0 +1,94 @@
+//! The module contains [`Peaker`] trait and its implementations to be used in [`Height`] and [`Width`].
+//!
+//! [`Width`]: crate::settings::width::Width
+//! [`Height`]: crate::settings::height::Height
+
+/// A strategy of width function.
+/// It determines the order how the function is applied.
+pub trait Peaker {
+ /// Creates a new instance.
+ fn create() -> Self;
+ /// This function returns a column index which will be changed.
+ /// Or `None` if no changes are necessary.
+ fn peak(&mut self, min_widths: &[usize], widths: &[usize]) -> Option<usize>;
+}
+
+/// A Peaker which goes over column 1 by 1.
+#[derive(Debug, Default, Clone)]
+pub struct PriorityNone {
+ i: usize,
+}
+
+impl Peaker for PriorityNone {
+ fn create() -> Self {
+ Self { i: 0 }
+ }
+
+ fn peak(&mut self, _: &[usize], widths: &[usize]) -> Option<usize> {
+ let mut i = self.i;
+ let mut count_empty = 0;
+ while widths[i] == 0 {
+ i += 1;
+ if i >= widths.len() {
+ i = 0;
+ }
+
+ count_empty += 1;
+ if count_empty == widths.len() {
+ return None;
+ }
+ }
+
+ let col = i;
+
+ i += 1;
+ if i >= widths.len() {
+ i = 0;
+ }
+
+ self.i = i;
+
+ Some(col)
+ }
+}
+
+/// A Peaker which goes over the biggest column first.
+#[derive(Debug, Default, Clone)]
+pub struct PriorityMax;
+
+impl Peaker for PriorityMax {
+ fn create() -> Self {
+ Self
+ }
+
+ fn peak(&mut self, _: &[usize], widths: &[usize]) -> Option<usize> {
+ let col = (0..widths.len()).max_by_key(|&i| widths[i]).unwrap();
+ if widths[col] == 0 {
+ None
+ } else {
+ Some(col)
+ }
+ }
+}
+
+/// A Peaker which goes over the smallest column first.
+#[derive(Debug, Default, Clone)]
+pub struct PriorityMin;
+
+impl Peaker for PriorityMin {
+ fn create() -> Self {
+ Self
+ }
+
+ fn peak(&mut self, min_widths: &[usize], widths: &[usize]) -> Option<usize> {
+ let col = (0..widths.len())
+ .filter(|&i| min_widths.is_empty() || widths[i] > min_widths[i])
+ .min_by_key(|&i| widths[i])
+ .unwrap();
+ if widths[col] == 0 {
+ None
+ } else {
+ Some(col)
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/rotate/mod.rs b/vendor/tabled/src/settings/rotate/mod.rs
new file mode 100644
index 000000000..426417190
--- /dev/null
+++ b/vendor/tabled/src/settings/rotate/mod.rs
@@ -0,0 +1,155 @@
+//! This module contains a [`Rotate`] primitive which can be used in order to rotate [`Table`].
+//!
+//! It's also possible to transpose the table at the point of construction.
+//! See [`Builder::index`].
+//!
+//! # Example
+//!
+#![cfg_attr(feature = "std", doc = "```")]
+#![cfg_attr(not(feature = "std"), doc = "```ignore")]
+//! use tabled::{Table, settings::Rotate};
+//!
+//! let data = [[1, 2, 3], [4, 5, 6]];
+//!
+//! let table = Table::new(data).with(Rotate::Left).to_string();
+//!
+//! assert_eq!(
+//! table,
+//! concat!(
+//! "+---+---+---+\n",
+//! "| 2 | 3 | 6 |\n",
+//! "+---+---+---+\n",
+//! "| 1 | 2 | 5 |\n",
+//! "+---+---+---+\n",
+//! "| 0 | 1 | 4 |\n",
+//! "+---+---+---+",
+//! )
+//! );
+//! ```
+//!
+//! [`Table`]: crate::Table
+//! [`Builder::index`]: crate::builder::Builder::index
+
+// use core::cmp::max;
+use core::cmp::max;
+
+use crate::{
+ grid::records::{ExactRecords, Records, Resizable},
+ settings::TableOption,
+};
+
+/// Rotate can be used to rotate a table by 90 degrees.
+#[derive(Debug)]
+pub enum Rotate {
+ /// Rotate [`Table`] to the left.
+ ///
+ /// [`Table`]: crate::Table
+ Left,
+ /// Rotate [`Table`] to the right.
+ ///
+ /// [`Table`]: crate::Table
+ Right,
+ /// Rotate [`Table`] to the top.
+ ///
+ /// So the top becomes the bottom.
+ ///
+ /// [`Table`]: crate::Table
+ Top,
+ /// Rotate [`Table`] to the bottom.
+ ///
+ /// So the top becomes the bottom.
+ ///
+ /// [`Table`]: crate::Table
+ Bottom,
+}
+
+impl<R, D, C> TableOption<R, D, C> for Rotate
+where
+ R: Records + ExactRecords + Resizable,
+{
+ fn change(self, records: &mut R, _: &mut C, _: &mut D) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ match self {
+ Self::Left => {
+ let size = max(count_rows, count_cols);
+
+ {
+ for _ in count_rows..size {
+ records.push_row();
+ }
+
+ for _ in count_cols..size {
+ records.push_column();
+ }
+ }
+
+ for col in 0..size {
+ for row in col..size {
+ records.swap((col, row), (row, col));
+ }
+ }
+
+ for row in 0..count_cols / 2 {
+ records.swap_row(row, count_cols - row - 1);
+ }
+
+ {
+ for (shift, row) in (count_rows..size).enumerate() {
+ let row = row - shift;
+ records.remove_column(row);
+ }
+
+ for (shift, col) in (count_cols..size).enumerate() {
+ let col = col - shift;
+ records.remove_row(col);
+ }
+ }
+ }
+ Self::Right => {
+ let size = max(count_rows, count_cols);
+
+ {
+ for _ in count_rows..size {
+ records.push_row();
+ }
+
+ for _ in count_cols..size {
+ records.push_column();
+ }
+ }
+
+ for col in 0..size {
+ for row in col..size {
+ records.swap((col, row), (row, col));
+ }
+ }
+
+ for col in 0..count_rows / 2 {
+ records.swap_column(col, count_rows - col - 1);
+ }
+
+ {
+ for (shift, row) in (count_rows..size).enumerate() {
+ let row = row - shift;
+ records.remove_column(row);
+ }
+
+ for (shift, col) in (count_cols..size).enumerate() {
+ let col = col - shift;
+ records.remove_row(col);
+ }
+ }
+ }
+ Self::Bottom | Self::Top => {
+ for row in 0..count_rows / 2 {
+ for col in 0..count_cols {
+ let last_row = count_rows - row - 1;
+ records.swap((last_row, col), (row, col));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/settings_list.rs b/vendor/tabled/src/settings/settings_list.rs
new file mode 100644
index 000000000..dc8aec179
--- /dev/null
+++ b/vendor/tabled/src/settings/settings_list.rs
@@ -0,0 +1,71 @@
+use crate::settings::TableOption;
+
+#[cfg(feature = "std")]
+use crate::grid::config::Entity;
+#[cfg(feature = "std")]
+use crate::settings::CellOption;
+
+/// Settings is a combinator of [`TableOption`]s.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Settings<A = EmptySettings, B = EmptySettings>(A, B);
+
+impl Default for Settings<EmptySettings, EmptySettings> {
+ fn default() -> Self {
+ Self(EmptySettings, EmptySettings)
+ }
+}
+
+impl Settings<(), ()> {
+ /// Creates an empty list.
+ pub const fn empty() -> Settings<EmptySettings, EmptySettings> {
+ Settings(EmptySettings, EmptySettings)
+ }
+}
+
+impl<A, B> Settings<A, B> {
+ /// Creates a new combinator.
+ pub const fn new(settings1: A, settings2: B) -> Settings<A, B> {
+ Settings(settings1, settings2)
+ }
+
+ /// Add an option to a combinator.
+ pub const fn with<C>(self, settings: C) -> Settings<Self, C> {
+ Settings(self, settings)
+ }
+}
+
+#[cfg(feature = "std")]
+impl<R, C, A, B> CellOption<R, C> for Settings<A, B>
+where
+ A: CellOption<R, C>,
+ B: CellOption<R, C>,
+{
+ fn change(self, records: &mut R, cfg: &mut C, entity: Entity) {
+ self.0.change(records, cfg, entity);
+ self.1.change(records, cfg, entity);
+ }
+}
+
+impl<R, D, C, A, B> TableOption<R, D, C> for Settings<A, B>
+where
+ A: TableOption<R, D, C>,
+ B: TableOption<R, D, C>,
+{
+ fn change(self, records: &mut R, cfg: &mut C, dims: &mut D) {
+ self.0.change(records, cfg, dims);
+ self.1.change(records, cfg, dims);
+ }
+}
+
+/// A marker structure to be able to create an empty [`Settings`].
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct EmptySettings;
+
+#[cfg(feature = "std")]
+impl<R, C> CellOption<R, C> for EmptySettings {
+ fn change(self, _: &mut R, _: &mut C, _: Entity) {}
+}
+
+impl<R, D, C> TableOption<R, D, C> for EmptySettings {
+ fn change(self, _: &mut R, _: &mut C, _: &mut D) {}
+}
diff --git a/vendor/tabled/src/settings/shadow/mod.rs b/vendor/tabled/src/settings/shadow/mod.rs
new file mode 100644
index 000000000..6b8ff4861
--- /dev/null
+++ b/vendor/tabled/src/settings/shadow/mod.rs
@@ -0,0 +1,195 @@
+//! This module contains a [`Shadow`] option for a [`Table`].
+//!
+//! # Example
+//!
+//! ```
+//! use tabled::{Table, settings::{Shadow, Style}};
+//!
+//! let data = vec!["Hello", "World", "!"];
+//!
+//! let table = Table::new(data)
+//! .with(Style::markdown())
+//! .with(Shadow::new(1))
+//! .to_string();
+//!
+//! assert_eq!(
+//! table,
+//! concat!(
+//! "| &str | \n",
+//! "|-------|▒\n",
+//! "| Hello |▒\n",
+//! "| World |▒\n",
+//! "| ! |▒\n",
+//! " ▒▒▒▒▒▒▒▒▒",
+//! )
+//! );
+//! ```
+//!
+//! [`Table`]: crate::Table
+
+use crate::{
+ grid::color::AnsiColor,
+ grid::config::{ColoredConfig, Indent, Offset, Sides},
+ settings::{color::Color, TableOption},
+};
+
+/// The structure represents a shadow of a table.
+///
+/// NOTICE: It uses [`Margin`] therefore it often can't be combined.
+///
+/// [`Margin`]: crate::settings::Margin
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Shadow {
+ c: char,
+ size: usize,
+ size_offset: usize,
+ direction: Sides<bool>,
+ color: Option<Color>,
+}
+
+impl Shadow {
+ /// A default fill character to be used.
+ pub const DEFAULT_FILL: char = '▒';
+
+ /// Construct's an [`Shadow`] object with default fill [`Shadow::DEFAULT_FILL`].
+ ///
+ /// It uses space(' ') as a default fill character.
+ /// To set a custom character you can use [`Self::set_fill`] function.
+ pub fn new(size: usize) -> Self {
+ Self {
+ c: Self::DEFAULT_FILL,
+ size,
+ size_offset: 1,
+ direction: Sides::new(false, true, false, true),
+ color: None,
+ }
+ }
+
+ /// The function, sets a characters for the [`Shadow`] to be used.
+ pub fn set_fill(mut self, c: char) -> Self {
+ self.c = c;
+ self
+ }
+
+ /// Set an offset value (default is '1').
+ pub fn set_offset(mut self, size: usize) -> Self {
+ self.size_offset = size;
+ self
+ }
+
+ /// Switch shadow to top.
+ pub fn set_top(mut self) -> Self {
+ self.direction.top = true;
+ self.direction.bottom = false;
+ self
+ }
+
+ /// Switch shadow to bottom.
+ pub fn set_bottom(mut self) -> Self {
+ self.direction.bottom = true;
+ self.direction.top = false;
+ self
+ }
+
+ /// Switch shadow to left.
+ pub fn set_left(mut self) -> Self {
+ self.direction.left = true;
+ self.direction.right = false;
+ self
+ }
+
+ /// Switch shadow to right.
+ pub fn set_right(mut self) -> Self {
+ self.direction.right = true;
+ self.direction.left = false;
+ self
+ }
+
+ /// Sets a color for a shadow.
+ pub fn set_color(mut self, color: Color) -> Self {
+ self.color = Some(color);
+ self
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for Shadow {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ set_margin(cfg, self.size, self.c, &self.direction);
+ set_margin_offset(cfg, self.size_offset, &self.direction);
+
+ if let Some(color) = &self.color {
+ set_margin_color(cfg, color.clone().into(), &self.direction);
+ }
+ }
+}
+
+fn set_margin(cfg: &mut ColoredConfig, size: usize, c: char, direction: &Sides<bool>) {
+ let mut margin: Sides<Indent> = Sides::default();
+ if direction.top {
+ margin.top.size = size;
+ margin.top.fill = c;
+ }
+
+ if direction.bottom {
+ margin.bottom.size = size;
+ margin.bottom.fill = c;
+ }
+
+ if direction.left {
+ margin.left.size = size;
+ margin.left.fill = c;
+ }
+
+ if direction.right {
+ margin.right.size = size;
+ margin.right.fill = c;
+ }
+
+ cfg.set_margin(margin);
+}
+
+fn set_margin_offset(cfg: &mut ColoredConfig, size: usize, direction: &Sides<bool>) {
+ let mut margin = Sides::filled(Offset::Begin(0));
+ if direction.right && direction.bottom {
+ margin.bottom = Offset::Begin(size);
+ margin.right = Offset::Begin(size);
+ }
+
+ if direction.right && direction.top {
+ margin.top = Offset::Begin(size);
+ margin.right = Offset::End(size);
+ }
+
+ if direction.left && direction.bottom {
+ margin.bottom = Offset::End(size);
+ margin.left = Offset::Begin(size);
+ }
+
+ if direction.left && direction.top {
+ margin.top = Offset::End(size);
+ margin.left = Offset::End(size);
+ }
+
+ cfg.set_margin_offset(margin);
+}
+
+fn set_margin_color(cfg: &mut ColoredConfig, color: AnsiColor<'static>, direction: &Sides<bool>) {
+ let mut margin: Sides<Option<AnsiColor<'static>>> = Sides::default();
+ if direction.right {
+ margin.right = Some(color.clone());
+ }
+
+ if direction.top {
+ margin.top = Some(color.clone());
+ }
+
+ if direction.left {
+ margin.left = Some(color.clone());
+ }
+
+ if direction.bottom {
+ margin.bottom = Some(color.clone());
+ }
+
+ cfg.set_margin_color(margin);
+}
diff --git a/vendor/tabled/src/settings/span/column.rs b/vendor/tabled/src/settings/span/column.rs
new file mode 100644
index 000000000..50af64c23
--- /dev/null
+++ b/vendor/tabled/src/settings/span/column.rs
@@ -0,0 +1,125 @@
+use crate::{
+ grid::{
+ config::{ColoredConfig, Entity, Position, SpannedConfig},
+ records::{ExactRecords, Records},
+ },
+ settings::CellOption,
+};
+
+/// Columns (Vertical) span.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct ColumnSpan {
+ size: usize,
+}
+
+impl ColumnSpan {
+ /// Creates a new column (vertical) span.
+ pub fn new(size: usize) -> Self {
+ Self { size }
+ }
+
+ /// Creates a new column (vertical) span with a maximux value possible.
+ pub fn max() -> Self {
+ Self::new(usize::MAX)
+ }
+}
+
+impl<R> CellOption<R, ColoredConfig> for ColumnSpan
+where
+ R: Records + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ set_col_spans(cfg, self.size, entity, (count_rows, count_cols));
+ remove_false_spans(cfg);
+ }
+}
+
+fn set_col_spans(cfg: &mut SpannedConfig, span: usize, entity: Entity, shape: (usize, usize)) {
+ for pos in entity.iter(shape.0, shape.1) {
+ if !is_valid_pos(pos, shape) {
+ continue;
+ }
+
+ let mut span = span;
+ if !is_column_span_valid(pos.1, span, shape.1) {
+ span = shape.1 - pos.1;
+ }
+
+ if span_has_intersections(cfg, pos, span) {
+ continue;
+ }
+
+ set_span_column(cfg, pos, span);
+ }
+}
+
+fn set_span_column(cfg: &mut SpannedConfig, pos: (usize, usize), span: usize) {
+ if span == 0 {
+ let (row, col) = pos;
+ if col == 0 {
+ return;
+ }
+
+ if let Some(closecol) = closest_visible(cfg, (row, col - 1)) {
+ let span = col + 1 - closecol;
+ cfg.set_column_span((row, closecol), span);
+ }
+ }
+
+ cfg.set_column_span(pos, span);
+}
+
+fn closest_visible(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> {
+ loop {
+ if cfg.is_cell_visible(pos) {
+ return Some(pos.1);
+ }
+
+ if pos.1 == 0 {
+ return None;
+ }
+
+ pos.1 -= 1;
+ }
+}
+
+fn is_column_span_valid(col: usize, span: usize, count_cols: usize) -> bool {
+ span + col <= count_cols
+}
+
+fn is_valid_pos((row, col): Position, (count_rows, count_cols): (usize, usize)) -> bool {
+ row < count_rows && col < count_cols
+}
+
+fn span_has_intersections(cfg: &SpannedConfig, (row, col): Position, span: usize) -> bool {
+ for col in col..col + span {
+ if !cfg.is_cell_visible((row, col)) {
+ return true;
+ }
+ }
+
+ false
+}
+
+fn remove_false_spans(cfg: &mut SpannedConfig) {
+ for (pos, _) in cfg.get_column_spans() {
+ if cfg.is_cell_visible(pos) {
+ continue;
+ }
+
+ cfg.set_row_span(pos, 1);
+ cfg.set_column_span(pos, 1);
+ }
+
+ for (pos, _) in cfg.get_row_spans() {
+ if cfg.is_cell_visible(pos) {
+ continue;
+ }
+
+ cfg.set_row_span(pos, 1);
+ cfg.set_column_span(pos, 1);
+ }
+}
diff --git a/vendor/tabled/src/settings/span/mod.rs b/vendor/tabled/src/settings/span/mod.rs
new file mode 100644
index 000000000..1139a2b99
--- /dev/null
+++ b/vendor/tabled/src/settings/span/mod.rs
@@ -0,0 +1,68 @@
+//! This module contains a [`Span`] settings, it helps to
+//! make a cell take more space then it generally takes.
+//!
+//! # Example
+//!
+//! ```
+//! use tabled::{settings::{Span, Modify}, Table};
+//!
+//! let data = [[1, 2, 3], [4, 5, 6]];
+//!
+//! let table = Table::new(data)
+//! .with(Modify::new((2, 0)).with(Span::column(2)))
+//! .with(Modify::new((0, 1)).with(Span::column(2)))
+//! .to_string();
+//!
+//! assert_eq!(
+//! table,
+//! concat!(
+//! "+---+---+---+\n",
+//! "| 0 | 1 |\n",
+//! "+---+---+---+\n",
+//! "| 1 | 2 | 3 |\n",
+//! "+---+---+---+\n",
+//! "| 4 | 6 |\n",
+//! "+---+---+---+",
+//! )
+//! )
+//! ```
+
+mod column;
+mod row;
+
+pub use column::ColumnSpan;
+pub use row::RowSpan;
+
+/// Span represent a horizontal/column span setting for any cell on a [`Table`].
+///
+/// It will be ignored if:
+/// - cell position is out of scope
+/// - size is bigger then the total number of columns.
+/// - size is bigger then the total number of rows.
+///
+/// ```rust,no_run
+/// # use tabled::{Table, settings::{Style, Span, Modify, object::Columns}};
+/// # let data: Vec<&'static str> = Vec::new();
+/// let table = Table::new(&data)
+/// .with(Modify::new(Columns::single(0)).with(Span::column(2)));
+/// ```
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct Span;
+
+impl Span {
+ /// New constructs a horizontal/column [`Span`].
+ ///
+ /// If size is bigger then the total number of columns it will be ignored.
+ pub fn column(size: usize) -> ColumnSpan {
+ ColumnSpan::new(size)
+ }
+
+ /// New constructs a vertical/row [`Span`].
+ ///
+ /// If size is bigger then the total number of rows it will be ignored.
+ pub fn row(size: usize) -> RowSpan {
+ RowSpan::new(size)
+ }
+}
diff --git a/vendor/tabled/src/settings/span/row.rs b/vendor/tabled/src/settings/span/row.rs
new file mode 100644
index 000000000..68673f7a2
--- /dev/null
+++ b/vendor/tabled/src/settings/span/row.rs
@@ -0,0 +1,126 @@
+use crate::{
+ grid::{
+ config::{ColoredConfig, Entity, Position, SpannedConfig},
+ records::{ExactRecords, Records},
+ },
+ settings::CellOption,
+};
+
+/// Row (vertical) span.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct RowSpan {
+ size: usize,
+}
+
+impl RowSpan {
+ /// Creates a new row (vertical) span.
+ pub const fn new(size: usize) -> Self {
+ Self { size }
+ }
+
+ /// Creates a new row (vertical) span with a maximux value possible.
+ pub const fn max() -> Self {
+ Self::new(usize::MAX)
+ }
+}
+
+impl<R> CellOption<R, ColoredConfig> for RowSpan
+where
+ R: Records + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let count_rows = records.count_rows();
+ let count_cols = records.count_columns();
+
+ set_row_spans(cfg, self.size, entity, (count_rows, count_cols));
+ remove_false_spans(cfg);
+ }
+}
+
+fn set_row_spans(cfg: &mut SpannedConfig, span: usize, entity: Entity, shape: (usize, usize)) {
+ for pos in entity.iter(shape.0, shape.1) {
+ if !is_valid_pos(pos, shape) {
+ continue;
+ }
+
+ let mut span = span;
+ if !is_row_span_valid(pos.0, span, shape.0) {
+ span = shape.0 - pos.0;
+ }
+
+ if span_has_intersections(cfg, pos, span) {
+ continue;
+ }
+
+ set_span_row(cfg, pos, span);
+ }
+}
+
+fn set_span_row(cfg: &mut SpannedConfig, pos: (usize, usize), span: usize) {
+ if span == 0 {
+ let (row, col) = pos;
+ if row == 0 {
+ return;
+ }
+
+ if let Some(closerow) = closest_visible_row(cfg, (row - 1, col)) {
+ let span = row + 1 - closerow;
+ cfg.set_row_span((closerow, col), span);
+ }
+ }
+
+ cfg.set_row_span(pos, span);
+}
+
+fn closest_visible_row(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> {
+ loop {
+ if cfg.is_cell_visible(pos) {
+ return Some(pos.0);
+ }
+
+ if pos.0 == 0 {
+ // can happen if we have a above horizontal spanned cell
+ return None;
+ }
+
+ pos.0 -= 1;
+ }
+}
+
+fn is_row_span_valid(row: usize, span: usize, count_rows: usize) -> bool {
+ span + row <= count_rows
+}
+
+fn is_valid_pos((row, col): Position, (count_rows, count_cols): (usize, usize)) -> bool {
+ row < count_rows && col < count_cols
+}
+
+fn span_has_intersections(cfg: &SpannedConfig, (row, col): Position, span: usize) -> bool {
+ for row in row..row + span {
+ if !cfg.is_cell_visible((row, col)) {
+ return true;
+ }
+ }
+
+ false
+}
+
+fn remove_false_spans(cfg: &mut SpannedConfig) {
+ for (pos, _) in cfg.get_column_spans() {
+ if cfg.is_cell_visible(pos) {
+ continue;
+ }
+
+ cfg.set_row_span(pos, 1);
+ cfg.set_column_span(pos, 1);
+ }
+
+ for (pos, _) in cfg.get_row_spans() {
+ if cfg.is_cell_visible(pos) {
+ continue;
+ }
+
+ cfg.set_row_span(pos, 1);
+ cfg.set_column_span(pos, 1);
+ }
+}
diff --git a/vendor/tabled/src/settings/split/mod.rs b/vendor/tabled/src/settings/split/mod.rs
new file mode 100644
index 000000000..32f820aef
--- /dev/null
+++ b/vendor/tabled/src/settings/split/mod.rs
@@ -0,0 +1,420 @@
+//! This module contains a [`Split`] setting which is used to
+//! format the cells of a [`Table`] by a provided index, direction, behavior, and display preference.
+//!
+//! [`Table`]: crate::Table
+
+use core::ops::Range;
+
+use papergrid::{config::Position, records::PeekableRecords};
+
+use crate::grid::records::{ExactRecords, Records, Resizable};
+
+use super::TableOption;
+
+#[derive(Debug, Clone, Copy)]
+enum Direction {
+ Column,
+ Row,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Behavior {
+ Concat,
+ Zip,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+enum Display {
+ Clean,
+ Retain,
+}
+
+/// Returns a new [`Table`] formatted with several optional parameters.
+///
+/// The required index parameter determines how many columns/rows a table will be redistributed into.
+///
+/// - index
+/// - direction
+/// - behavior
+/// - display
+///
+/// # Example
+///
+/// ```rust,no_run
+/// use std::iter::FromIterator;
+/// use tabled::{
+/// settings::split::Split,
+/// Table,
+/// };
+///
+/// let mut table = Table::from_iter(['a'..='z']);
+/// let table = table.with(Split::column(4)).to_string();
+///
+/// assert_eq!(table, "+---+---+---+---+\n\
+/// | a | b | c | d |\n\
+/// +---+---+---+---+\n\
+/// | e | f | g | h |\n\
+/// +---+---+---+---+\n\
+/// | i | j | k | l |\n\
+/// +---+---+---+---+\n\
+/// | m | n | o | p |\n\
+/// +---+---+---+---+\n\
+/// | q | r | s | t |\n\
+/// +---+---+---+---+\n\
+/// | u | v | w | x |\n\
+/// +---+---+---+---+\n\
+/// | y | z | | |\n\
+/// +---+---+---+---+")
+/// ```
+///
+/// [`Table`]: crate::Table
+#[derive(Debug, Clone, Copy)]
+pub struct Split {
+ direction: Direction,
+ behavior: Behavior,
+ display: Display,
+ index: usize,
+}
+
+impl Split {
+ /// Returns a new [`Table`] split on the column at the provided index.
+ ///
+ /// The column found at that index becomes the new right-most column in the returned table.
+ /// Columns found beyond the index are redistributed into the table based on other defined
+ /// parameters.
+ ///
+ /// ```rust,no_run
+ /// # use tabled::settings::split::Split;
+ /// Split::column(4);
+ /// ```
+ ///
+ /// [`Table`]: crate::Table
+ pub fn column(index: usize) -> Self {
+ Split {
+ direction: Direction::Column,
+ behavior: Behavior::Zip,
+ display: Display::Clean,
+ index,
+ }
+ }
+
+ /// Returns a new [`Table`] split on the row at the provided index.
+ ///
+ /// The row found at that index becomes the new bottom row in the returned table.
+ /// Rows found beyond the index are redistributed into the table based on other defined
+ /// parameters.
+ ///
+ /// ```rust,no_run
+ /// # use tabled::settings::split::Split;
+ /// Split::row(4);
+ /// ```
+ ///
+ /// [`Table`]: crate::Table
+ pub fn row(index: usize) -> Self {
+ Split {
+ direction: Direction::Row,
+ behavior: Behavior::Zip,
+ display: Display::Clean,
+ index,
+ }
+ }
+
+ /// Returns a split [`Table`] with the redistributed cells pushed to the back of the new shape.
+ ///
+ /// ```text
+ /// +---+---+
+ /// | a | b |
+ /// +---+---+
+ /// +---+---+---+---+---+ | f | g |
+ /// | a | b | c | d | e | Split::column(2).concat() +---+---+
+ /// +---+---+---+---+---+ => | c | d |
+ /// | f | g | h | i | j | +---+---+
+ /// +---+---+---+---+---+ | h | i |
+ /// +---+---+
+ /// | e | |
+ /// +---+---+
+ /// | j | |
+ /// +---+---+
+ /// ```
+ ///
+ /// [`Table`]: crate::Table
+ pub fn concat(self) -> Self {
+ Self {
+ behavior: Behavior::Concat,
+ ..self
+ }
+ }
+
+ /// Returns a split [`Table`] with the redistributed cells inserted behind
+ /// the first correlating column/row one after another.
+ ///
+ /// ```text
+ /// +---+---+
+ /// | a | b |
+ /// +---+---+
+ /// +---+---+---+---+---+ | c | d |
+ /// | a | b | c | d | e | Split::column(2).zip() +---+---+
+ /// +---+---+---+---+---+ => | e | |
+ /// | f | g | h | i | j | +---+---+
+ /// +---+---+---+---+---+ | f | g |
+ /// +---+---+
+ /// | h | i |
+ /// +---+---+
+ /// | j | |
+ /// +---+---+
+ /// ```
+ ///
+ /// [`Table`]: crate::Table
+ pub fn zip(self) -> Self {
+ Self {
+ behavior: Behavior::Zip,
+ ..self
+ }
+ }
+
+ /// Returns a split [`Table`] with the empty columns/rows filtered out.
+ ///
+ /// ```text
+ ///
+ ///
+ /// +---+---+---+
+ /// +---+---+---+---+---+ | a | b | c |
+ /// | a | b | c | d | e | Split::column(3).clean() +---+---+---+
+ /// +---+---+---+---+---+ => | d | e | |
+ /// | f | g | h | | | +---+---+---+
+ /// +---+---+---+---+---+ | f | g | h |
+ /// ^ ^ +---+---+---+
+ /// these cells are filtered
+ /// from the resulting table
+ /// ```
+ ///
+ /// ## Notes
+ ///
+ /// This is apart of the default configuration for Split.
+ ///
+ /// See [`retain`] for an alternative display option.
+ ///
+ /// [`Table`]: crate::Table
+ /// [`retain`]: crate::settings::split::Split::retain
+ pub fn clean(self) -> Self {
+ Self {
+ display: Display::Clean,
+ ..self
+ }
+ }
+
+ /// Returns a split [`Table`] with all cells retained.
+ ///
+ /// ```text
+ /// +---+---+---+
+ /// | a | b | c |
+ /// +---+---+---+
+ /// +---+---+---+---+---+ | d | e | |
+ /// | a | b | c | d | e | Split::column(3).retain() +---+---+---+
+ /// +---+---+---+---+---+ => | f | g | h |
+ /// | f | g | h | | | +---+---+---+
+ /// +---+---+---+---+---+ |-----------> | | | |
+ /// ^ ^ | +---+---+---+
+ /// |___|_____cells are kept!
+ /// ```
+ ///
+ /// ## Notes
+ ///
+ /// See [`clean`] for an alternative display option.
+ ///
+ /// [`Table`]: crate::Table
+ /// [`clean`]: crate::settings::split::Split::clean
+ pub fn retain(self) -> Self {
+ Self {
+ display: Display::Retain,
+ ..self
+ }
+ }
+}
+
+impl<R, D, Cfg> TableOption<R, D, Cfg> for Split
+where
+ R: Records + ExactRecords + Resizable + PeekableRecords,
+{
+ fn change(self, records: &mut R, _: &mut Cfg, _: &mut D) {
+ // variables
+ let Split {
+ direction,
+ behavior,
+ display,
+ index: section_length,
+ } = self;
+ let mut filtered_sections = 0;
+
+ // early return check
+ if records.count_columns() == 0 || records.count_rows() == 0 || section_length == 0 {
+ return;
+ }
+
+ // computed variables
+ let (primary_length, secondary_length) = compute_length_arrangement(records, direction);
+ let sections_per_direction = ceil_div(primary_length, section_length);
+ let (outer_range, inner_range) =
+ compute_range_order(secondary_length, sections_per_direction, behavior);
+
+ // work
+ for outer_index in outer_range {
+ let from_secondary_index = outer_index * sections_per_direction - filtered_sections;
+ for inner_index in inner_range.clone() {
+ let (section_index, from_secondary_index, to_secondary_index) =
+ compute_range_variables(
+ outer_index,
+ inner_index,
+ secondary_length,
+ from_secondary_index,
+ sections_per_direction,
+ filtered_sections,
+ behavior,
+ );
+
+ match (direction, behavior) {
+ (Direction::Column, Behavior::Concat) => records.push_row(),
+ (Direction::Column, Behavior::Zip) => records.insert_row(to_secondary_index),
+ (Direction::Row, Behavior::Concat) => records.push_column(),
+ (Direction::Row, Behavior::Zip) => records.insert_column(to_secondary_index),
+ }
+
+ let section_is_empty = copy_section(
+ records,
+ section_length,
+ section_index,
+ primary_length,
+ from_secondary_index,
+ to_secondary_index,
+ direction,
+ );
+
+ if section_is_empty && display == Display::Clean {
+ delete(records, to_secondary_index, direction);
+ filtered_sections += 1;
+ }
+ }
+ }
+
+ cleanup(records, section_length, primary_length, direction);
+ }
+}
+
+/// Determine which direction should be considered the primary, and which the secondary based on direction
+fn compute_length_arrangement<R>(records: &mut R, direction: Direction) -> (usize, usize)
+where
+ R: Records + ExactRecords,
+{
+ match direction {
+ Direction::Column => (records.count_columns(), records.count_rows()),
+ Direction::Row => (records.count_rows(), records.count_columns()),
+ }
+}
+
+/// reduce the table size to the length of the index in the specified direction
+fn cleanup<R>(records: &mut R, section_length: usize, primary_length: usize, direction: Direction)
+where
+ R: Resizable,
+{
+ for segment in (section_length..primary_length).rev() {
+ match direction {
+ Direction::Column => records.remove_column(segment),
+ Direction::Row => records.remove_row(segment),
+ }
+ }
+}
+
+/// Delete target index row or column
+fn delete<R>(records: &mut R, target_index: usize, direction: Direction)
+where
+ R: Resizable,
+{
+ match direction {
+ Direction::Column => records.remove_row(target_index),
+ Direction::Row => records.remove_column(target_index),
+ }
+}
+
+/// copy cells to new location
+///
+/// returns if the copied section was entirely blank
+fn copy_section<R>(
+ records: &mut R,
+ section_length: usize,
+ section_index: usize,
+ primary_length: usize,
+ from_secondary_index: usize,
+ to_secondary_index: usize,
+ direction: Direction,
+) -> bool
+where
+ R: ExactRecords + Resizable + PeekableRecords,
+{
+ let mut section_is_empty = true;
+ for to_primary_index in 0..section_length {
+ let from_primary_index = to_primary_index + section_index * section_length;
+
+ if from_primary_index < primary_length {
+ let from_position =
+ format_position(direction, from_primary_index, from_secondary_index);
+ if records.get_text(from_position) != "" {
+ section_is_empty = false;
+ }
+ records.swap(
+ from_position,
+ format_position(direction, to_primary_index, to_secondary_index),
+ );
+ }
+ }
+ section_is_empty
+}
+
+/// determine section over direction or vice versa based on behavior
+fn compute_range_order(
+ direction_length: usize,
+ sections_per_direction: usize,
+ behavior: Behavior,
+) -> (Range<usize>, Range<usize>) {
+ match behavior {
+ Behavior::Concat => (1..sections_per_direction, 0..direction_length),
+ Behavior::Zip => (0..direction_length, 1..sections_per_direction),
+ }
+}
+
+/// helper function for shimming both behaviors to work within a single nested loop
+fn compute_range_variables(
+ outer_index: usize,
+ inner_index: usize,
+ direction_length: usize,
+ from_secondary_index: usize,
+ sections_per_direction: usize,
+ filtered_sections: usize,
+ behavior: Behavior,
+) -> (usize, usize, usize) {
+ match behavior {
+ Behavior::Concat => (
+ outer_index,
+ inner_index,
+ inner_index + outer_index * direction_length - filtered_sections,
+ ),
+ Behavior::Zip => (
+ inner_index,
+ from_secondary_index,
+ outer_index * sections_per_direction + inner_index - filtered_sections,
+ ),
+ }
+}
+
+/// utility for arguments of a position easily
+fn format_position(direction: Direction, primary_index: usize, secondary_index: usize) -> Position {
+ match direction {
+ Direction::Column => (secondary_index, primary_index),
+ Direction::Row => (primary_index, secondary_index),
+ }
+}
+
+/// ceil division utility because the std lib ceil_div isn't stable yet
+fn ceil_div(x: usize, y: usize) -> usize {
+ debug_assert!(x != 0);
+ 1 + ((x - 1) / y)
+}
diff --git a/vendor/tabled/src/settings/style/border.rs b/vendor/tabled/src/settings/style/border.rs
new file mode 100644
index 000000000..a8cbefb5d
--- /dev/null
+++ b/vendor/tabled/src/settings/style/border.rs
@@ -0,0 +1,159 @@
+use crate::{
+ grid::{
+ config::{Border as GBorder, ColoredConfig, Entity},
+ records::{ExactRecords, Records},
+ },
+ settings::CellOption,
+};
+
+/// Border represents a border of a Cell.
+///
+/// ```text
+/// top border
+/// |
+/// V
+/// corner top left ------> +_______+ <---- corner top left
+/// | |
+/// left border ----------> | cell | <---- right border
+/// | |
+/// corner bottom right --> +_______+ <---- corner bottom right
+/// ^
+/// |
+/// bottom border
+/// ```
+///
+/// ```rust,no_run
+/// # use tabled::{Table, settings::{Modify, style::{Style, Border}, object::Rows}};
+/// # let data: Vec<&'static str> = Vec::new();
+/// let table = Table::new(&data)
+/// .with(Style::ascii())
+/// .with(Modify::new(Rows::single(0)).with(Border::default().top('x')));
+/// ```
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Border(GBorder<char>);
+
+impl Border {
+ /// This function constructs a cell borders with all sides set.
+ #[allow(clippy::too_many_arguments)]
+ pub const fn full(
+ top: char,
+ bottom: char,
+ left: char,
+ right: char,
+ top_left: char,
+ top_right: char,
+ bottom_left: char,
+ bottom_right: char,
+ ) -> Self {
+ Self(GBorder::full(
+ top,
+ bottom,
+ left,
+ right,
+ top_left,
+ top_right,
+ bottom_left,
+ bottom_right,
+ ))
+ }
+
+ /// This function constructs a cell borders with all sides's char set to a given character.
+ /// It behaves like [`Border::full`] with the same character set to each side.
+ pub const fn filled(c: char) -> Self {
+ Self::full(c, c, c, c, c, c, c, c)
+ }
+
+ /// Using this function you deconstruct the existing borders.
+ pub const fn empty() -> EmptyBorder {
+ EmptyBorder
+ }
+
+ /// Set a top border character.
+ pub const fn top(mut self, c: char) -> Self {
+ self.0.top = Some(c);
+ self
+ }
+
+ /// Set a bottom border character.
+ pub const fn bottom(mut self, c: char) -> Self {
+ self.0.bottom = Some(c);
+ self
+ }
+
+ /// Set a left border character.
+ pub const fn left(mut self, c: char) -> Self {
+ self.0.left = Some(c);
+ self
+ }
+
+ /// Set a right border character.
+ pub const fn right(mut self, c: char) -> Self {
+ self.0.right = Some(c);
+ self
+ }
+
+ /// Set a top left intersection character.
+ pub const fn corner_top_left(mut self, c: char) -> Self {
+ self.0.left_top_corner = Some(c);
+ self
+ }
+
+ /// Set a top right intersection character.
+ pub const fn corner_top_right(mut self, c: char) -> Self {
+ self.0.right_top_corner = Some(c);
+ self
+ }
+
+ /// Set a bottom left intersection character.
+ pub const fn corner_bottom_left(mut self, c: char) -> Self {
+ self.0.left_bottom_corner = Some(c);
+ self
+ }
+
+ /// Set a bottom right intersection character.
+ pub const fn corner_bottom_right(mut self, c: char) -> Self {
+ self.0.right_bottom_corner = Some(c);
+ self
+ }
+}
+
+impl<R> CellOption<R, ColoredConfig> for Border
+where
+ R: Records + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let shape = (records.count_rows(), records.count_columns());
+
+ for pos in entity.iter(shape.0, shape.1) {
+ cfg.set_border(pos, self.0);
+ }
+ }
+}
+
+impl From<GBorder<char>> for Border {
+ fn from(b: GBorder<char>) -> Border {
+ Border(b)
+ }
+}
+
+impl From<Border> for GBorder<char> {
+ fn from(value: Border) -> Self {
+ value.0
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct EmptyBorder;
+
+impl<R> CellOption<R, ColoredConfig> for EmptyBorder
+where
+ R: Records + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let shape = (records.count_rows(), records.count_columns());
+
+ for pos in entity.iter(shape.0, shape.1) {
+ cfg.remove_border(pos, shape);
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/style/border_char.rs b/vendor/tabled/src/settings/style/border_char.rs
new file mode 100644
index 000000000..b1e04c579
--- /dev/null
+++ b/vendor/tabled/src/settings/style/border_char.rs
@@ -0,0 +1,99 @@
+use crate::{
+ grid::config::{ColoredConfig, Entity, Position, SpannedConfig},
+ grid::records::{ExactRecords, Records},
+ settings::CellOption,
+};
+
+use super::Offset;
+
+/// [`BorderChar`] sets a char to a specific location on a horizontal line.
+///
+/// # Example
+///
+/// ```rust
+/// use tabled::{Table, settings::{style::{Style, BorderChar, Offset}, Modify, object::{Object, Rows, Columns}}};
+///
+/// let mut table = Table::new(["Hello World"]);
+/// table
+/// .with(Style::markdown())
+/// .with(Modify::new(Rows::single(1))
+/// .with(BorderChar::horizontal(':', Offset::Begin(0)))
+/// .with(BorderChar::horizontal(':', Offset::End(0)))
+/// )
+/// .with(Modify::new((1, 0).and((1, 1))).with(BorderChar::vertical('#', Offset::Begin(0))));
+///
+/// assert_eq!(
+/// table.to_string(),
+/// concat!(
+/// "| &str |\n",
+/// "|:-----------:|\n",
+/// "# Hello World #",
+/// ),
+/// );
+/// ```
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct BorderChar {
+ c: char,
+ offset: Offset,
+ horizontal: bool,
+}
+
+impl BorderChar {
+ /// Creates a [`BorderChar`] which overrides horizontal line.
+ pub fn horizontal(c: char, offset: Offset) -> Self {
+ Self {
+ c,
+ offset,
+ horizontal: true,
+ }
+ }
+
+ /// Creates a [`BorderChar`] which overrides vertical line.
+ pub fn vertical(c: char, offset: Offset) -> Self {
+ Self {
+ c,
+ offset,
+ horizontal: false,
+ }
+ }
+}
+
+impl<R> CellOption<R, ColoredConfig> for BorderChar
+where
+ R: Records + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let cells = entity.iter(records.count_rows(), records.count_columns());
+
+ match self.horizontal {
+ true => add_char_horizontal(cfg, self.c, self.offset, cells),
+ false => add_char_vertical(cfg, self.c, self.offset, cells),
+ }
+ }
+}
+
+fn add_char_vertical<I: Iterator<Item = Position>>(
+ cfg: &mut SpannedConfig,
+ c: char,
+ offset: Offset,
+ cells: I,
+) {
+ let offset = offset.into();
+
+ for pos in cells {
+ cfg.set_vertical_char(pos, c, offset);
+ }
+}
+
+fn add_char_horizontal<I: Iterator<Item = Position>>(
+ cfg: &mut SpannedConfig,
+ c: char,
+ offset: Offset,
+ cells: I,
+) {
+ let offset = offset.into();
+
+ for pos in cells {
+ cfg.set_horizontal_char(pos, c, offset);
+ }
+}
diff --git a/vendor/tabled/src/settings/style/border_color.rs b/vendor/tabled/src/settings/style/border_color.rs
new file mode 100644
index 000000000..6b3fa2b1a
--- /dev/null
+++ b/vendor/tabled/src/settings/style/border_color.rs
@@ -0,0 +1,155 @@
+//! This module contains a configuration of a Border to set its color via [`BorderColor`].
+
+use crate::{
+ grid::{
+ color::AnsiColor,
+ config::{Border, ColoredConfig, Entity},
+ records::{ExactRecords, Records},
+ },
+ settings::{color::Color, CellOption, TableOption},
+};
+
+/// BorderColored represents a colored border of a Cell.
+///
+/// ```rust,no_run
+/// # use tabled::{settings::{style::BorderColor, Style, Color, object::Rows, Modify}, Table};
+/// #
+/// # let data: Vec<&'static str> = Vec::new();
+/// #
+/// let table = Table::new(&data)
+/// .with(Style::ascii())
+/// .with(Modify::new(Rows::single(0)).with(BorderColor::default().top(Color::FG_RED)));
+/// ```
+#[derive(Debug, Clone, Default, Eq, PartialEq)]
+pub struct BorderColor(Border<AnsiColor<'static>>);
+
+impl BorderColor {
+ /// This function constructs a cell borders with all sides set.
+ #[allow(clippy::too_many_arguments)]
+ pub fn full(
+ top: Color,
+ bottom: Color,
+ left: Color,
+ right: Color,
+ top_left: Color,
+ top_right: Color,
+ bottom_left: Color,
+ bottom_right: Color,
+ ) -> Self {
+ Self(Border::full(
+ top.into(),
+ bottom.into(),
+ left.into(),
+ right.into(),
+ top_left.into(),
+ top_right.into(),
+ bottom_left.into(),
+ bottom_right.into(),
+ ))
+ }
+
+ /// This function constructs a cell borders with all sides's char set to a given character.
+ /// It behaves like [`Border::full`] with the same character set to each side.
+ pub fn filled(c: Color) -> Self {
+ let c: AnsiColor<'_> = c.into();
+
+ Self(Border {
+ top: Some(c.clone()),
+ bottom: Some(c.clone()),
+ left: Some(c.clone()),
+ right: Some(c.clone()),
+ left_bottom_corner: Some(c.clone()),
+ left_top_corner: Some(c.clone()),
+ right_bottom_corner: Some(c.clone()),
+ right_top_corner: Some(c),
+ })
+ }
+
+ /// Set a top border character.
+ pub fn top(mut self, c: Color) -> Self {
+ self.0.top = Some(c.into());
+ self
+ }
+
+ /// Set a bottom border character.
+ pub fn bottom(mut self, c: Color) -> Self {
+ self.0.bottom = Some(c.into());
+ self
+ }
+
+ /// Set a left border character.
+ pub fn left(mut self, c: Color) -> Self {
+ self.0.left = Some(c.into());
+ self
+ }
+
+ /// Set a right border character.
+ pub fn right(mut self, c: Color) -> Self {
+ self.0.right = Some(c.into());
+ self
+ }
+
+ /// Set a top left intersection character.
+ pub fn corner_top_left(mut self, c: Color) -> Self {
+ self.0.left_top_corner = Some(c.into());
+ self
+ }
+
+ /// Set a top right intersection character.
+ pub fn corner_top_right(mut self, c: Color) -> Self {
+ self.0.right_top_corner = Some(c.into());
+ self
+ }
+
+ /// Set a bottom left intersection character.
+ pub fn corner_bottom_left(mut self, c: Color) -> Self {
+ self.0.left_bottom_corner = Some(c.into());
+ self
+ }
+
+ /// Set a bottom right intersection character.
+ pub fn corner_bottom_right(mut self, c: Color) -> Self {
+ self.0.right_bottom_corner = Some(c.into());
+ self
+ }
+}
+
+impl<R> CellOption<R, ColoredConfig> for BorderColor
+where
+ R: Records + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ let border_color = &self.0;
+
+ for pos in entity.iter(count_rows, count_columns) {
+ cfg.set_border_color(pos, border_color.clone());
+ }
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for BorderColor
+where
+ R: Records + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ let border_color = &self.0;
+
+ for row in 0..count_rows {
+ for col in 0..count_columns {
+ cfg.set_border_color((row, col), border_color.clone());
+ }
+ }
+ }
+}
+
+impl From<BorderColor> for Border<AnsiColor<'static>> {
+ fn from(val: BorderColor) -> Self {
+ val.0
+ }
+}
diff --git a/vendor/tabled/src/settings/style/border_text.rs b/vendor/tabled/src/settings/style/border_text.rs
new file mode 100644
index 000000000..163a19821
--- /dev/null
+++ b/vendor/tabled/src/settings/style/border_text.rs
@@ -0,0 +1,253 @@
+use crate::{
+ grid::{
+ color::AnsiColor,
+ config::{self, ColoredConfig, SpannedConfig},
+ dimension::{Dimension, Estimate},
+ records::{ExactRecords, Records},
+ },
+ settings::{
+ object::{FirstRow, LastRow},
+ Color, TableOption,
+ },
+};
+
+use super::Offset;
+
+/// [`BorderText`] writes a custom text on a border.
+///
+/// # Example
+///
+/// ```rust
+/// use tabled::{Table, settings::style::BorderText, settings::object::Rows};
+///
+/// let mut table = Table::new(["Hello World"]);
+/// table
+/// .with(BorderText::new("+-.table").horizontal(Rows::first()));
+///
+/// assert_eq!(
+/// table.to_string(),
+/// "+-.table------+\n\
+/// | &str |\n\
+/// +-------------+\n\
+/// | Hello World |\n\
+/// +-------------+"
+/// );
+/// ```
+#[derive(Debug)]
+pub struct BorderText<L> {
+ text: String,
+ offset: Offset,
+ color: Option<AnsiColor<'static>>,
+ line: L,
+}
+
+impl BorderText<()> {
+ /// Creates a [`BorderText`] instance.
+ ///
+ /// Lines are numbered from 0 to the `count_rows` included
+ /// (`line >= 0 && line <= count_rows`).
+ pub fn new<S: Into<String>>(text: S) -> Self {
+ BorderText {
+ text: text.into(),
+ line: (),
+ offset: Offset::Begin(0),
+ color: None,
+ }
+ }
+}
+
+impl<Line> BorderText<Line> {
+ /// Set a line on which we will set the text.
+ pub fn horizontal<L>(self, line: L) -> BorderText<L> {
+ BorderText {
+ line,
+ text: self.text,
+ offset: self.offset,
+ color: self.color,
+ }
+ }
+
+ /// Set an offset from which the text will be started.
+ pub fn offset(self, offset: Offset) -> Self {
+ BorderText {
+ offset,
+ text: self.text,
+ line: self.line,
+ color: self.color,
+ }
+ }
+
+ /// Set a color of the text.
+ pub fn color(self, color: Color) -> Self {
+ BorderText {
+ color: Some(color.into()),
+ text: self.text,
+ line: self.line,
+ offset: self.offset,
+ }
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for BorderText<usize>
+where
+ R: Records + ExactRecords,
+ for<'a> &'a R: Records,
+ for<'a> D: Estimate<&'a R, ColoredConfig>,
+ D: Dimension,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
+ dims.estimate(records, cfg);
+ let shape = (records.count_rows(), records.count_columns());
+ let line = self.line;
+ set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape);
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for BorderText<FirstRow>
+where
+ R: Records + ExactRecords,
+ for<'a> &'a R: Records,
+ for<'a> D: Estimate<&'a R, ColoredConfig>,
+ D: Dimension,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
+ dims.estimate(records, cfg);
+ let shape = (records.count_rows(), records.count_columns());
+ let line = 0;
+ set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape);
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for BorderText<LastRow>
+where
+ R: Records + ExactRecords,
+ for<'a> &'a R: Records,
+ for<'a> D: Estimate<&'a R, ColoredConfig>,
+ D: Dimension,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
+ dims.estimate(records, cfg);
+ let shape = (records.count_rows(), records.count_columns());
+ let line = records.count_rows();
+ set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape);
+ }
+}
+
+fn set_horizontal_chars<D: Dimension>(
+ cfg: &mut SpannedConfig,
+ dims: &D,
+ offset: Offset,
+ line: usize,
+ text: &str,
+ color: &Option<AnsiColor<'static>>,
+ shape: (usize, usize),
+) {
+ let (_, count_columns) = shape;
+ let pos = get_start_pos(cfg, dims, offset, count_columns);
+ let pos = match pos {
+ Some(pos) => pos,
+ None => return,
+ };
+
+ let mut chars = text.chars();
+ let mut i = cfg.has_vertical(0, count_columns) as usize;
+ if i == 1 && pos == 0 {
+ let c = match chars.next() {
+ Some(c) => c,
+ None => return,
+ };
+
+ let mut b = cfg.get_border((line, 0), shape);
+ b.left_top_corner = b.left_top_corner.map(|_| c);
+ cfg.set_border((line, 0), b);
+
+ if let Some(color) = color.as_ref() {
+ let mut b = cfg.get_border_color((line, 0), shape).cloned();
+ b.left_top_corner = Some(color.clone());
+ cfg.set_border_color((line, 0), b);
+ }
+ }
+
+ for col in 0..count_columns {
+ let w = dims.get_width(col);
+ if i + w > pos {
+ for off in 0..w {
+ if i + off < pos {
+ continue;
+ }
+
+ let c = match chars.next() {
+ Some(c) => c,
+ None => return,
+ };
+
+ cfg.set_horizontal_char((line, col), c, config::Offset::Begin(off));
+ if let Some(color) = color.as_ref() {
+ cfg.set_horizontal_color(
+ (line, col),
+ color.clone(),
+ config::Offset::Begin(off),
+ );
+ }
+ }
+ }
+
+ i += w;
+
+ if cfg.has_vertical(col + 1, count_columns) {
+ i += 1;
+
+ if i > pos {
+ let c = match chars.next() {
+ Some(c) => c,
+ None => return,
+ };
+
+ let mut b = cfg.get_border((line, col), shape);
+ b.right_top_corner = b.right_top_corner.map(|_| c);
+ cfg.set_border((line, col), b);
+
+ if let Some(color) = color.as_ref() {
+ let mut b = cfg.get_border_color((line, col), shape).cloned();
+ b.right_top_corner = Some(color.clone());
+ cfg.set_border_color((line, col), b);
+ }
+ }
+ }
+ }
+}
+
+fn get_start_pos<D: Dimension>(
+ cfg: &SpannedConfig,
+ dims: &D,
+ offset: Offset,
+ count_columns: usize,
+) -> Option<usize> {
+ let totalw = total_width(cfg, dims, count_columns);
+ match offset {
+ Offset::Begin(i) => {
+ if i > totalw {
+ None
+ } else {
+ Some(i)
+ }
+ }
+ Offset::End(i) => {
+ if i > totalw {
+ None
+ } else {
+ Some(totalw - i)
+ }
+ }
+ }
+}
+
+fn total_width<D: Dimension>(cfg: &SpannedConfig, dims: &D, count_columns: usize) -> usize {
+ let mut totalw = cfg.has_vertical(0, count_columns) as usize;
+ for col in 0..count_columns {
+ totalw += dims.get_width(col);
+ totalw += cfg.has_vertical(col + 1, count_columns) as usize;
+ }
+
+ totalw
+}
diff --git a/vendor/tabled/src/settings/style/builder.rs b/vendor/tabled/src/settings/style/builder.rs
new file mode 100644
index 000000000..d55c400cc
--- /dev/null
+++ b/vendor/tabled/src/settings/style/builder.rs
@@ -0,0 +1,1208 @@
+//! This module contains a compile time style builder [`Style`].
+
+use core::marker::PhantomData;
+
+use crate::{
+ grid::config::{Borders, CompactConfig, CompactMultilineConfig},
+ settings::TableOption,
+};
+
+#[cfg(feature = "std")]
+use crate::grid::config::ColoredConfig;
+
+use super::{HorizontalLine, Line, VerticalLine};
+
+/// Style is represents a theme of a [`Table`].
+///
+/// ```text
+/// corner top left top intersection corner top right
+/// . | .
+/// . V .
+/// ╭───┬───┬───┬───┬───┬───┬────┬────┬────╮
+/// │ i │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
+/// ├───┼───┼───┼───┼───┼───┼────┼────┼────┤ <- this horizontal line is custom 'horizontals'
+/// │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ other lines horizontal lines are not set they called 'horizontal'
+/// │ 1 │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
+/// │ 2 │ 0 │ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │
+/// ╰───┴───┴───┴───┴───┴───┴────┴────┴────╯
+/// . ^ ^ .
+/// . | | .
+/// corner bottom left | bottom intersection corner bottom right
+/// |
+/// |
+/// all this vertical lines are called 'vertical'
+/// ```
+///
+///
+/// ```text
+/// ┌───┬───┬───┬───┬───┐
+/// │ 0 │ 1 │ 2 │ 3 │ 4 │
+/// intersection left ->├───X───X───X───X───┤ <- all this horizontal lines are called 'horizontal'
+/// │ 1 │ 2 │ 3 │ 4 │ 5 │
+/// ├───X───X───X───X───┤ <- intersection right
+/// │ 2 │ 3 │ 4 │ 5 │ 6 │
+/// └───┴───┴───┴───┴───┘
+///
+/// All 'X' positions are called 'intersection'.
+/// It's a place where 'vertical' and 'horizontal' lines intersect.
+/// ```
+///
+/// It tries to limit an controlling a valid state of it.
+/// For example, it won't allow to call method [`Style::corner_top_left`] unless [`Style::left`] and [`Style::top`] is set.
+///
+/// You can turn [`Style`] into [`RawStyle`] to have more control using [`Into`] implementation.
+///
+/// # Example
+///
+#[cfg_attr(feature = "std", doc = "```")]
+#[cfg_attr(not(feature = "std"), doc = "```ignore")]
+/// use tabled::{Table, settings::Style};
+///
+/// let style = Style::ascii()
+/// .bottom('*')
+/// .intersection(' ');
+///
+/// let data = vec!["Hello", "2021"];
+/// let table = Table::new(&data).with(style).to_string();
+///
+/// println!("{}", table);
+/// ```
+///
+/// [`Table`]: crate::Table
+/// [`RawStyle`]: crate::settings::style::RawStyle
+/// [`Style::corner_top_left`]: Style::corner_top_left
+/// [`Style::left`]: Style.left
+/// [`Style::top`]: Style.function.top
+#[derive(Debug, Clone)]
+pub struct Style<T, B, L, R, H, V, HLines = HLineArray<0>, VLines = VLineArray<0>> {
+ borders: Borders<char>,
+ horizontals: HLines,
+ verticals: VLines,
+ _top: PhantomData<T>,
+ _bottom: PhantomData<B>,
+ _left: PhantomData<L>,
+ _right: PhantomData<R>,
+ _horizontal: PhantomData<H>,
+ _vertical: PhantomData<V>,
+}
+
+type HLineArray<const N: usize> = [HorizontalLine; N];
+
+type VLineArray<const N: usize> = [VerticalLine; N];
+
+/// A marker struct which is used in [`Style`].
+#[derive(Debug, Clone)]
+pub struct On;
+
+impl Style<(), (), (), (), (), (), (), ()> {
+ /// This style is a style with no styling options on,
+ ///
+ /// ```text
+ /// id destribution link
+ /// 0 Fedora https://getfedora.org/
+ /// 2 OpenSUSE https://www.opensuse.org/
+ /// 3 Endeavouros https://endeavouros.com/
+ /// ```
+ ///
+ /// Note: The cells in the example have 1-left and 1-right indent.
+ ///
+ /// This style can be used as a base style to build a custom one.
+ ///
+ /// ```rust,no_run
+ /// # use tabled::settings::Style;
+ /// let style = Style::empty()
+ /// .top('*')
+ /// .bottom('*')
+ /// .vertical('#')
+ /// .intersection_top('*');
+ /// ```
+ pub const fn empty() -> Style<(), (), (), (), (), ()> {
+ Style::new(
+ create_borders(
+ Line::empty(),
+ Line::empty(),
+ Line::empty(),
+ None,
+ None,
+ None,
+ ),
+ [],
+ [],
+ )
+ }
+
+ /// This style is analog of `empty` but with a vertical space(' ') line.
+ ///
+ /// ```text
+ /// id destribution link
+ /// 0 Fedora https://getfedora.org/
+ /// 2 OpenSUSE https://www.opensuse.org/
+ /// 3 Endeavouros https://endeavouros.com/
+ /// ```
+ pub const fn blank() -> Style<(), (), (), (), (), On> {
+ Style::new(
+ create_borders(
+ Line::empty(),
+ Line::empty(),
+ Line::empty(),
+ None,
+ None,
+ Some(' '),
+ ),
+ [],
+ [],
+ )
+ }
+
+ /// This is a style which relays only on ASCII charset.
+ ///
+ /// It has horizontal and vertical lines.
+ ///
+ /// ```text
+ /// +----+--------------+---------------------------+
+ /// | id | destribution | link |
+ /// +----+--------------+---------------------------+
+ /// | 0 | Fedora | https://getfedora.org/ |
+ /// +----+--------------+---------------------------+
+ /// | 2 | OpenSUSE | https://www.opensuse.org/ |
+ /// +----+--------------+---------------------------+
+ /// | 3 | Endeavouros | https://endeavouros.com/ |
+ /// +----+--------------+---------------------------+
+ /// ```
+ pub const fn ascii() -> Style<On, On, On, On, On, On> {
+ Style::new(
+ create_borders(
+ Line::full('-', '+', '+', '+'),
+ Line::full('-', '+', '+', '+'),
+ Line::full('-', '+', '+', '+'),
+ Some('|'),
+ Some('|'),
+ Some('|'),
+ ),
+ [],
+ [],
+ )
+ }
+
+ /// `psql` style looks like a table style `PostgreSQL` uses.
+ ///
+ /// It has only 1 horizontal line which splits header.
+ /// And no left and right vertical lines.
+ ///
+ /// ```text
+ /// id | destribution | link
+ /// ----+--------------+---------------------------
+ /// 0 | Fedora | https://getfedora.org/
+ /// 2 | OpenSUSE | https://www.opensuse.org/
+ /// 3 | Endeavouros | https://endeavouros.com/
+ /// ```
+ pub const fn psql() -> Style<(), (), (), (), (), On, HLineArray<1>> {
+ Style::new(
+ create_borders(
+ Line::empty(),
+ Line::empty(),
+ Line::empty(),
+ None,
+ None,
+ Some('|'),
+ ),
+ [HorizontalLine::new(1, Line::empty())
+ .main(Some('-'))
+ .intersection(Some('+'))],
+ [],
+ )
+ }
+
+ /// `markdown` style mimics a `Markdown` table style.
+ ///
+ /// ```text
+ /// | id | destribution | link |
+ /// |----|--------------|---------------------------|
+ /// | 0 | Fedora | https://getfedora.org/ |
+ /// | 2 | OpenSUSE | https://www.opensuse.org/ |
+ /// | 3 | Endeavouros | https://endeavouros.com/ |
+ /// ```
+ pub const fn markdown() -> Style<(), (), On, On, (), On, HLineArray<1>> {
+ Style::new(
+ create_borders(
+ Line::empty(),
+ Line::empty(),
+ Line::empty(),
+ Some('|'),
+ Some('|'),
+ Some('|'),
+ ),
+ [HorizontalLine::new(1, Line::full('-', '|', '|', '|'))],
+ [],
+ )
+ }
+
+ /// This style is analog of [`Style::ascii`] which uses UTF-8 charset.
+ ///
+ /// It has vertical and horizontal split lines.
+ ///
+ /// ```text
+ /// ┌────┬──────────────┬───────────────────────────┐
+ /// │ id │ destribution │ link │
+ /// ├────┼──────────────┼───────────────────────────┤
+ /// │ 0 │ Fedora │ https://getfedora.org/ │
+ /// ├────┼──────────────┼───────────────────────────┤
+ /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │
+ /// ├────┼──────────────┼───────────────────────────┤
+ /// │ 3 │ Endeavouros │ https://endeavouros.com/ │
+ /// └────┴──────────────┴───────────────────────────┘
+ /// ```
+ pub const fn modern() -> Style<On, On, On, On, On, On> {
+ Style::new(
+ create_borders(
+ Line::full('─', '┬', '┌', '┐'),
+ Line::full('─', '┴', '└', '┘'),
+ Line::full('─', '┼', '├', '┤'),
+ Some('│'),
+ Some('│'),
+ Some('│'),
+ ),
+ [],
+ [],
+ )
+ }
+
+ /// This style looks like a [`Style::modern`] but without horozizontal lines except a header.
+ ///
+ /// Beware: It uses UTF-8 characters.
+ ///
+ /// ```text
+ /// ┌────┬──────────────┬───────────────────────────┐
+ /// │ id │ destribution │ link │
+ /// ├────┼──────────────┼───────────────────────────┤
+ /// │ 0 │ Fedora │ https://getfedora.org/ │
+ /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │
+ /// │ 3 │ Endeavouros │ https://endeavouros.com/ │
+ /// └────┴──────────────┴───────────────────────────┘
+ /// ```
+ pub const fn sharp() -> Style<On, On, On, On, (), On, HLineArray<1>> {
+ Style::new(
+ create_borders(
+ Line::full('─', '┬', '┌', '┐'),
+ Line::full('─', '┴', '└', '┘'),
+ Line::empty(),
+ Some('│'),
+ Some('│'),
+ Some('│'),
+ ),
+ [HorizontalLine::new(1, Line::full('─', '┼', '├', '┤'))],
+ [],
+ )
+ }
+
+ /// This style looks like a [`Style::sharp`] but with rounded corners.
+ ///
+ /// Beware: It uses UTF-8 characters.
+ ///
+ /// ```text
+ /// ╭────┬──────────────┬───────────────────────────╮
+ /// │ id │ destribution │ link │
+ /// ├────┼──────────────┼───────────────────────────┤
+ /// │ 0 │ Fedora │ https://getfedora.org/ │
+ /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │
+ /// │ 3 │ Endeavouros │ https://endeavouros.com/ │
+ /// ╰────┴──────────────┴───────────────────────────╯
+ /// ```
+ pub const fn rounded() -> Style<On, On, On, On, (), On, HLineArray<1>> {
+ Style::new(
+ create_borders(
+ Line::full('─', '┬', '╭', '╮'),
+ Line::full('─', '┴', '╰', '╯'),
+ Line::empty(),
+ Some('│'),
+ Some('│'),
+ Some('│'),
+ ),
+ [HorizontalLine::new(1, Line::full('─', '┼', '├', '┤'))],
+ [],
+ )
+ }
+
+ /// This style uses a chars which resembles '2 lines'.
+ ///
+ /// Beware: It uses UTF8 characters.
+ ///
+ /// ```text
+ /// ╔════╦══════════════╦═══════════════════════════╗
+ /// ║ id ║ destribution ║ link ║
+ /// ╠════╬══════════════╬═══════════════════════════╣
+ /// ║ 0 ║ Fedora ║ https://getfedora.org/ ║
+ /// ╠════╬══════════════╬═══════════════════════════╣
+ /// ║ 2 ║ OpenSUSE ║ https://www.opensuse.org/ ║
+ /// ╠════╬══════════════╬═══════════════════════════╣
+ /// ║ 3 ║ Endeavouros ║ https://endeavouros.com/ ║
+ /// ╚════╩══════════════╩═══════════════════════════╝
+ /// ```
+ pub const fn extended() -> Style<On, On, On, On, On, On> {
+ Style::new(
+ create_borders(
+ Line::full('═', '╦', '╔', '╗'),
+ Line::full('═', '╩', '╚', '╝'),
+ Line::full('═', '╬', '╠', '╣'),
+ Some('║'),
+ Some('║'),
+ Some('║'),
+ ),
+ [],
+ [],
+ )
+ }
+
+ /// This is a style uses only '.' and ':' chars.
+ /// It has a vertical and horizontal split lines.
+ ///
+ /// ```text
+ /// .................................................
+ /// : id : destribution : link :
+ /// :....:..............:...........................:
+ /// : 0 : Fedora : https://getfedora.org/ :
+ /// :....:..............:...........................:
+ /// : 2 : OpenSUSE : https://www.opensuse.org/ :
+ /// :....:..............:...........................:
+ /// : 3 : Endeavouros : https://endeavouros.com/ :
+ /// :....:..............:...........................:
+ /// ```
+ pub const fn dots() -> Style<On, On, On, On, On, On> {
+ Style::new(
+ create_borders(
+ Line::full('.', '.', '.', '.'),
+ Line::full('.', ':', ':', ':'),
+ Line::full('.', ':', ':', ':'),
+ Some(':'),
+ Some(':'),
+ Some(':'),
+ ),
+ [],
+ [],
+ )
+ }
+
+ /// This style is one of table views in `ReStructuredText`.
+ ///
+ /// ```text
+ /// ==== ============== ===========================
+ /// id destribution link
+ /// ==== ============== ===========================
+ /// 0 Fedora https://getfedora.org/
+ /// 2 OpenSUSE https://www.opensuse.org/
+ /// 3 Endeavouros https://endeavouros.com/
+ /// ==== ============== ===========================
+ /// ```
+ pub const fn re_structured_text() -> Style<On, On, (), (), (), On, HLineArray<1>> {
+ Style::new(
+ create_borders(
+ Line::new(Some('='), Some(' '), None, None),
+ Line::new(Some('='), Some(' '), None, None),
+ Line::empty(),
+ None,
+ None,
+ Some(' '),
+ ),
+ [HorizontalLine::new(
+ 1,
+ Line::new(Some('='), Some(' '), None, None),
+ )],
+ [],
+ )
+ }
+
+ /// This is a theme analog of [`Style::rounded`], but in using ascii charset and
+ /// with no horizontal lines.
+ ///
+ /// ```text
+ /// .-----------------------------------------------.
+ /// | id | destribution | link |
+ /// | 0 | Fedora | https://getfedora.org/ |
+ /// | 2 | OpenSUSE | https://www.opensuse.org/ |
+ /// | 3 | Endeavouros | https://endeavouros.com/ |
+ /// '-----------------------------------------------'
+ /// ```
+ pub const fn ascii_rounded() -> Style<On, On, On, On, (), On> {
+ Style::new(
+ create_borders(
+ Line::full('-', '-', '.', '.'),
+ Line::full('-', '-', '\'', '\''),
+ Line::empty(),
+ Some('|'),
+ Some('|'),
+ Some('|'),
+ ),
+ [],
+ [],
+ )
+ }
+}
+
+impl<T, B, L, R, H, V, HLines, VLines> Style<T, B, L, R, H, V, HLines, VLines> {
+ /// Frame function returns a frame as a border.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use tabled::{Table, settings::{Style, Highlight, object::Rows}};
+ ///
+ /// let data = [["10:52:19", "Hello"], ["10:52:20", "World"]];
+ /// let table = Table::new(data)
+ /// .with(Highlight::new(Rows::first(), Style::modern().get_frame()))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// concat!(
+ /// "┌──────────────────┐\n",
+ /// "│ 0 | 1 │\n",
+ /// "└──────────────────┘\n",
+ /// "| 10:52:19 | Hello |\n",
+ /// "+----------+-------+\n",
+ /// "| 10:52:20 | World |\n",
+ /// "+----------+-------+",
+ /// )
+ /// );
+ /// ```
+ #[cfg(feature = "std")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+ pub const fn get_frame(&self) -> super::Border {
+ let mut border = super::Border::filled(' ');
+
+ if let Some(c) = self.borders.top {
+ border = border.top(c);
+ }
+
+ if let Some(c) = self.borders.bottom {
+ border = border.bottom(c);
+ }
+
+ if let Some(c) = self.borders.left {
+ border = border.left(c);
+ }
+
+ if let Some(c) = self.borders.right {
+ border = border.right(c);
+ }
+
+ if let Some(c) = self.borders.top_left {
+ border = border.corner_top_left(c);
+ }
+
+ if let Some(c) = self.borders.bottom_left {
+ border = border.corner_bottom_left(c);
+ }
+
+ if let Some(c) = self.borders.top_right {
+ border = border.corner_top_right(c);
+ }
+
+ if let Some(c) = self.borders.bottom_right {
+ border = border.corner_bottom_right(c);
+ }
+
+ border
+ }
+
+ /// Get a [`Style`]'s default horizontal line.
+ ///
+ /// It doesn't return an overloaded line via [`Style::horizontals`].
+ ///
+ /// # Example
+ ///
+ #[cfg_attr(feature = "std", doc = "```")]
+ #[cfg_attr(not(feature = "std"), doc = "```ignore")]
+ /// use tabled::{settings::style::{Style, HorizontalLine, Line}, Table};
+ ///
+ /// let table = Table::new((0..3).map(|i| ("Hello", "World", i)))
+ /// .with(Style::ascii().remove_horizontal().horizontals([HorizontalLine::new(1, Style::modern().get_horizontal())]))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// concat!(
+ /// "+-------+-------+-----+\n",
+ /// "| &str | &str | i32 |\n",
+ /// "├───────┼───────┼─────┤\n",
+ /// "| Hello | World | 0 |\n",
+ /// "| Hello | World | 1 |\n",
+ /// "| Hello | World | 2 |\n",
+ /// "+-------+-------+-----+",
+ /// )
+ /// )
+ /// ```
+ pub const fn get_horizontal(&self) -> Line {
+ Line::new(
+ self.borders.horizontal,
+ self.borders.intersection,
+ self.borders.left_intersection,
+ self.borders.right_intersection,
+ )
+ }
+
+ /// Get a [`Style`]'s default horizontal line.
+ ///
+ /// It doesn't return an overloaded line via [`Style::verticals`].
+ ///
+ /// # Example
+ ///
+ #[cfg_attr(feature = "std", doc = "```")]
+ #[cfg_attr(not(feature = "std"), doc = "```ignore")]
+ /// use tabled::{settings::style::{Style, VerticalLine, Line}, Table};
+ ///
+ /// let table = Table::new((0..3).map(|i| ("Hello", "World", i)))
+ /// .with(Style::ascii().remove_horizontal().verticals([VerticalLine::new(1, Style::modern().get_vertical())]))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// concat!(
+ /// "+-------┬-------+-----+\n",
+ /// "| &str │ &str | i32 |\n",
+ /// "| Hello │ World | 0 |\n",
+ /// "| Hello │ World | 1 |\n",
+ /// "| Hello │ World | 2 |\n",
+ /// "+-------┴-------+-----+",
+ /// )
+ /// )
+ /// ```
+ pub const fn get_vertical(&self) -> Line {
+ Line::new(
+ self.borders.vertical,
+ self.borders.intersection,
+ self.borders.top_intersection,
+ self.borders.bottom_intersection,
+ )
+ }
+
+ /// Sets a top border.
+ ///
+ /// Any corners and intersections which were set will be overridden.
+ pub fn top(mut self, c: char) -> Style<On, B, L, R, H, V, HLines, VLines>
+ where
+ for<'a> &'a mut VLines: IntoIterator<Item = &'a mut VerticalLine>,
+ {
+ self.borders.top = Some(c);
+
+ if self.borders.has_left() {
+ self.borders.top_left = Some(c);
+ }
+
+ if self.borders.has_right() {
+ self.borders.top_right = Some(c);
+ }
+
+ if self.borders.has_vertical() {
+ self.borders.top_intersection = Some(c);
+ }
+
+ for vl in &mut self.verticals {
+ vl.line.connector1 = Some(c);
+ }
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+
+ /// Sets a bottom border.
+ ///
+ /// Any corners and intersections which were set will be overridden.
+ pub fn bottom(mut self, c: char) -> Style<T, On, L, R, H, V, HLines, VLines>
+ where
+ for<'a> &'a mut VLines: IntoIterator<Item = &'a mut VerticalLine>,
+ {
+ self.borders.bottom = Some(c);
+
+ if self.borders.has_left() {
+ self.borders.bottom_left = Some(c);
+ }
+
+ if self.borders.has_right() {
+ self.borders.bottom_right = Some(c);
+ }
+
+ if self.borders.has_vertical() {
+ self.borders.bottom_intersection = Some(c);
+ }
+
+ for vl in &mut self.verticals {
+ vl.line.connector2 = Some(c);
+ }
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+
+ /// Sets a left border.
+ ///
+ /// Any corners and intersections which were set will be overridden.
+ pub fn left(mut self, c: char) -> Style<T, B, On, R, H, V, HLines, VLines>
+ where
+ for<'a> &'a mut HLines: IntoIterator<Item = &'a mut HorizontalLine>,
+ {
+ self.borders.left = Some(c);
+
+ if self.borders.has_top() {
+ self.borders.top_left = Some(c);
+ }
+
+ if self.borders.has_bottom() {
+ self.borders.bottom_left = Some(c);
+ }
+
+ if self.borders.has_horizontal() {
+ self.borders.left_intersection = Some(c);
+ }
+
+ for hl in &mut self.horizontals {
+ hl.line.connector1 = Some(c);
+ }
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+
+ /// Sets a right border.
+ ///
+ /// Any corners and intersections which were set will be overridden.
+ pub fn right(mut self, c: char) -> Style<T, B, L, On, H, V, HLines, VLines>
+ where
+ for<'a> &'a mut HLines: IntoIterator<Item = &'a mut HorizontalLine>,
+ {
+ self.borders.right = Some(c);
+
+ if self.borders.has_top() {
+ self.borders.top_right = Some(c);
+ }
+
+ if self.borders.has_bottom() {
+ self.borders.bottom_right = Some(c);
+ }
+
+ if self.borders.has_horizontal() {
+ self.borders.right_intersection = Some(c);
+ }
+
+ for hl in &mut self.horizontals {
+ hl.line.connector2 = Some(c);
+ }
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+
+ /// Sets a horizontal split line.
+ ///
+ /// Any corners and intersections which were set will be overridden.
+ pub fn horizontal(mut self, c: char) -> Style<T, B, L, R, On, V, HLines, VLines>
+ where
+ for<'a> &'a mut VLines: IntoIterator<Item = &'a mut VerticalLine>,
+ {
+ self.borders.horizontal = Some(c);
+
+ if self.borders.has_vertical() {
+ self.borders.intersection = Some(c);
+ }
+
+ if self.borders.has_left() {
+ self.borders.left_intersection = Some(c);
+ }
+
+ if self.borders.has_right() {
+ self.borders.right_intersection = Some(c);
+ }
+
+ for vl in &mut self.verticals {
+ vl.line.intersection = Some(c);
+ }
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+
+ /// Sets a vertical split line.
+ ///
+ /// Any corners and intersections which were set will be overridden.
+ pub fn vertical(mut self, c: char) -> Style<T, B, L, R, H, On, HLines, VLines>
+ where
+ for<'a> &'a mut HLines: IntoIterator<Item = &'a mut HorizontalLine>,
+ {
+ self.borders.vertical = Some(c);
+
+ if self.borders.has_horizontal() {
+ self.borders.intersection = Some(c);
+ }
+
+ if self.borders.has_top() {
+ self.borders.top_intersection = Some(c);
+ }
+
+ if self.borders.has_bottom() {
+ self.borders.bottom_intersection = Some(c);
+ }
+
+ for hl in &mut self.horizontals {
+ hl.line.intersection = Some(c);
+ }
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+
+ /// Set border horizontal lines.
+ ///
+ /// # Example
+ ///
+ #[cfg_attr(feature = "derive", doc = "```")]
+ #[cfg_attr(not(feature = "derive"), doc = "```ignore")]
+ /// use tabled::{settings::style::{Style, HorizontalLine, Line}, Table};
+ ///
+ /// let table = Table::new((0..3).map(|i| ("Hello", i)))
+ /// .with(Style::rounded().horizontals((1..4).map(|i| HorizontalLine::new(i, Line::filled('#')))))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// concat!(
+ /// "╭───────┬─────╮\n",
+ /// "│ &str │ i32 │\n",
+ /// "###############\n",
+ /// "│ Hello │ 0 │\n",
+ /// "###############\n",
+ /// "│ Hello │ 1 │\n",
+ /// "###############\n",
+ /// "│ Hello │ 2 │\n",
+ /// "╰───────┴─────╯",
+ /// )
+ /// )
+ /// ```
+ pub fn horizontals<NewLines>(self, lines: NewLines) -> Style<T, B, L, R, H, V, NewLines, VLines>
+ where
+ NewLines: IntoIterator<Item = HorizontalLine> + Clone,
+ {
+ Style::new(self.borders, lines, self.verticals)
+ }
+
+ /// Set border vertical lines.
+ ///
+ /// # Example
+ ///
+ #[cfg_attr(feature = "derive", doc = "```")]
+ #[cfg_attr(not(feature = "derive"), doc = "```ignore")]
+ /// use tabled::{Table, settings::style::{Style, VerticalLine, Line}};
+ ///
+ /// let table = Table::new((0..3).map(|i| ("Hello", i)))
+ /// .with(Style::rounded().verticals((0..3).map(|i| VerticalLine::new(i, Line::filled('#')))))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// concat!(
+ /// "#───────#─────#\n",
+ /// "# &str # i32 #\n",
+ /// "├───────┼─────┤\n",
+ /// "# Hello # 0 #\n",
+ /// "# Hello # 1 #\n",
+ /// "# Hello # 2 #\n",
+ /// "#───────#─────#",
+ /// )
+ /// )
+ /// ```
+ pub fn verticals<NewLines>(self, lines: NewLines) -> Style<T, B, L, R, H, V, HLines, NewLines>
+ where
+ NewLines: IntoIterator<Item = VerticalLine> + Clone,
+ {
+ Style::new(self.borders, self.horizontals, lines)
+ }
+
+ /// Removes all horizontal lines set by [`Style::horizontals`]
+ pub fn remove_horizontals(self) -> Style<T, B, L, R, H, V, HLineArray<0>, VLines> {
+ Style::new(self.borders, [], self.verticals)
+ }
+
+ /// Removes all verticals lines set by [`Style::verticals`]
+ pub fn remove_verticals(self) -> Style<T, B, L, R, H, V, HLines, VLineArray<0>> {
+ Style::new(self.borders, self.horizontals, [])
+ }
+}
+
+impl<B, R, H, V, HLines, VLines> Style<On, B, On, R, H, V, HLines, VLines> {
+ /// Sets a top left corner.
+ pub fn corner_top_left(mut self, c: char) -> Self {
+ self.borders.top_left = Some(c);
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+}
+
+impl<B, L, H, V, HLines, VLines> Style<On, B, L, On, H, V, HLines, VLines> {
+ /// Sets a top right corner.
+ pub fn corner_top_right(mut self, c: char) -> Self {
+ self.borders.top_right = Some(c);
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+}
+
+impl<T, L, H, V, HLines, VLines> Style<T, On, L, On, H, V, HLines, VLines> {
+ /// Sets a bottom right corner.
+ pub fn corner_bottom_right(mut self, c: char) -> Self {
+ self.borders.bottom_right = Some(c);
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+}
+
+impl<T, R, H, V, HLines, VLines> Style<T, On, On, R, H, V, HLines, VLines> {
+ /// Sets a bottom left corner.
+ pub fn corner_bottom_left(mut self, c: char) -> Self {
+ self.borders.bottom_left = Some(c);
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+}
+
+impl<T, B, R, V, HLines, VLines> Style<T, B, On, R, On, V, HLines, VLines> {
+ /// Sets a left intersection char.
+ pub fn intersection_left(mut self, c: char) -> Self {
+ self.borders.left_intersection = Some(c);
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+}
+
+impl<T, B, L, V, HLines, VLines> Style<T, B, L, On, On, V, HLines, VLines> {
+ /// Sets a right intersection char.
+ pub fn intersection_right(mut self, c: char) -> Self {
+ self.borders.right_intersection = Some(c);
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+}
+
+impl<B, L, R, H, HLines, VLines> Style<On, B, L, R, H, On, HLines, VLines> {
+ /// Sets a top intersection char.
+ pub fn intersection_top(mut self, c: char) -> Self {
+ self.borders.top_intersection = Some(c);
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+}
+
+impl<T, L, R, H, HLines, VLines> Style<T, On, L, R, H, On, HLines, VLines> {
+ /// Sets a bottom intersection char.
+ pub fn intersection_bottom(mut self, c: char) -> Self {
+ self.borders.bottom_intersection = Some(c);
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+}
+
+impl<T, B, L, R, HLines, VLines> Style<T, B, L, R, On, On, HLines, VLines> {
+ /// Sets an inner intersection char.
+ /// A char between horizontal and vertical split lines.
+ pub fn intersection(mut self, c: char) -> Self {
+ self.borders.intersection = Some(c);
+
+ Style::new(self.borders, self.horizontals, self.verticals)
+ }
+}
+
+impl<B, L, R, H, V, HLines, VLines> Style<On, B, L, R, H, V, HLines, VLines> {
+ /// Removes top border.
+ pub fn remove_top(
+ mut self,
+ ) -> Style<(), B, L, R, H, V, HLines, VerticalLineIter<VLines::IntoIter>>
+ where
+ VLines: IntoIterator<Item = VerticalLine> + Clone,
+ {
+ self.borders.top = None;
+ self.borders.top_intersection = None;
+ self.borders.top_left = None;
+ self.borders.top_right = None;
+
+ let iter = VerticalLineIter::new(self.verticals.into_iter(), false, true, false);
+ Style::new(self.borders, self.horizontals, iter)
+ }
+}
+
+impl<T, L, R, H, V, HLines, VLines> Style<T, On, L, R, H, V, HLines, VLines> {
+ /// Removes bottom border.
+ pub fn remove_bottom(
+ mut self,
+ ) -> Style<T, (), L, R, H, V, HLines, VerticalLineIter<VLines::IntoIter>>
+ where
+ VLines: IntoIterator<Item = VerticalLine> + Clone,
+ {
+ self.borders.bottom = None;
+ self.borders.bottom_intersection = None;
+ self.borders.bottom_left = None;
+ self.borders.bottom_right = None;
+
+ let iter = VerticalLineIter::new(self.verticals.into_iter(), false, false, true);
+ Style::new(self.borders, self.horizontals, iter)
+ }
+}
+
+impl<T, B, R, H, V, HLines, VLines> Style<T, B, On, R, H, V, HLines, VLines> {
+ /// Removes left border.
+ pub fn remove_left(
+ mut self,
+ ) -> Style<T, B, (), R, H, V, HorizontalLineIter<HLines::IntoIter>, VLines>
+ where
+ HLines: IntoIterator<Item = HorizontalLine> + Clone,
+ {
+ self.borders.left = None;
+ self.borders.left_intersection = None;
+ self.borders.top_left = None;
+ self.borders.bottom_left = None;
+
+ let iter = HorizontalLineIter::new(self.horizontals.into_iter(), false, true, false);
+ Style::new(self.borders, iter, self.verticals)
+ }
+}
+
+impl<T, B, L, H, V, HLines, VLines> Style<T, B, L, On, H, V, HLines, VLines> {
+ /// Removes right border.
+ pub fn remove_right(
+ mut self,
+ ) -> Style<T, B, L, (), H, V, HorizontalLineIter<HLines::IntoIter>, VLines>
+ where
+ HLines: IntoIterator<Item = HorizontalLine> + Clone,
+ {
+ self.borders.right = None;
+ self.borders.right_intersection = None;
+ self.borders.top_right = None;
+ self.borders.bottom_right = None;
+
+ let iter = HorizontalLineIter::new(self.horizontals.into_iter(), false, false, true);
+ Style::new(self.borders, iter, self.verticals)
+ }
+}
+
+impl<T, B, L, R, V, HLines, VLines> Style<T, B, L, R, On, V, HLines, VLines> {
+ /// Removes horizontal split lines.
+ ///
+ /// Not including custom split lines.
+ pub fn remove_horizontal(
+ mut self,
+ ) -> Style<T, B, L, R, (), V, HLines, VerticalLineIter<VLines::IntoIter>>
+ where
+ VLines: IntoIterator<Item = VerticalLine> + Clone,
+ {
+ self.borders.horizontal = None;
+ self.borders.left_intersection = None;
+ self.borders.right_intersection = None;
+ self.borders.intersection = None;
+
+ let iter = VerticalLineIter::new(self.verticals.into_iter(), true, false, false);
+ Style::new(self.borders, self.horizontals, iter)
+ }
+}
+
+impl<T, B, L, R, H, HLines, VLines> Style<T, B, L, R, H, On, HLines, VLines> {
+ /// Removes vertical split lines.
+ pub fn remove_vertical(
+ mut self,
+ ) -> Style<T, B, L, R, H, (), HorizontalLineIter<HLines::IntoIter>, VLines>
+ where
+ HLines: IntoIterator<Item = HorizontalLine> + Clone,
+ {
+ self.borders.vertical = None;
+ self.borders.top_intersection = None;
+ self.borders.bottom_intersection = None;
+ self.borders.intersection = None;
+
+ let iter = HorizontalLineIter::new(self.horizontals.into_iter(), true, false, false);
+ Style::new(self.borders, iter, self.verticals)
+ }
+}
+
+impl<T, B, L, R, H, V, HLines, VLines> Style<T, B, L, R, H, V, HLines, VLines> {
+ const fn new(borders: Borders<char>, horizontals: HLines, verticals: VLines) -> Self {
+ Self {
+ borders,
+ horizontals,
+ verticals,
+ _top: PhantomData,
+ _bottom: PhantomData,
+ _left: PhantomData,
+ _right: PhantomData,
+ _horizontal: PhantomData,
+ _vertical: PhantomData,
+ }
+ }
+
+ /// Return borders of a table.
+ pub const fn get_borders(&self) -> &Borders<char> {
+ &self.borders
+ }
+
+ /// Return custom horizontals which were set.
+ pub const fn get_horizontals(&self) -> &HLines {
+ &self.horizontals
+ }
+
+ /// Return custom verticals which were set.
+ pub const fn get_verticals(&self) -> &VLines {
+ &self.verticals
+ }
+}
+
+#[cfg(feature = "std")]
+impl<T, B, L, R, H, V, HLines, VLines, I, D> TableOption<I, D, ColoredConfig>
+ for Style<T, B, L, R, H, V, HLines, VLines>
+where
+ HLines: IntoIterator<Item = HorizontalLine> + Clone,
+ VLines: IntoIterator<Item = VerticalLine> + Clone,
+{
+ fn change(self, records: &mut I, cfg: &mut ColoredConfig, dimension: &mut D) {
+ cfg.clear_theme();
+
+ cfg.set_borders(self.borders);
+
+ for hl in self.horizontals {
+ hl.change(records, cfg, dimension);
+ }
+
+ for vl in self.verticals {
+ vl.change(records, cfg, dimension);
+ }
+ }
+}
+
+impl<T, B, L, R, H, V, HLines, VLines, I, D> TableOption<I, D, CompactConfig>
+ for Style<T, B, L, R, H, V, HLines, VLines>
+where
+ HLines: IntoIterator<Item = HorizontalLine> + Clone,
+{
+ fn change(self, records: &mut I, cfg: &mut CompactConfig, dimension: &mut D) {
+ *cfg = cfg.set_borders(self.borders);
+
+ let first_line = self.horizontals.into_iter().next();
+ if let Some(line) = first_line {
+ line.change(records, cfg, dimension);
+ }
+ }
+}
+
+impl<T, B, L, R, H, V, HLines, VLines, I, D> TableOption<I, D, CompactMultilineConfig>
+ for Style<T, B, L, R, H, V, HLines, VLines>
+where
+ HLines: IntoIterator<Item = HorizontalLine> + Clone,
+{
+ fn change(self, records: &mut I, cfg: &mut CompactMultilineConfig, dimension: &mut D) {
+ self.change(records, cfg.as_mut(), dimension)
+ }
+}
+
+/// An iterator which limits [`Line`] influence on iterations over lines for in [`Style`].
+#[derive(Debug, Clone)]
+pub struct HorizontalLineIter<I> {
+ iter: I,
+ intersection: bool,
+ left: bool,
+ right: bool,
+}
+
+impl<I> HorizontalLineIter<I> {
+ fn new(iter: I, intersection: bool, left: bool, right: bool) -> Self {
+ Self {
+ iter,
+ intersection,
+ left,
+ right,
+ }
+ }
+}
+
+impl<I> Iterator for HorizontalLineIter<I>
+where
+ I: Iterator<Item = HorizontalLine>,
+{
+ type Item = HorizontalLine;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut hl = self.iter.next()?;
+
+ if self.intersection {
+ hl.line.intersection = None;
+ }
+
+ if self.left {
+ hl.line.connector1 = None;
+ }
+
+ if self.right {
+ hl.line.connector2 = None;
+ }
+
+ Some(hl)
+ }
+}
+
+/// An iterator which limits [`Line`] influence on iterations over lines for in [`Style`].
+#[derive(Debug, Clone)]
+pub struct VerticalLineIter<I> {
+ iter: I,
+ intersection: bool,
+ top: bool,
+ bottom: bool,
+}
+
+impl<I> VerticalLineIter<I> {
+ fn new(iter: I, intersection: bool, top: bool, bottom: bool) -> Self {
+ Self {
+ iter,
+ intersection,
+ top,
+ bottom,
+ }
+ }
+}
+
+impl<I> Iterator for VerticalLineIter<I>
+where
+ I: Iterator<Item = VerticalLine>,
+{
+ type Item = VerticalLine;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut hl = self.iter.next()?;
+
+ if self.intersection {
+ hl.line.intersection = None;
+ }
+
+ if self.top {
+ hl.line.connector1 = None;
+ }
+
+ if self.bottom {
+ hl.line.connector2 = None;
+ }
+
+ Some(hl)
+ }
+}
+
+const fn create_borders(
+ top: Line,
+ bottom: Line,
+ horizontal: Line,
+ left: Option<char>,
+ right: Option<char>,
+ vertical: Option<char>,
+) -> Borders<char> {
+ Borders {
+ top: top.main,
+ bottom: bottom.main,
+ top_left: top.connector1,
+ top_right: top.connector2,
+ bottom_left: bottom.connector1,
+ bottom_right: bottom.connector2,
+ top_intersection: top.intersection,
+ bottom_intersection: bottom.intersection,
+ left_intersection: horizontal.connector1,
+ right_intersection: horizontal.connector2,
+ horizontal: horizontal.main,
+ intersection: horizontal.intersection,
+ left,
+ right,
+ vertical,
+ }
+}
diff --git a/vendor/tabled/src/settings/style/horizontal_line.rs b/vendor/tabled/src/settings/style/horizontal_line.rs
new file mode 100644
index 000000000..bbcdc3fb4
--- /dev/null
+++ b/vendor/tabled/src/settings/style/horizontal_line.rs
@@ -0,0 +1,69 @@
+use crate::{
+ grid::config::{CompactConfig, CompactMultilineConfig},
+ settings::TableOption,
+};
+
+#[cfg(feature = "std")]
+use crate::grid::config::{ColoredConfig, HorizontalLine as GridLine};
+
+use super::Line;
+
+/// A horizontal split line which can be used to set a border.
+#[cfg_attr(not(feature = "std"), allow(dead_code))]
+#[derive(Debug, Clone)]
+pub struct HorizontalLine {
+ pub(super) index: usize,
+ pub(super) line: Line,
+}
+
+impl HorizontalLine {
+ /// Creates a new horizontal split line.
+ pub const fn new(index: usize, line: Line) -> Self {
+ Self { index, line }
+ }
+
+ /// Sets a horizontal character.
+ pub const fn main(mut self, c: Option<char>) -> Self {
+ self.line.main = c;
+ self
+ }
+
+ /// Sets a vertical intersection character.
+ pub const fn intersection(mut self, c: Option<char>) -> Self {
+ self.line.intersection = c;
+ self
+ }
+
+ /// Sets a left character.
+ pub const fn left(mut self, c: Option<char>) -> Self {
+ self.line.connector1 = c;
+ self
+ }
+
+ /// Sets a right character.
+ pub const fn right(mut self, c: Option<char>) -> Self {
+ self.line.connector2 = c;
+ self
+ }
+}
+
+#[cfg(feature = "std")]
+impl<R, D> TableOption<R, D, ColoredConfig> for HorizontalLine {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ cfg.insert_horizontal_line(self.index, GridLine::from(self.line))
+ }
+}
+
+impl<R, D> TableOption<R, D, CompactConfig> for HorizontalLine {
+ fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) {
+ if self.index == 1 {
+ *cfg = cfg.set_first_horizontal_line(papergrid::config::Line::from(self.line));
+ }
+ }
+}
+
+impl<R, D> TableOption<R, D, CompactMultilineConfig> for HorizontalLine {
+ fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) {
+ self.change(records, cfg.as_mut(), dimension)
+ }
+}
diff --git a/vendor/tabled/src/settings/style/line.rs b/vendor/tabled/src/settings/style/line.rs
new file mode 100644
index 000000000..10b3895c4
--- /dev/null
+++ b/vendor/tabled/src/settings/style/line.rs
@@ -0,0 +1,91 @@
+#[cfg(feature = "std")]
+use crate::grid::config::{HorizontalLine, VerticalLine};
+
+/// The structure represent a vertical or horizontal line.
+#[derive(Debug, Default, Clone, Copy)]
+pub struct Line {
+ pub(crate) main: Option<char>,
+ pub(crate) intersection: Option<char>,
+ pub(crate) connector1: Option<char>,
+ pub(crate) connector2: Option<char>,
+}
+
+impl Line {
+ /// Creates a new [`Line`] object.
+ pub const fn new(
+ main: Option<char>,
+ intersection: Option<char>,
+ connector1: Option<char>,
+ connector2: Option<char>,
+ ) -> Self {
+ Self {
+ main,
+ intersection,
+ connector1,
+ connector2,
+ }
+ }
+
+ /// Creates a new [`Line`] object with all chars set.
+ pub const fn full(main: char, intersection: char, connector1: char, connector2: char) -> Self {
+ Self::new(
+ Some(main),
+ Some(intersection),
+ Some(connector1),
+ Some(connector2),
+ )
+ }
+
+ /// Creates a new [`Line`] object with all chars set to the provided one.
+ pub const fn filled(c: char) -> Self {
+ Self::full(c, c, c, c)
+ }
+
+ /// Creates a new [`Line`] object with all chars not set.
+ pub const fn empty() -> Self {
+ Self::new(None, None, None, None)
+ }
+
+ /// Checks if the line has nothing set.
+ pub const fn is_empty(&self) -> bool {
+ self.main.is_none()
+ && self.intersection.is_none()
+ && self.connector1.is_none()
+ && self.connector2.is_none()
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<Line> for HorizontalLine {
+ fn from(l: Line) -> Self {
+ Self {
+ main: l.main,
+ intersection: l.intersection,
+ left: l.connector1,
+ right: l.connector2,
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<Line> for VerticalLine {
+ fn from(l: Line) -> Self {
+ Self {
+ main: l.main,
+ intersection: l.intersection,
+ top: l.connector1,
+ bottom: l.connector2,
+ }
+ }
+}
+
+impl From<Line> for papergrid::config::Line<char> {
+ fn from(l: Line) -> Self {
+ Self {
+ main: l.main.unwrap_or(' '),
+ intersection: l.intersection,
+ connect1: l.connector1,
+ connect2: l.connector2,
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/style/mod.rs b/vendor/tabled/src/settings/style/mod.rs
new file mode 100644
index 000000000..b8d7f688d
--- /dev/null
+++ b/vendor/tabled/src/settings/style/mod.rs
@@ -0,0 +1,127 @@
+//! This module contains a list of primitives which can be applied to change [`Table`] style.
+//!
+//! ## [`Style`]
+//!
+//! It is responsible for a table border style.
+//! An individual cell border can be set by [`Border`].
+//!
+//! ### Example
+//!
+#![cfg_attr(feature = "std", doc = "```")]
+#![cfg_attr(not(feature = "std"), doc = "```ignore")]
+//! use tabled::{Table, settings::Style};
+//!
+//! let data = vec!["Hello", "2022"];
+//! let mut table = Table::new(&data);
+//! table.with(Style::psql());
+//!
+//! assert_eq!(
+//! table.to_string(),
+//! concat!(
+//! " &str \n",
+//! "-------\n",
+//! " Hello \n",
+//! " 2022 ",
+//! )
+//! )
+//! ```
+//!
+//! ## [`BorderText`]
+//!
+//! It's used to override a border with a custom text.
+//!
+//! ### Example
+//!
+#![cfg_attr(feature = "std", doc = "```")]
+#![cfg_attr(not(feature = "std"), doc = "```ignore")]
+//! use tabled::{Table, settings::style::{BorderText, Style}};
+//!
+//! let data = vec!["Hello", "2022"];
+//! let table = Table::new(&data)
+//! .with(Style::psql())
+//! .with(BorderText::new("Santa").horizontal(1))
+//! .to_string();
+//!
+//! assert_eq!(
+//! table,
+//! concat!(
+//! " &str \n",
+//! "Santa--\n",
+//! " Hello \n",
+//! " 2022 ",
+//! )
+//! )
+//! ```
+//!
+//! ## [`Border`]
+//!
+//! [`Border`] can be used to modify cell's borders.
+//!
+//! It's possible to set a collored border when `color` feature is on.
+//!
+//! ### Example
+//!
+#![cfg_attr(feature = "std", doc = "```")]
+#![cfg_attr(not(feature = "std"), doc = "```ignore")]
+//! use tabled::{Table, settings::{Modify, Style}};
+//!
+//! let data = vec!["Hello", "2022"];
+//! let table = Table::new(&data)
+//! .with(Style::psql())
+//! .with(Modify::new((0, 0)).with(Style::modern().get_frame()))
+//! .to_string();
+//!
+//! assert_eq!(
+//! table,
+//! concat!(
+//! "┌───────┐\n",
+//! "│ &str │\n",
+//! "└───────┘\n",
+//! " Hello \n",
+//! " 2022 ",
+//! )
+//! )
+//! ```
+//!
+//! ## [`RawStyle`]
+//!
+//! A different representation of [`Style`].
+//! With no checks in place.
+//!
+//! It also contains a list of types to support colors.
+//!
+//! [`Table`]: crate::Table
+//! [`BorderText`]: crate::settings::style::BorderText
+//! [`RawStyle`]: crate::settings::style::RawStyle
+
+#[cfg(feature = "std")]
+mod border;
+#[cfg(feature = "std")]
+mod border_char;
+#[cfg(feature = "std")]
+mod border_color;
+#[cfg(feature = "std")]
+mod border_text;
+#[cfg(feature = "std")]
+mod offset;
+#[cfg(feature = "std")]
+mod raw_style;
+#[cfg(feature = "std")]
+mod span_border_correction;
+
+mod builder;
+mod horizontal_line;
+mod line;
+mod vertical_line;
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub use self::{
+ border::Border, border_char::BorderChar, border_color::BorderColor, border_text::BorderText,
+ offset::Offset, raw_style::RawStyle, span_border_correction::BorderSpanCorrection,
+};
+
+pub use builder::{HorizontalLineIter, On, Style, VerticalLineIter};
+pub use horizontal_line::HorizontalLine;
+pub use line::Line;
+pub use vertical_line::VerticalLine;
diff --git a/vendor/tabled/src/settings/style/offset.rs b/vendor/tabled/src/settings/style/offset.rs
new file mode 100644
index 000000000..3f8a4f85e
--- /dev/null
+++ b/vendor/tabled/src/settings/style/offset.rs
@@ -0,0 +1,19 @@
+use crate::grid::config;
+
+/// The structure represents an offset in a text.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum Offset {
+ /// An offset from the start.
+ Begin(usize),
+ /// An offset from the end.
+ End(usize),
+}
+
+impl From<Offset> for config::Offset {
+ fn from(o: Offset) -> Self {
+ match o {
+ Offset::Begin(i) => config::Offset::Begin(i),
+ Offset::End(i) => config::Offset::End(i),
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/style/raw_style.rs b/vendor/tabled/src/settings/style/raw_style.rs
new file mode 100644
index 000000000..eedb48e79
--- /dev/null
+++ b/vendor/tabled/src/settings/style/raw_style.rs
@@ -0,0 +1,438 @@
+//! This module contains [`RawStyle`] structure, which is analogues to [`Style`] but not generic,
+//! so sometimes it can be used more conviently.
+
+use std::collections::HashMap;
+
+use crate::{
+ grid::{color::AnsiColor, config, config::Borders, config::ColoredConfig, records::Records},
+ settings::{Color, TableOption},
+};
+
+use super::{Border, HorizontalLine, Line, Style, VerticalLine};
+
+/// A raw style data, which can be produced safely from [`Style`].
+///
+/// It can be useful in order to not have a generics and be able to use it as a variable more conveniently.
+#[derive(Default, Debug, Clone)]
+pub struct RawStyle {
+ borders: Borders<char>,
+ colors: Borders<AnsiColor<'static>>,
+ horizontals: HashMap<usize, Line>,
+ verticals: HashMap<usize, Line>,
+}
+
+impl RawStyle {
+ /// Set a top border character.
+ pub fn set_top(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.top = s;
+ self
+ }
+
+ /// Set a top border color.
+ pub fn set_color_top(&mut self, color: Color) -> &mut Self {
+ self.colors.top = Some(color.into());
+ self
+ }
+
+ /// Set a bottom border character.
+ pub fn set_bottom(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.bottom = s;
+ self
+ }
+
+ /// Set a bottom border color.
+ pub fn set_color_bottom(&mut self, color: Color) -> &mut Self {
+ self.colors.bottom = Some(color.into());
+ self
+ }
+
+ /// Set a left border character.
+ pub fn set_left(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.left = s;
+ self
+ }
+
+ /// Set a left border color.
+ pub fn set_color_left(&mut self, color: Color) -> &mut Self {
+ self.colors.left = Some(color.into());
+ self
+ }
+
+ /// Set a right border character.
+ pub fn set_right(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.right = s;
+ self
+ }
+
+ /// Set a right border color.
+ pub fn set_color_right(&mut self, color: Color) -> &mut Self {
+ self.colors.right = Some(color.into());
+ self
+ }
+
+ /// Set a top intersection character.
+ pub fn set_intersection_top(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.top_intersection = s;
+ self
+ }
+
+ /// Set a top intersection color.
+ pub fn set_color_intersection_top(&mut self, color: Color) -> &mut Self {
+ self.colors.top_intersection = Some(color.into());
+ self
+ }
+
+ /// Set a bottom intersection character.
+ pub fn set_intersection_bottom(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.bottom_intersection = s;
+ self
+ }
+
+ /// Set a bottom intersection color.
+ pub fn set_color_intersection_bottom(&mut self, color: Color) -> &mut Self {
+ self.colors.bottom_intersection = Some(color.into());
+ self
+ }
+
+ /// Set a left split character.
+ pub fn set_intersection_left(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.left_intersection = s;
+ self
+ }
+
+ /// Set a bottom intersection color.
+ pub fn set_color_intersection_left(&mut self, color: Color) -> &mut Self {
+ self.colors.left_intersection = Some(color.into());
+ self
+ }
+
+ /// Set a right split character.
+ pub fn set_intersection_right(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.right_intersection = s;
+ self
+ }
+
+ /// Set a bottom intersection color.
+ pub fn set_color_intersection_right(&mut self, color: Color) -> &mut Self {
+ self.colors.right_intersection = Some(color.into());
+ self
+ }
+
+ /// Set an internal character.
+ pub fn set_intersection(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.intersection = s;
+ self
+ }
+
+ /// Set a bottom intersection color.
+ pub fn set_color_intersection(&mut self, color: Color) -> &mut Self {
+ self.colors.intersection = Some(color.into());
+ self
+ }
+
+ /// Set a vertical character.
+ pub fn set_vertical(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.vertical = s;
+ self
+ }
+
+ /// Set a bottom intersection color.
+ pub fn set_color_vertical(&mut self, color: Color) -> &mut Self {
+ self.colors.vertical = Some(color.into());
+ self
+ }
+
+ /// Set a horizontal character.
+ pub fn set_horizontal(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.horizontal = s;
+ self
+ }
+
+ /// Set a bottom intersection color.
+ pub fn set_color_horizontal(&mut self, color: Color) -> &mut Self {
+ self.colors.horizontal = Some(color.into());
+ self
+ }
+
+ /// Set a character for a top left corner.
+ pub fn set_corner_top_left(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.top_left = s;
+ self
+ }
+ /// Set a bottom intersection color.
+ pub fn set_color_corner_top_left(&mut self, color: Color) -> &mut Self {
+ self.colors.top_left = Some(color.into());
+ self
+ }
+
+ /// Set a character for a top right corner.
+ pub fn set_corner_top_right(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.top_right = s;
+ self
+ }
+
+ /// Set a bottom intersection color.
+ pub fn set_color_corner_top_right(&mut self, color: Color) -> &mut Self {
+ self.colors.top_right = Some(color.into());
+ self
+ }
+
+ /// Set a character for a bottom left corner.
+ pub fn set_corner_bottom_left(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.bottom_left = s;
+ self
+ }
+ /// Set a bottom intersection color.
+ pub fn set_color_corner_bottom_left(&mut self, color: Color) -> &mut Self {
+ self.colors.bottom_left = Some(color.into());
+ self
+ }
+
+ /// Set a character for a bottom right corner.
+ pub fn set_corner_bottom_right(&mut self, s: Option<char>) -> &mut Self {
+ self.borders.bottom_right = s;
+ self
+ }
+ /// Set a bottom intersection color.
+ pub fn set_color_corner_bottom_right(&mut self, color: Color) -> &mut Self {
+ self.colors.bottom_right = Some(color.into());
+ self
+ }
+
+ /// Set horizontal border lines.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::collections::HashMap;
+ /// use tabled::{Table, settings::style::{Style, Line, RawStyle}};
+ ///
+ /// let mut style = RawStyle::from(Style::re_structured_text());
+ ///
+ /// let mut lines = HashMap::new();
+ /// lines.insert(1, Style::extended().get_horizontal());
+ /// style.set_horizontals(lines);
+ ///
+ /// let table = Table::new((0..3).map(|i| ("Hello", i)))
+ /// .with(style)
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// concat!(
+ /// " ======= ===== \n",
+ /// " &str i32 \n",
+ /// "╠═══════╬═════╣\n",
+ /// " Hello 0 \n",
+ /// " Hello 1 \n",
+ /// " Hello 2 \n",
+ /// " ======= ===== ",
+ /// ),
+ /// )
+ /// ```
+ pub fn set_horizontals(&mut self, lines: HashMap<usize, Line>) -> &mut Self {
+ self.horizontals = lines;
+ self
+ }
+
+ /// Insert a horizontal line to a specific row location.
+ pub fn insert_horizontal(&mut self, row: usize, line: Line) -> &mut Self {
+ let _ = self.horizontals.insert(row, line);
+ self
+ }
+
+ /// Insert a horizontal line to a specific row location.
+ pub fn get_horizontal(&self, row: usize) -> Option<Line> {
+ self.horizontals.get(&row).cloned()
+ }
+
+ /// Set vertical border lines.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::collections::HashMap;
+ /// use tabled::{Table, settings::style::{Style, Line, RawStyle}};
+ ///
+ /// let mut style = RawStyle::from(Style::re_structured_text());
+ ///
+ /// let mut lines = HashMap::new();
+ /// lines.insert(1, Style::extended().get_horizontal());
+ /// style.set_verticals(lines);
+ ///
+ /// let table = Table::new((0..3).map(|i| ("Hello", i)))
+ /// .with(style)
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// concat!(
+ /// "=======╠=====\n",
+ /// " &str ═ i32 \n",
+ /// "======= =====\n",
+ /// " Hello ═ 0 \n",
+ /// " Hello ═ 1 \n",
+ /// " Hello ═ 2 \n",
+ /// "=======╣=====",
+ /// ),
+ /// )
+ /// ```
+ pub fn set_verticals(&mut self, lines: HashMap<usize, Line>) -> &mut Self {
+ self.verticals = lines;
+ self
+ }
+
+ /// Insert a vertical line into specific column location.
+ pub fn insert_vertical(&mut self, column: usize, line: Line) -> &mut Self {
+ let _ = self.verticals.insert(column, line);
+ self
+ }
+
+ /// Get a left char.
+ pub fn get_left(&self) -> Option<char> {
+ self.borders.left
+ }
+
+ /// Get a left intersection char.
+ pub fn get_left_intersection(&self) -> Option<char> {
+ self.borders.left_intersection
+ }
+
+ /// Get a right char.
+ pub fn get_right(&self) -> Option<char> {
+ self.borders.right
+ }
+
+ /// Get a right intersection char.
+ pub fn get_right_intersection(&self) -> Option<char> {
+ self.borders.right_intersection
+ }
+
+ /// Get a top char.
+ pub fn get_top(&self) -> Option<char> {
+ self.borders.top
+ }
+
+ /// Get a top left char.
+ pub fn get_top_left(&self) -> Option<char> {
+ self.borders.top_left
+ }
+
+ /// Get a top right char.
+ pub fn get_top_right(&self) -> Option<char> {
+ self.borders.top_right
+ }
+
+ /// Get a top intersection char.
+ pub fn get_top_intersection(&self) -> Option<char> {
+ self.borders.top_intersection
+ }
+
+ /// Get a bottom intersection char.
+ pub fn get_bottom(&self) -> Option<char> {
+ self.borders.bottom
+ }
+
+ /// Get a bottom intersection char.
+ pub fn get_bottom_left(&self) -> Option<char> {
+ self.borders.bottom_left
+ }
+
+ /// Get a bottom intersection char.
+ pub fn get_bottom_right(&self) -> Option<char> {
+ self.borders.bottom_right
+ }
+
+ /// Get a bottom intersection char.
+ pub fn get_bottom_intersection(&self) -> Option<char> {
+ self.borders.bottom_intersection
+ }
+
+ /// Returns an outer border of the style.
+ pub fn get_frame(&self) -> Border {
+ Border::from(crate::grid::config::Border {
+ top: self.borders.top,
+ bottom: self.borders.bottom,
+ left: self.borders.left,
+ right: self.borders.right,
+ left_top_corner: self.borders.top_left,
+ right_top_corner: self.borders.top_right,
+ left_bottom_corner: self.borders.bottom_left,
+ right_bottom_corner: self.borders.bottom_right,
+ })
+ }
+
+ /// Returns an general borders configuration of the style.
+ pub fn get_borders(&self) -> Borders<char> {
+ self.borders
+ }
+}
+
+impl From<Borders<char>> for RawStyle {
+ fn from(borders: Borders<char>) -> Self {
+ Self {
+ borders,
+ horizontals: HashMap::new(),
+ verticals: HashMap::new(),
+ colors: Borders::default(),
+ }
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for RawStyle
+where
+ R: Records,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) {
+ (&self).change(records, cfg, dimension)
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for &RawStyle {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ cfg.clear_theme();
+
+ cfg.set_borders(self.borders);
+
+ for (&row, line) in &self.horizontals {
+ cfg.insert_horizontal_line(row, config::HorizontalLine::from(*line));
+ }
+
+ for (&col, line) in &self.verticals {
+ cfg.insert_vertical_line(col, config::VerticalLine::from(*line));
+ }
+
+ if !self.colors.is_empty() {
+ cfg.set_borders_color(self.colors.clone());
+ }
+ }
+}
+
+impl<T, B, L, R, H, V, HLines, VLines> From<Style<T, B, L, R, H, V, HLines, VLines>> for RawStyle
+where
+ HLines: IntoIterator<Item = HorizontalLine> + Clone,
+ VLines: IntoIterator<Item = VerticalLine> + Clone,
+{
+ fn from(style: Style<T, B, L, R, H, V, HLines, VLines>) -> Self {
+ let horizontals = style
+ .get_horizontals()
+ .clone()
+ .into_iter()
+ .map(|hr| (hr.index, hr.line))
+ .collect();
+
+ let verticals = style
+ .get_verticals()
+ .clone()
+ .into_iter()
+ .map(|hr| (hr.index, hr.line))
+ .collect();
+
+ Self {
+ borders: *style.get_borders(),
+ horizontals,
+ verticals,
+ colors: Borders::default(),
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/style/span_border_correction.rs b/vendor/tabled/src/settings/style/span_border_correction.rs
new file mode 100644
index 000000000..665a12788
--- /dev/null
+++ b/vendor/tabled/src/settings/style/span_border_correction.rs
@@ -0,0 +1,216 @@
+//! This module contains [`BorderSpanCorrection`] structure, which can be useful when [`Span`] is used, and
+//! you want to fix the intersections symbols which are left intact by default.
+//!
+//! [`Span`]: crate::settings::span::Span
+
+use crate::{
+ grid::{
+ config::{ColoredConfig, Position, SpannedConfig},
+ records::{ExactRecords, Records},
+ },
+ settings::TableOption,
+};
+
+/// A correctness function of style for [`Table`] which has [`Span`]s.
+///
+/// Try to fix the style when table contains spans.
+///
+/// By default [`Style`] doesn't implies any logic to better render split lines when
+/// [`Span`] is used.
+///
+/// So this function can be used to set the split lines in regard of spans used.
+///
+/// # Example
+///
+/// ```
+/// use tabled::{
+/// Table,
+/// settings::{
+/// Modify, style::{Style, BorderSpanCorrection},
+/// Format, Span, object::Cell
+/// }
+/// };
+///
+/// let data = vec![
+/// ("09", "June", "2022"),
+/// ("10", "July", "2022"),
+/// ];
+///
+/// let mut table = Table::new(data);
+/// table.with(Modify::new((0, 0)).with("date").with(Span::column(3)));
+///
+/// assert_eq!(
+/// table.to_string(),
+/// concat!(
+/// "+----+------+------+\n",
+/// "| date |\n",
+/// "+----+------+------+\n",
+/// "| 09 | June | 2022 |\n",
+/// "+----+------+------+\n",
+/// "| 10 | July | 2022 |\n",
+/// "+----+------+------+",
+/// )
+/// );
+///
+/// table.with(BorderSpanCorrection);
+///
+/// assert_eq!(
+/// table.to_string(),
+/// concat!(
+/// "+------------------+\n",
+/// "| date |\n",
+/// "+----+------+------+\n",
+/// "| 09 | June | 2022 |\n",
+/// "+----+------+------+\n",
+/// "| 10 | July | 2022 |\n",
+/// "+----+------+------+",
+/// )
+/// );
+/// ```
+/// See [`BorderSpanCorrection`].
+///
+/// [`Table`]: crate::Table
+/// [`Span`]: crate::settings::span::Span
+/// [`Style`]: crate::settings::Style
+/// [`Style::correct_spans`]: crate::settings::style::BorderSpanCorrection
+#[derive(Debug)]
+pub struct BorderSpanCorrection;
+
+impl<R, D> TableOption<R, D, ColoredConfig> for BorderSpanCorrection
+where
+ R: Records + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let shape = (records.count_rows(), records.count_columns());
+ correct_span_styles(cfg, shape);
+ }
+}
+
+fn correct_span_styles(cfg: &mut SpannedConfig, shape: (usize, usize)) {
+ for ((row, c), span) in cfg.get_column_spans() {
+ for col in c..c + span {
+ if col == 0 {
+ continue;
+ }
+
+ let is_first = col == c;
+ let has_up = row > 0 && has_left(cfg, (row - 1, col), shape);
+ let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col), shape);
+
+ let borders = cfg.get_borders();
+
+ let mut border = cfg.get_border((row, col), shape);
+
+ let has_top_border = border.left_top_corner.is_some() && border.top.is_some();
+ if has_top_border {
+ if has_up && is_first {
+ border.left_top_corner = borders.intersection;
+ } else if has_up {
+ border.left_top_corner = borders.bottom_intersection;
+ } else if is_first {
+ border.left_top_corner = borders.top_intersection;
+ } else {
+ border.left_top_corner = border.top;
+ }
+ }
+
+ let has_bottom_border = border.left_bottom_corner.is_some() && border.bottom.is_some();
+ if has_bottom_border {
+ if has_down && is_first {
+ border.left_bottom_corner = borders.intersection;
+ } else if has_down {
+ border.left_bottom_corner = borders.top_intersection;
+ } else if is_first {
+ border.left_bottom_corner = borders.bottom_intersection;
+ } else {
+ border.left_bottom_corner = border.bottom;
+ }
+ }
+
+ cfg.set_border((row, col), border);
+ }
+ }
+
+ for ((r, col), span) in cfg.get_row_spans() {
+ for row in r + 1..r + span {
+ let mut border = cfg.get_border((row, col), shape);
+ let borders = cfg.get_borders();
+
+ let has_left_border = border.left_top_corner.is_some();
+ if has_left_border {
+ let has_left = col > 0 && has_top(cfg, (row, col - 1), shape);
+ if has_left {
+ border.left_top_corner = borders.right_intersection;
+ } else {
+ border.left_top_corner = borders.vertical;
+ }
+ }
+
+ let has_right_border = border.right_top_corner.is_some();
+ if has_right_border {
+ let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1), shape);
+ if has_right {
+ border.right_top_corner = borders.left_intersection;
+ } else {
+ border.right_top_corner = borders.vertical;
+ }
+ }
+
+ cfg.set_border((row, col), border);
+ }
+ }
+
+ let cells = iter_totaly_spanned_cells(cfg, shape).collect::<Vec<_>>();
+ for (row, col) in cells {
+ if row == 0 {
+ continue;
+ }
+
+ let mut border = cfg.get_border((row, col), shape);
+ let borders = cfg.get_borders();
+
+ let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1), shape);
+ let has_up = has_left(cfg, (row - 1, col), shape);
+ if has_up && !has_right {
+ border.right_top_corner = borders.right_intersection;
+ }
+
+ let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col), shape);
+ if has_down {
+ border.left_bottom_corner = borders.top_intersection;
+ }
+
+ cfg.set_border((row, col), border);
+ }
+}
+
+fn has_left(cfg: &SpannedConfig, pos: Position, shape: (usize, usize)) -> bool {
+ if cfg.is_cell_covered_by_both_spans(pos) || cfg.is_cell_covered_by_column_span(pos) {
+ return false;
+ }
+
+ let border = cfg.get_border(pos, shape);
+ border.left.is_some() || border.left_top_corner.is_some() || border.left_bottom_corner.is_some()
+}
+
+fn has_top(cfg: &SpannedConfig, pos: Position, shape: (usize, usize)) -> bool {
+ if cfg.is_cell_covered_by_both_spans(pos) || cfg.is_cell_covered_by_row_span(pos) {
+ return false;
+ }
+
+ let border = cfg.get_border(pos, shape);
+ border.top.is_some() || border.left_top_corner.is_some() || border.right_top_corner.is_some()
+}
+
+fn iter_totaly_spanned_cells(
+ cfg: &SpannedConfig,
+ shape: (usize, usize),
+) -> impl Iterator<Item = Position> + '_ {
+ // todo: can be optimized
+ let (count_rows, count_cols) = shape;
+ (0..count_rows).flat_map(move |row| {
+ (0..count_cols)
+ .map(move |col| (row, col))
+ .filter(move |&p| cfg.is_cell_covered_by_both_spans(p))
+ })
+}
diff --git a/vendor/tabled/src/settings/style/vertical_line.rs b/vendor/tabled/src/settings/style/vertical_line.rs
new file mode 100644
index 000000000..adbef2c82
--- /dev/null
+++ b/vendor/tabled/src/settings/style/vertical_line.rs
@@ -0,0 +1,50 @@
+#[cfg(feature = "std")]
+use crate::grid::config::{ColoredConfig, VerticalLine as VLine};
+
+use super::Line;
+
+/// A horizontal split line which can be used to set a border.
+#[cfg_attr(not(feature = "std"), allow(dead_code))]
+#[derive(Debug, Clone)]
+pub struct VerticalLine {
+ pub(crate) index: usize,
+ pub(crate) line: Line,
+}
+
+impl VerticalLine {
+ /// Creates a new horizontal split line.
+ pub const fn new(index: usize, line: Line) -> Self {
+ Self { index, line }
+ }
+
+ /// Sets a horizontal character.
+ pub const fn main(mut self, c: Option<char>) -> Self {
+ self.line.main = c;
+ self
+ }
+
+ /// Sets a vertical intersection character.
+ pub const fn intersection(mut self, c: Option<char>) -> Self {
+ self.line.intersection = c;
+ self
+ }
+
+ /// Sets a top character.
+ pub const fn top(mut self, c: Option<char>) -> Self {
+ self.line.connector1 = c;
+ self
+ }
+
+ /// Sets a bottom character.
+ pub const fn bottom(mut self, c: Option<char>) -> Self {
+ self.line.connector2 = c;
+ self
+ }
+}
+
+#[cfg(feature = "std")]
+impl<R, D> crate::settings::TableOption<R, D, ColoredConfig> for VerticalLine {
+ fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ cfg.insert_vertical_line(self.index, VLine::from(self.line));
+ }
+}
diff --git a/vendor/tabled/src/settings/table_option.rs b/vendor/tabled/src/settings/table_option.rs
new file mode 100644
index 000000000..e20aa4480
--- /dev/null
+++ b/vendor/tabled/src/settings/table_option.rs
@@ -0,0 +1,30 @@
+/// A trait which is responsible for configuration of a [`Table`].
+///
+/// [`Table`]: crate::Table
+pub trait TableOption<R, D, C> {
+ /// The function allows modification of records and a grid configuration.
+ fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D);
+}
+
+impl<T, R, D, C> TableOption<R, D, C> for &[T]
+where
+ for<'a> &'a T: TableOption<R, D, C>,
+{
+ fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D) {
+ for opt in self {
+ opt.change(records, cfg, dimension)
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl<T, R, D, C> TableOption<R, D, C> for Vec<T>
+where
+ T: TableOption<R, D, C>,
+{
+ fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D) {
+ for opt in self {
+ opt.change(records, cfg, dimension)
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/themes/colorization.rs b/vendor/tabled/src/settings/themes/colorization.rs
new file mode 100644
index 000000000..41c721330
--- /dev/null
+++ b/vendor/tabled/src/settings/themes/colorization.rs
@@ -0,0 +1,388 @@
+use papergrid::{
+ color::AnsiColor,
+ config::{Entity, Sides},
+};
+
+use crate::{
+ grid::{
+ config::ColoredConfig,
+ records::{ExactRecords, Records},
+ },
+ settings::{object::Object, Color, TableOption},
+};
+
+/// [`Colorization`] sets a color for the whole table data (so it's not include the borders).
+///
+/// You can colorize borders in a different round using [`BorderColor`] or [`RawStyle`]
+///
+/// # Examples
+///
+/// ```
+/// use std::iter::FromIterator;
+///
+/// use tabled::builder::Builder;
+/// use tabled::settings::{style::BorderColor, themes::Colorization, Color, Style};
+///
+/// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]];
+///
+/// let color1 = Color::FG_BLACK | Color::BG_WHITE;
+/// let color2 = Color::BG_BLACK | Color::FG_WHITE;
+/// let color3 = Color::FG_RED | Color::BG_RED;
+///
+/// let mut table = Builder::from_iter(data).build();
+/// table
+/// .with(Colorization::chess(color1, color2))
+/// .with(Style::modern())
+/// .with(BorderColor::filled(color3));
+///
+/// println!("{table}");
+/// ```
+///
+/// [`RawStyle`]: crate::settings::style::RawStyle
+/// [`BorderColor`]: crate::settings::style::BorderColor
+#[derive(Debug, Clone)]
+pub struct Colorization {
+ pattern: ColorizationPattern,
+ colors: Vec<Color>,
+}
+
+#[derive(Debug, Clone)]
+enum ColorizationPattern {
+ Column,
+ Row,
+ ByRow,
+ ByColumn,
+ Chess,
+}
+
+impl Colorization {
+ /// Creates a [`Colorization`] with a chess pattern.
+ ///
+ /// ```
+ /// use std::iter::FromIterator;
+ ///
+ /// use tabled::builder::Builder;
+ /// use tabled::settings::{themes::Colorization, Color, Style};
+ ///
+ /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]];
+ ///
+ /// let color1 = Color::FG_BLACK | Color::BG_WHITE;
+ /// let color2 = Color::BG_BLACK | Color::FG_WHITE;
+ ///
+ /// let mut table = Builder::from_iter(data).build();
+ /// table
+ /// .with(Colorization::chess(color1, color2))
+ /// .with(Style::empty());
+ ///
+ /// println!("{table}");
+ /// ```
+ pub fn chess(white: Color, black: Color) -> Self {
+ Self::new(vec![white, black], ColorizationPattern::Chess)
+ }
+
+ /// Creates a [`Colorization`] with a target [`Object`].
+ ///
+ /// ```
+ /// use std::iter::FromIterator;
+ ///
+ /// use tabled::builder::Builder;
+ /// use tabled::settings::object::Rows;
+ /// use tabled::settings::{themes::Colorization, Color, Style};
+ ///
+ /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]];
+ ///
+ /// let color1 = Color::FG_BLACK | Color::BG_WHITE;
+ /// let color2 = Color::BG_BLACK | Color::FG_WHITE;
+ ///
+ /// let mut table = Builder::from_iter(data).build();
+ /// table
+ /// .with(Colorization::exact([color1, color2], Rows::first()))
+ /// .with(Style::empty());
+ ///
+ /// println!("{table}");
+ /// ```
+ pub fn exact<I, O>(colors: I, target: O) -> ExactColorization<O>
+ where
+ I: IntoIterator,
+ I::Item: Into<Color>,
+ {
+ let colors = colors.into_iter().map(Into::into).collect();
+ ExactColorization::new(colors, target)
+ }
+
+ /// Creates a [`Colorization`] with a pattern which changes row by row.
+ ///
+ /// ```
+ /// use std::iter::FromIterator;
+ ///
+ /// use tabled::builder::Builder;
+ /// use tabled::settings::object::Rows;
+ /// use tabled::settings::{themes::Colorization, Color, Style};
+ ///
+ /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]];
+ ///
+ /// let color1 = Color::FG_BLACK | Color::BG_WHITE;
+ /// let color2 = Color::BG_BLACK | Color::FG_WHITE;
+ ///
+ /// let mut table = Builder::from_iter(data).build();
+ /// table
+ /// .with(Colorization::rows([color1, color2]))
+ /// .with(Style::empty());
+ ///
+ /// println!("{table}");
+ /// ```
+ pub fn rows<I>(colors: I) -> Self
+ where
+ I: IntoIterator,
+ I::Item: Into<Color>,
+ {
+ Self::new(colors, ColorizationPattern::Row)
+ }
+
+ /// Creates a [`Colorization`] with a pattern which changes column by column.
+ ///
+ /// ```
+ /// use std::iter::FromIterator;
+ ///
+ /// use tabled::builder::Builder;
+ /// use tabled::settings::object::Rows;
+ /// use tabled::settings::{themes::Colorization, Color, Style};
+ ///
+ /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]];
+ ///
+ /// let color1 = Color::FG_BLACK | Color::BG_WHITE;
+ /// let color2 = Color::BG_BLACK | Color::FG_WHITE;
+ ///
+ /// let mut table = Builder::from_iter(data).build();
+ /// table
+ /// .with(Colorization::columns([color1, color2]))
+ /// .with(Style::empty());
+ ///
+ /// println!("{table}");
+ /// ```
+ pub fn columns<I>(colors: I) -> Self
+ where
+ I: IntoIterator,
+ I::Item: Into<Color>,
+ {
+ Self::new(colors, ColorizationPattern::Column)
+ }
+
+ /// Creates a [`Colorization`] with a pattern which peaks cells one by one iterating over rows.
+ ///
+ /// ```
+ /// use std::iter::FromIterator;
+ ///
+ /// use tabled::builder::Builder;
+ /// use tabled::settings::object::Rows;
+ /// use tabled::settings::{themes::Colorization, Color, Style};
+ ///
+ /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]];
+ ///
+ /// let color1 = Color::FG_BLACK | Color::BG_WHITE;
+ /// let color2 = Color::BG_BLACK | Color::FG_WHITE;
+ ///
+ /// let mut table = Builder::from_iter(data).build();
+ /// table
+ /// .with(Colorization::by_row([color1, color2]))
+ /// .with(Style::empty());
+ ///
+ /// println!("{table}");
+ /// ```
+ pub fn by_row<I>(colors: I) -> Self
+ where
+ I: IntoIterator,
+ I::Item: Into<Color>,
+ {
+ Self::new(colors, ColorizationPattern::ByRow)
+ }
+
+ /// Creates a [`Colorization`] with a pattern which peaks cells one by one iterating over columns.
+ ///
+ /// ```
+ /// use std::iter::FromIterator;
+ ///
+ /// use tabled::builder::Builder;
+ /// use tabled::settings::object::Rows;
+ /// use tabled::settings::{themes::Colorization, Color, Style};
+ ///
+ /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]];
+ ///
+ /// let color1 = Color::FG_BLACK | Color::BG_WHITE;
+ /// let color2 = Color::BG_BLACK | Color::FG_WHITE;
+ ///
+ /// let mut table = Builder::from_iter(data).build();
+ /// table
+ /// .with(Colorization::by_column([color1, color2]))
+ /// .with(Style::empty());
+ ///
+ /// println!("{table}");
+ /// ```
+ pub fn by_column<I>(colors: I) -> Self
+ where
+ I: IntoIterator,
+ I::Item: Into<Color>,
+ {
+ Self::new(colors, ColorizationPattern::ByColumn)
+ }
+
+ fn new<I>(colors: I, pattern: ColorizationPattern) -> Self
+ where
+ I: IntoIterator,
+ I::Item: Into<Color>,
+ {
+ let colors = colors.into_iter().map(Into::into).collect();
+ Self { colors, pattern }
+ }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for Colorization
+where
+ R: Records + ExactRecords,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ if self.colors.is_empty() {
+ return;
+ }
+
+ let count_columns = records.count_columns();
+ let count_rows = records.count_rows();
+
+ match self.pattern {
+ ColorizationPattern::Column => colorize_columns(&self.colors, count_columns, cfg),
+ ColorizationPattern::Row => colorize_rows(&self.colors, count_rows, cfg),
+ ColorizationPattern::ByRow => {
+ colorize_by_row(&self.colors, count_rows, count_columns, cfg)
+ }
+ ColorizationPattern::ByColumn => {
+ colorize_by_column(&self.colors, count_rows, count_columns, cfg)
+ }
+ ColorizationPattern::Chess => {
+ colorize_diogonals(&self.colors, count_rows, count_columns, cfg)
+ }
+ }
+ }
+}
+
+fn colorize_columns(colors: &[Color], count_columns: usize, cfg: &mut ColoredConfig) {
+ for (col, color) in (0..count_columns).zip(colors.iter().cycle()) {
+ colorize_entity(color, Entity::Column(col), cfg);
+ }
+}
+
+fn colorize_rows(colors: &[Color], count_rows: usize, cfg: &mut ColoredConfig) {
+ for (row, color) in (0..count_rows).zip(colors.iter().cycle()) {
+ colorize_entity(color, Entity::Row(row), cfg);
+ }
+}
+
+fn colorize_by_row(
+ colors: &[Color],
+ count_rows: usize,
+ count_columns: usize,
+ cfg: &mut ColoredConfig,
+) {
+ let mut color_peek = colors.iter().cycle();
+ for row in 0..count_rows {
+ for col in 0..count_columns {
+ let color = color_peek.next().unwrap();
+ colorize_entity(color, Entity::Cell(row, col), cfg);
+ }
+ }
+}
+
+fn colorize_by_column(
+ colors: &[Color],
+ count_rows: usize,
+ count_columns: usize,
+ cfg: &mut ColoredConfig,
+) {
+ let mut color_peek = colors.iter().cycle();
+ for col in 0..count_columns {
+ for row in 0..count_rows {
+ let color = color_peek.next().unwrap();
+ colorize_entity(color, Entity::Cell(row, col), cfg);
+ }
+ }
+}
+
+fn colorize_diogonals(
+ colors: &[Color],
+ count_rows: usize,
+ count_columns: usize,
+ cfg: &mut ColoredConfig,
+) {
+ let mut color_peek = colors.iter().cycle();
+ for mut row in 0..count_rows {
+ let color = color_peek.next().unwrap();
+ for col in 0..count_columns {
+ colorize_entity(color, Entity::Cell(row, col), cfg);
+
+ row += 1;
+ if row == count_rows {
+ break;
+ }
+ }
+ }
+
+ let _ = color_peek.next().unwrap();
+
+ for mut col in 1..count_columns {
+ let color = color_peek.next().unwrap();
+ for row in 0..count_rows {
+ colorize_entity(color, Entity::Cell(row, col), cfg);
+
+ col += 1;
+ if col == count_columns {
+ break;
+ }
+ }
+ }
+}
+
+fn colorize_entity(color: &Color, pos: Entity, cfg: &mut ColoredConfig) {
+ let ansi_color = AnsiColor::from(color.clone());
+ let _ = cfg.set_color(pos, ansi_color.clone());
+ cfg.set_justification_color(pos, Some(ansi_color.clone()));
+ cfg.set_padding_color(
+ pos,
+ Sides::new(
+ Some(ansi_color.clone()),
+ Some(ansi_color.clone()),
+ Some(ansi_color.clone()),
+ Some(ansi_color),
+ ),
+ );
+}
+
+/// A colorization of a target [`Object`].
+///
+/// Can be created by [`Colorization::exact`].
+#[derive(Debug, Clone)]
+pub struct ExactColorization<O> {
+ colors: Vec<Color>,
+ target: O,
+}
+
+impl<O> ExactColorization<O> {
+ fn new(colors: Vec<Color>, target: O) -> Self {
+ Self { colors, target }
+ }
+}
+
+impl<R, D, O> TableOption<R, D, ColoredConfig> for ExactColorization<O>
+where
+ O: Object<R>,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ if self.colors.is_empty() {
+ return;
+ }
+
+ let mut color_peek = self.colors.iter().cycle();
+ for pos in self.target.cells(records) {
+ let color = color_peek.next().unwrap();
+ colorize_entity(color, pos, cfg);
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/themes/column_names.rs b/vendor/tabled/src/settings/themes/column_names.rs
new file mode 100644
index 000000000..02848166d
--- /dev/null
+++ b/vendor/tabled/src/settings/themes/column_names.rs
@@ -0,0 +1,355 @@
+use crate::{
+ grid::{
+ config::{AlignmentHorizontal, ColoredConfig},
+ dimension::{CompleteDimensionVecRecords, Dimension, Estimate},
+ records::{
+ vec_records::{CellInfo, VecRecords},
+ ExactRecords, PeekableRecords, Records, Resizable,
+ },
+ util::string::string_width,
+ },
+ settings::{
+ style::{BorderText, Offset},
+ Color, TableOption,
+ },
+};
+
+/// [`ColumnNames`] sets strings on horizontal lines for the columns.
+///
+/// Notice that using a [`Default`] would reuse a names from the first row.
+///
+/// # Examples
+///
+/// ```
+/// use std::iter::FromIterator;
+/// use tabled::{Table, settings::themes::ColumnNames};
+///
+/// let data = vec![
+/// vec!["Hello", "World"],
+/// vec!["Hello", "World"],
+/// ];
+///
+/// let mut table = Table::from_iter(data);
+/// table.with(ColumnNames::new(["head1", "head2"]).set_offset(3).set_line(2));
+///
+/// assert_eq!(
+/// table.to_string(),
+/// "+--------+--------+\n\
+/// | Hello | World |\n\
+/// +--------+--------+\n\
+/// | Hello | World |\n\
+/// +---head1+---head2+"
+/// );
+/// ```
+///
+/// [`Default`] usage.
+///
+/// ```
+/// use std::iter::FromIterator;
+/// use tabled::{Table, settings::themes::ColumnNames};
+///
+/// let data = vec![
+/// vec!["Hello", "World"],
+/// vec!["Hello", "World"],
+/// ];
+///
+/// let mut table = Table::from_iter(data);
+/// table.with(ColumnNames::default());
+///
+/// assert_eq!(
+/// table.to_string(),
+/// "+Hello--+World--+\n\
+/// | Hello | World |\n\
+/// +-------+-------+"
+/// );
+/// ```
+#[derive(Debug, Clone)]
+pub struct ColumnNames {
+ names: Option<Vec<String>>,
+ colors: Vec<Option<Color>>,
+ offset: usize,
+ line: usize,
+ alignment: AlignmentHorizontal,
+}
+
+impl Default for ColumnNames {
+ fn default() -> Self {
+ Self {
+ names: Default::default(),
+ colors: Default::default(),
+ offset: Default::default(),
+ line: Default::default(),
+ alignment: AlignmentHorizontal::Left,
+ }
+ }
+}
+
+impl ColumnNames {
+ /// Creates a [`ColumnNames`] with a given names.
+ ///
+ /// Using a [`Default`] would reuse a names from the first row.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::iter::FromIterator;
+ /// use tabled::{Table, settings::themes::ColumnNames};
+ ///
+ /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
+ /// table.with(ColumnNames::new(["head1", "head2"]));
+ ///
+ /// assert_eq!(
+ /// table.to_string(),
+ /// "+head1--+head2--+\n\
+ /// | Hello | World |\n\
+ /// +-------+-------+"
+ /// );
+ /// ```
+ pub fn new<I>(names: I) -> Self
+ where
+ I: IntoIterator,
+ I::Item: Into<String>,
+ {
+ let names = names.into_iter().map(Into::into).collect::<Vec<_>>();
+ Self {
+ names: Some(names),
+ colors: Vec::new(),
+ offset: 0,
+ line: 0,
+ alignment: AlignmentHorizontal::Left,
+ }
+ }
+
+ /// Set color for the column names.
+ ///
+ /// By default there's no colors.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::iter::FromIterator;
+ /// use tabled::Table;
+ /// use tabled::settings::{Color, themes::ColumnNames};
+ ///
+ /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
+ /// table.with(ColumnNames::new(["head1", "head2"]).set_colors([Color::FG_RED]));
+ ///
+ /// assert_eq!(
+ /// table.to_string(),
+ /// "+\u{1b}[31mh\u{1b}[39m\u{1b}[31me\u{1b}[39m\u{1b}[31ma\u{1b}[39m\u{1b}[31md\u{1b}[39m\u{1b}[31m1\u{1b}[39m--+head2--+\n\
+ /// | Hello | World |\n\
+ /// +-------+-------+"
+ /// );
+ /// ```
+ pub fn set_colors<I>(self, colors: I) -> Self
+ where
+ I: IntoIterator,
+ I::Item: Into<Option<Color>>,
+ {
+ let colors = colors.into_iter().map(Into::into).collect::<Vec<_>>();
+ Self {
+ names: self.names,
+ offset: self.offset,
+ line: self.line,
+ alignment: self.alignment,
+ colors,
+ }
+ }
+
+ /// Set a left offset after which the names will be used.
+ ///
+ /// By default there's no offset.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::iter::FromIterator;
+ /// use tabled::{Table, settings::themes::ColumnNames};
+ ///
+ /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
+ /// table.with(ColumnNames::new(["head1", "head2"]).set_offset(1));
+ ///
+ /// assert_eq!(
+ /// table.to_string(),
+ /// "+-head1-+-head2-+\n\
+ /// | Hello | World |\n\
+ /// +-------+-------+"
+ /// );
+ /// ```
+ pub fn set_offset(self, i: usize) -> Self {
+ Self {
+ names: self.names,
+ colors: self.colors,
+ line: self.line,
+ alignment: self.alignment,
+ offset: i,
+ }
+ }
+
+ /// Set a horizontal line the names will be applied to.
+ ///
+ /// The default value is 0 (the top horizontal line).
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::iter::FromIterator;
+ /// use tabled::{Table, settings::themes::ColumnNames};
+ ///
+ /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
+ /// table.with(ColumnNames::new(["head1", "head2"]).set_line(1));
+ ///
+ /// assert_eq!(
+ /// table.to_string(),
+ /// "+-------+-------+\n\
+ /// | Hello | World |\n\
+ /// +head1--+head2--+"
+ /// );
+ /// ```
+ pub fn set_line(self, i: usize) -> Self {
+ Self {
+ names: self.names,
+ colors: self.colors,
+ offset: self.offset,
+ alignment: self.alignment,
+ line: i,
+ }
+ }
+
+ /// Set an alignment for the names.
+ ///
+ /// By default it's left aligned.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::iter::FromIterator;
+ /// use tabled::{
+ /// Table,
+ /// settings::themes::ColumnNames,
+ /// grid::config::AlignmentHorizontal,
+ /// };
+ ///
+ /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
+ /// table.with(ColumnNames::new(["head1", "head2"]).set_alignment(AlignmentHorizontal::Right));
+ ///
+ /// assert_eq!(
+ /// table.to_string(),
+ /// "+--head1+--head2+\n\
+ /// | Hello | World |\n\
+ /// +-------+-------+"
+ /// );
+ /// ```
+ pub fn set_alignment(self, alignment: AlignmentHorizontal) -> Self {
+ Self {
+ names: self.names,
+ colors: self.colors,
+ offset: self.offset,
+ line: self.line,
+ alignment,
+ }
+ }
+}
+
+impl TableOption<VecRecords<CellInfo<String>>, CompleteDimensionVecRecords<'static>, ColoredConfig>
+ for ColumnNames
+{
+ fn change(
+ self,
+ records: &mut VecRecords<CellInfo<String>>,
+ cfg: &mut ColoredConfig,
+ dims: &mut CompleteDimensionVecRecords<'static>,
+ ) {
+ let names = match self.names {
+ Some(names) => names,
+ None => {
+ if records.count_rows() == 0 || records.count_columns() == 0 {
+ return;
+ }
+
+ let names = (0..records.count_columns())
+ .map(|column| records.get_text((0, column)))
+ .map(ToString::to_string)
+ .collect::<Vec<_>>();
+
+ records.remove_row(0);
+
+ names
+ }
+ };
+
+ let names = names.iter().map(|name| name.lines().next().unwrap_or(""));
+
+ dims.estimate(&*records, cfg);
+
+ let mut widths = (0..records.count_columns())
+ .map(|column| dims.get_width(column))
+ .collect::<Vec<_>>();
+
+ let names = names.take(widths.len());
+
+ let offset = self.offset;
+ widths
+ .iter_mut()
+ .zip(names.clone())
+ .for_each(|(width, text)| {
+ let name_width = string_width(text) + offset;
+ *width = std::cmp::max(name_width, *width);
+ });
+
+ let _ = dims.set_widths(widths.clone());
+
+ let mut total_width = 0;
+ for (i, (width, name)) in widths.iter().zip(names).enumerate() {
+ let color = get_color(&self.colors, i);
+ let offset = total_width + 1;
+ let btext = get_border_text(
+ name,
+ offset,
+ *width,
+ self.alignment,
+ self.offset,
+ self.line,
+ color,
+ );
+ btext.change(records, cfg, dims);
+
+ total_width += width + 1;
+ }
+ }
+}
+
+fn get_border_text(
+ text: &str,
+ offset: usize,
+ available: usize,
+ alignment: AlignmentHorizontal,
+ alignment_offset: usize,
+ line: usize,
+ color: Option<&Color>,
+) -> BorderText<usize> {
+ let name = text.to_string();
+ let left_indent = get_indent(text, alignment, alignment_offset, available);
+ let left_offset = Offset::Begin(offset + left_indent);
+ let mut btext = BorderText::new(name).horizontal(line).offset(left_offset);
+ if let Some(color) = color {
+ btext = btext.color(color.clone());
+ }
+
+ btext
+}
+
+fn get_color(colors: &[Option<Color>], i: usize) -> Option<&Color> {
+ colors.get(i).and_then(|color| match color {
+ Some(color) => Some(color),
+ None => None,
+ })
+}
+
+fn get_indent(text: &str, align: AlignmentHorizontal, offset: usize, available: usize) -> usize {
+ match align {
+ AlignmentHorizontal::Left => offset,
+ AlignmentHorizontal::Right => available - string_width(text) - offset,
+ AlignmentHorizontal::Center => (available - string_width(text)) / 2,
+ }
+}
diff --git a/vendor/tabled/src/settings/themes/mod.rs b/vendor/tabled/src/settings/themes/mod.rs
new file mode 100644
index 000000000..75cf458cc
--- /dev/null
+++ b/vendor/tabled/src/settings/themes/mod.rs
@@ -0,0 +1,9 @@
+//! The module contains a varieity of configurations of table, which often
+//! changes not a single setting.
+//! As such they are making relatively big changes to the configuration.
+
+mod colorization;
+mod column_names;
+
+pub use colorization::{Colorization, ExactColorization};
+pub use column_names::ColumnNames;
diff --git a/vendor/tabled/src/settings/width/justify.rs b/vendor/tabled/src/settings/width/justify.rs
new file mode 100644
index 000000000..03b2afe0d
--- /dev/null
+++ b/vendor/tabled/src/settings/width/justify.rs
@@ -0,0 +1,96 @@
+//! This module contains [`Justify`] structure, used to set an exact width to each column.
+
+use crate::{
+ grid::config::ColoredConfig,
+ grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut},
+ settings::{
+ measurement::{Max, Measurement, Min},
+ CellOption, TableOption, Width,
+ },
+};
+
+/// Justify sets all columns widths to the set value.
+///
+/// Be aware that it doesn't consider padding.
+/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0.
+///
+/// ## Examples
+///
+/// ```
+/// use tabled::{Table, settings::{Width, Style, object::Segment, Padding, Modify}};
+///
+/// let data = ["Hello", "World", "!"];
+///
+/// let table = Table::new(&data)
+/// .with(Style::markdown())
+/// .with(Modify::new(Segment::all()).with(Padding::zero()))
+/// .with(Width::justify(3));
+/// ```
+///
+/// [`Max`] usage to justify by a max column width.
+///
+/// ```
+/// use tabled::{Table, settings::{width::Justify, Style}};
+///
+/// let data = ["Hello", "World", "!"];
+///
+/// let table = Table::new(&data)
+/// .with(Style::markdown())
+/// .with(Justify::max());
+/// ```
+///
+/// [`Padding`]: crate::settings::Padding
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Justify<W> {
+ width: W,
+}
+
+impl<W> Justify<W>
+where
+ W: Measurement<Width>,
+{
+ /// Creates a new [`Justify`] instance.
+ ///
+ /// Be aware that [`Padding`] is not considered when comparing the width.
+ ///
+ /// [`Padding`]: crate::settings::Padding
+ pub fn new(width: W) -> Self {
+ Self { width }
+ }
+}
+
+impl Justify<Max> {
+ /// Creates a new Justify instance with a Max width used as a value.
+ pub fn max() -> Self {
+ Self { width: Max }
+ }
+}
+
+impl Justify<Min> {
+ /// Creates a new Justify instance with a Min width used as a value.
+ pub fn min() -> Self {
+ Self { width: Min }
+ }
+}
+
+impl<R, D, W> TableOption<R, D, ColoredConfig> for Justify<W>
+where
+ W: Measurement<Width>,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+ for<'a> &'a R: Records,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+ let width = self.width.measure(&*records, cfg);
+
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ for row in 0..count_rows {
+ for col in 0..count_columns {
+ let pos = (row, col).into();
+ CellOption::change(Width::increase(width), records, cfg, pos);
+ CellOption::change(Width::truncate(width), records, cfg, pos);
+ }
+ }
+ }
+}
diff --git a/vendor/tabled/src/settings/width/min_width.rs b/vendor/tabled/src/settings/width/min_width.rs
new file mode 100644
index 000000000..afb9ae302
--- /dev/null
+++ b/vendor/tabled/src/settings/width/min_width.rs
@@ -0,0 +1,205 @@
+//! This module contains [`MinWidth`] structure, used to increase width of a [`Table`]s or a cell on a [`Table`].
+//!
+//! [`Table`]: crate::Table
+
+use std::marker::PhantomData;
+
+use crate::{
+ grid::config::ColoredConfig,
+ grid::config::Entity,
+ grid::dimension::CompleteDimensionVecRecords,
+ grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut},
+ grid::util::string::{get_lines, string_width_multiline},
+ settings::{
+ measurement::Measurement,
+ peaker::{Peaker, PriorityNone},
+ CellOption, TableOption, Width,
+ },
+};
+
+use super::util::get_table_widths_with_total;
+
+/// [`MinWidth`] changes a content in case if it's length is lower then the boundary.
+///
+/// It can be applied to a whole table.
+///
+/// It does nothing in case if the content's length is bigger then the boundary.
+///
+/// Be aware that further changes of the table may cause the width being not set.
+/// For example applying [`Padding`] after applying [`MinWidth`] will make the former have no affect.
+/// (You should use [`Padding`] first).
+///
+/// Be aware that it doesn't consider padding.
+/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0.
+///
+/// ## Examples
+///
+/// Cell change
+///
+/// ```
+/// use tabled::{Table, settings::{object::Segment, Width, Style, Modify}};
+///
+/// let data = ["Hello", "World", "!"];
+///
+/// let table = Table::new(&data)
+/// .with(Style::markdown())
+/// .with(Modify::new(Segment::all()).with(Width::increase(10)));
+/// ```
+/// Table change
+///
+/// ```
+/// use tabled::{Table, settings::Width};
+///
+/// let table = Table::new(&["Hello World!"]).with(Width::increase(5));
+/// ```
+///
+/// [`Padding`]: crate::settings::Padding
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct MinWidth<W = usize, P = PriorityNone> {
+ width: W,
+ fill: char,
+ _priority: PhantomData<P>,
+}
+
+impl<W> MinWidth<W>
+where
+ W: Measurement<Width>,
+{
+ /// Creates a new instance of [`MinWidth`].
+ pub fn new(width: W) -> Self {
+ Self {
+ width,
+ fill: ' ',
+ _priority: PhantomData,
+ }
+ }
+}
+
+impl<W, P> MinWidth<W, P> {
+ /// Set's a fill character which will be used to fill the space
+ /// when increasing the length of the string to the set boundary.
+ ///
+ /// Used only if chaning cells.
+ pub fn fill_with(mut self, c: char) -> Self {
+ self.fill = c;
+ self
+ }
+
+ /// Priority defines the logic by which a increase of width will be applied when is done for the whole table.
+ ///
+ /// - [`PriorityNone`] which inc the columns one after another.
+ /// - [`PriorityMax`] inc the biggest columns first.
+ /// - [`PriorityMin`] inc the lowest columns first.
+ ///
+ /// [`PriorityMax`]: crate::settings::peaker::PriorityMax
+ /// [`PriorityMin`]: crate::settings::peaker::PriorityMin
+ pub fn priority<PP: Peaker>(self) -> MinWidth<W, PP> {
+ MinWidth {
+ fill: self.fill,
+ width: self.width,
+ _priority: PhantomData,
+ }
+ }
+}
+
+impl<W, R> CellOption<R, ColoredConfig> for MinWidth<W>
+where
+ W: Measurement<Width>,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+ for<'a> &'a R: Records,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let width = self.width.measure(&*records, cfg);
+
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ for pos in entity.iter(count_rows, count_columns) {
+ let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns;
+ if !is_valid_pos {
+ continue;
+ }
+
+ let cell = records.get_text(pos);
+ let cell_width = string_width_multiline(cell);
+ if cell_width >= width {
+ continue;
+ }
+
+ let content = increase_width(cell, width, self.fill);
+ records.set(pos, content);
+ }
+ }
+}
+
+impl<W, P, R> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> for MinWidth<W, P>
+where
+ W: Measurement<Width>,
+ P: Peaker,
+ R: Records + ExactRecords + PeekableRecords,
+ for<'a> &'a R: Records,
+{
+ fn change(
+ self,
+ records: &mut R,
+ cfg: &mut ColoredConfig,
+ dims: &mut CompleteDimensionVecRecords<'static>,
+ ) {
+ if records.count_rows() == 0 || records.count_columns() == 0 {
+ return;
+ }
+
+ let nessary_width = self.width.measure(&*records, cfg);
+
+ let (widths, total_width) = get_table_widths_with_total(&*records, cfg);
+ if total_width >= nessary_width {
+ return;
+ }
+
+ let widths = get_increase_list(widths, nessary_width, total_width, P::create());
+ let _ = dims.set_widths(widths);
+ }
+}
+
+fn get_increase_list<F>(
+ mut widths: Vec<usize>,
+ need: usize,
+ mut current: usize,
+ mut peaker: F,
+) -> Vec<usize>
+where
+ F: Peaker,
+{
+ while need != current {
+ let col = match peaker.peak(&[], &widths) {
+ Some(col) => col,
+ None => break,
+ };
+
+ widths[col] += 1;
+ current += 1;
+ }
+
+ widths
+}
+
+fn increase_width(s: &str, width: usize, fill_with: char) -> String {
+ use crate::grid::util::string::string_width;
+ use std::{borrow::Cow, iter::repeat};
+
+ get_lines(s)
+ .map(|line| {
+ let length = string_width(&line);
+
+ if length < width {
+ let mut line = line.into_owned();
+ let remain = width - length;
+ line.extend(repeat(fill_with).take(remain));
+ Cow::Owned(line)
+ } else {
+ line
+ }
+ })
+ .collect::<Vec<_>>()
+ .join("\n")
+}
diff --git a/vendor/tabled/src/settings/width/mod.rs b/vendor/tabled/src/settings/width/mod.rs
new file mode 100644
index 000000000..c1202f70f
--- /dev/null
+++ b/vendor/tabled/src/settings/width/mod.rs
@@ -0,0 +1,163 @@
+//! This module contains object which can be used to limit a cell to a given width:
+//!
+//! - [`Truncate`] cuts a cell content to limit width.
+//! - [`Wrap`] split the content via new lines in order to fit max width.
+//! - [`Justify`] sets columns width to the same value.
+//!
+//! To set a a table width, a combination of [`Width::truncate`] or [`Width::wrap`] and [`Width::increase`] can be used.
+//!
+//! ## Example
+//!
+//! ```
+//! use tabled::{Table, settings::Width};
+//!
+//! let table = Table::new(&["Hello World!"])
+//! .with(Width::wrap(7))
+//! .with(Width::increase(7))
+//! .to_string();
+//!
+//! assert_eq!(
+//! table,
+//! concat!(
+//! "+-----+\n",
+//! "| &st |\n",
+//! "| r |\n",
+//! "+-----+\n",
+//! "| Hel |\n",
+//! "| lo |\n",
+//! "| Wor |\n",
+//! "| ld! |\n",
+//! "+-----+",
+//! )
+//! );
+//! ```
+
+mod justify;
+mod min_width;
+mod truncate;
+mod util;
+mod width_list;
+mod wrap;
+
+use crate::settings::measurement::Measurement;
+
+pub use self::{
+ justify::Justify,
+ min_width::MinWidth,
+ truncate::{SuffixLimit, Truncate},
+ width_list::WidthList,
+ wrap::Wrap,
+};
+
+/// Width allows you to set a min and max width of an object on a [`Table`]
+/// using different strategies.
+///
+/// It also allows you to set a min and max width for a whole table.
+///
+/// You can apply a min and max strategy at the same time with the same value,
+/// the value will be a total table width.
+///
+/// It is an abstract factory.
+///
+/// Beware that borders are not removed when you set a size value to very small.
+/// For example if you set size to 0 the table still be rendered but with all content removed.
+///
+/// Also be aware that it doesn't changes [`Padding`] settings nor it considers them.
+///
+/// The function is color aware if a `color` feature is on.
+///
+/// ## Examples
+///
+/// ### Cell change
+///
+/// ```
+/// use tabled::{Table, settings::{object::Segment, Width, Style, Modify}};
+///
+/// let data = ["Hello", "World", "!"];
+///
+/// let table = Table::new(&data)
+/// .with(Style::markdown())
+/// .with(Modify::new(Segment::all()).with(Width::truncate(3).suffix("...")));
+/// ```
+///
+/// ### Table change
+///
+/// ```
+/// use tabled::{Table, settings::Width};
+///
+/// let table = Table::new(&["Hello World!"]).with(Width::wrap(5));
+/// ```
+///
+/// ### Total width
+///
+/// ```
+/// use tabled::{Table, settings::Width};
+///
+/// let table = Table::new(&["Hello World!"])
+/// .with(Width::wrap(5))
+/// .with(Width::increase(5));
+/// ```
+///
+/// [`Padding`]: crate::settings::Padding
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct Width;
+
+impl Width {
+ /// Returns a [`Wrap`] structure.
+ pub fn wrap<W: Measurement<Width>>(width: W) -> Wrap<W> {
+ Wrap::new(width)
+ }
+
+ /// Returns a [`Truncate`] structure.
+ pub fn truncate<W: Measurement<Width>>(width: W) -> Truncate<'static, W> {
+ Truncate::new(width)
+ }
+
+ /// Returns a [`MinWidth`] structure.
+ pub fn increase<W: Measurement<Width>>(width: W) -> MinWidth<W> {
+ MinWidth::new(width)
+ }
+
+ /// Returns a [`Justify`] structure.
+ pub fn justify<W: Measurement<Width>>(width: W) -> Justify<W> {
+ Justify::new(width)
+ }
+
+ /// Create [`WidthList`] to set a table width to a constant list of column widths.
+ ///
+ /// Notice if you provide a list with `.len()` smaller than `Table::count_columns` then it will have no affect.
+ ///
+ /// Also notice that you must provide values bigger than or equal to a real content width, otherwise it may panic.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use tabled::{Table, settings::Width};
+ ///
+ /// let data = vec![
+ /// ("Some\ndata", "here", "and here"),
+ /// ("Some\ndata on a next", "line", "right here"),
+ /// ];
+ ///
+ /// let table = Table::new(data)
+ /// .with(Width::list([20, 10, 12]))
+ /// .to_string();
+ ///
+ /// assert_eq!(
+ /// table,
+ /// "+--------------------+----------+------------+\n\
+ /// | &str | &str | &str |\n\
+ /// +--------------------+----------+------------+\n\
+ /// | Some | here | and here |\n\
+ /// | data | | |\n\
+ /// +--------------------+----------+------------+\n\
+ /// | Some | line | right here |\n\
+ /// | data on a next | | |\n\
+ /// +--------------------+----------+------------+"
+ /// )
+ /// ```
+ pub fn list<I: IntoIterator<Item = usize>>(rows: I) -> WidthList {
+ WidthList::new(rows.into_iter().collect())
+ }
+}
diff --git a/vendor/tabled/src/settings/width/truncate.rs b/vendor/tabled/src/settings/width/truncate.rs
new file mode 100644
index 000000000..c7336f037
--- /dev/null
+++ b/vendor/tabled/src/settings/width/truncate.rs
@@ -0,0 +1,505 @@
+//! This module contains [`Truncate`] structure, used to decrease width of a [`Table`]s or a cell on a [`Table`] by truncating the width.
+//!
+//! [`Table`]: crate::Table
+
+use std::{borrow::Cow, iter, marker::PhantomData};
+
+use crate::{
+ grid::{
+ config::{ColoredConfig, SpannedConfig},
+ dimension::CompleteDimensionVecRecords,
+ records::{EmptyRecords, ExactRecords, PeekableRecords, Records, RecordsMut},
+ util::string::{string_width, string_width_multiline},
+ },
+ settings::{
+ measurement::Measurement,
+ peaker::{Peaker, PriorityNone},
+ CellOption, TableOption, Width,
+ },
+};
+
+use super::util::{cut_str, get_table_widths, get_table_widths_with_total};
+
+/// Truncate cut the string to a given width if its length exceeds it.
+/// Otherwise keeps the content of a cell untouched.
+///
+/// The function is color aware if a `color` feature is on.
+///
+/// Be aware that it doesn't consider padding.
+/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0.
+///
+/// ## Example
+///
+/// ```
+/// use tabled::{Table, settings::{object::Segment, Width, Modify}};
+///
+/// let table = Table::new(&["Hello World!"])
+/// .with(Modify::new(Segment::all()).with(Width::truncate(3)));
+/// ```
+///
+/// [`Padding`]: crate::settings::Padding
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Truncate<'a, W = usize, P = PriorityNone> {
+ width: W,
+ suffix: Option<TruncateSuffix<'a>>,
+ multiline: bool,
+ _priority: PhantomData<P>,
+}
+#[cfg(feature = "color")]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+struct TruncateSuffix<'a> {
+ text: Cow<'a, str>,
+ limit: SuffixLimit,
+ try_color: bool,
+}
+
+#[cfg(not(feature = "color"))]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+struct TruncateSuffix<'a> {
+ text: Cow<'a, str>,
+ limit: SuffixLimit,
+}
+
+impl Default for TruncateSuffix<'_> {
+ fn default() -> Self {
+ Self {
+ text: Cow::default(),
+ limit: SuffixLimit::Cut,
+ #[cfg(feature = "color")]
+ try_color: false,
+ }
+ }
+}
+
+/// A suffix limit settings.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum SuffixLimit {
+ /// Cut the suffix.
+ Cut,
+ /// Don't show the suffix.
+ Ignore,
+ /// Use a string with n chars instead.
+ Replace(char),
+}
+
+impl<W> Truncate<'static, W>
+where
+ W: Measurement<Width>,
+{
+ /// Creates a [`Truncate`] object
+ pub fn new(width: W) -> Truncate<'static, W> {
+ Self {
+ width,
+ multiline: false,
+ suffix: None,
+ _priority: PhantomData,
+ }
+ }
+}
+
+impl<'a, W, P> Truncate<'a, W, P> {
+ /// Sets a suffix which will be appended to a resultant string.
+ ///
+ /// The suffix is used in 3 circumstances:
+ /// 1. If original string is *bigger* than the suffix.
+ /// We cut more of the original string and append the suffix.
+ /// 2. If suffix is bigger than the original string.
+ /// We cut the suffix to fit in the width by default.
+ /// But you can peak the behaviour by using [`Truncate::suffix_limit`]
+ pub fn suffix<S: Into<Cow<'a, str>>>(self, suffix: S) -> Truncate<'a, W, P> {
+ let mut suff = self.suffix.unwrap_or_default();
+ suff.text = suffix.into();
+
+ Truncate {
+ width: self.width,
+ multiline: self.multiline,
+ suffix: Some(suff),
+ _priority: PhantomData,
+ }
+ }
+
+ /// Sets a suffix limit, which is used when the suffix is too big to be used.
+ pub fn suffix_limit(self, limit: SuffixLimit) -> Truncate<'a, W, P> {
+ let mut suff = self.suffix.unwrap_or_default();
+ suff.limit = limit;
+
+ Truncate {
+ width: self.width,
+ multiline: self.multiline,
+ suffix: Some(suff),
+ _priority: PhantomData,
+ }
+ }
+
+ /// Use trancate logic per line, not as a string as a whole.
+ pub fn multiline(self) -> Truncate<'a, W, P> {
+ Truncate {
+ width: self.width,
+ multiline: true,
+ suffix: self.suffix,
+ _priority: self._priority,
+ }
+ }
+
+ #[cfg(feature = "color")]
+ /// Sets a optional logic to try to colorize a suffix.
+ pub fn suffix_try_color(self, color: bool) -> Truncate<'a, W, P> {
+ let mut suff = self.suffix.unwrap_or_default();
+ suff.try_color = color;
+
+ Truncate {
+ width: self.width,
+ multiline: self.multiline,
+ suffix: Some(suff),
+ _priority: PhantomData,
+ }
+ }
+}
+
+impl<'a, W, P> Truncate<'a, W, P> {
+ /// Priority defines the logic by which a truncate will be applied when is done for the whole table.
+ ///
+ /// - [`PriorityNone`] which cuts the columns one after another.
+ /// - [`PriorityMax`] cuts the biggest columns first.
+ /// - [`PriorityMin`] cuts the lowest columns first.
+ ///
+ /// [`PriorityMax`]: crate::settings::peaker::PriorityMax
+ /// [`PriorityMin`]: crate::settings::peaker::PriorityMin
+ pub fn priority<PP: Peaker>(self) -> Truncate<'a, W, PP> {
+ Truncate {
+ width: self.width,
+ multiline: self.multiline,
+ suffix: self.suffix,
+ _priority: PhantomData,
+ }
+ }
+}
+
+impl Truncate<'_, (), ()> {
+ /// Truncate a given string
+ pub fn truncate_text(text: &str, width: usize) -> Cow<'_, str> {
+ truncate_text(text, width, "", false)
+ }
+}
+
+impl<W, P, R> CellOption<R, ColoredConfig> for Truncate<'_, W, P>
+where
+ W: Measurement<Width>,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+ for<'a> &'a R: Records,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: papergrid::config::Entity) {
+ let available = self.width.measure(&*records, cfg);
+
+ let mut width = available;
+ let mut suffix = Cow::Borrowed("");
+
+ if let Some(x) = self.suffix.as_ref() {
+ let (cutted_suffix, rest_width) = make_suffix(x, width);
+ suffix = cutted_suffix;
+ width = rest_width;
+ };
+
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ let colorize = need_suffix_color_preservation(&self.suffix);
+
+ for pos in entity.iter(count_rows, count_columns) {
+ let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns;
+ if !is_valid_pos {
+ continue;
+ }
+
+ let text = records.get_text(pos);
+
+ let cell_width = string_width_multiline(text);
+ if available >= cell_width {
+ continue;
+ }
+
+ let text =
+ truncate_multiline(text, &suffix, width, available, colorize, self.multiline);
+
+ records.set(pos, text.into_owned());
+ }
+ }
+}
+
+fn truncate_multiline<'a>(
+ text: &'a str,
+ suffix: &'a str,
+ width: usize,
+ twidth: usize,
+ suffix_color: bool,
+ multiline: bool,
+) -> Cow<'a, str> {
+ if multiline {
+ let mut buf = String::new();
+ for (i, line) in crate::grid::util::string::get_lines(text).enumerate() {
+ if i != 0 {
+ buf.push('\n');
+ }
+
+ let line = make_text_truncated(&line, suffix, width, twidth, suffix_color);
+ buf.push_str(&line);
+ }
+
+ Cow::Owned(buf)
+ } else {
+ make_text_truncated(text, suffix, width, twidth, suffix_color)
+ }
+}
+
+fn make_text_truncated<'a>(
+ text: &'a str,
+ suffix: &'a str,
+ width: usize,
+ twidth: usize,
+ suffix_color: bool,
+) -> Cow<'a, str> {
+ if width == 0 {
+ if twidth == 0 {
+ Cow::Borrowed("")
+ } else {
+ Cow::Borrowed(suffix)
+ }
+ } else {
+ truncate_text(text, width, suffix, suffix_color)
+ }
+}
+
+fn need_suffix_color_preservation(_suffix: &Option<TruncateSuffix<'_>>) -> bool {
+ #[cfg(not(feature = "color"))]
+ {
+ false
+ }
+ #[cfg(feature = "color")]
+ {
+ _suffix.as_ref().map_or(false, |s| s.try_color)
+ }
+}
+
+fn make_suffix<'a>(suffix: &'a TruncateSuffix<'_>, width: usize) -> (Cow<'a, str>, usize) {
+ let suffix_length = string_width(&suffix.text);
+ if width > suffix_length {
+ return (Cow::Borrowed(suffix.text.as_ref()), width - suffix_length);
+ }
+
+ match suffix.limit {
+ SuffixLimit::Ignore => (Cow::Borrowed(""), width),
+ SuffixLimit::Cut => {
+ let suffix = cut_str(&suffix.text, width);
+ (suffix, 0)
+ }
+ SuffixLimit::Replace(c) => {
+ let suffix = Cow::Owned(iter::repeat(c).take(width).collect());
+ (suffix, 0)
+ }
+ }
+}
+
+impl<W, P, R> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig>
+ for Truncate<'_, W, P>
+where
+ W: Measurement<Width>,
+ P: Peaker,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+ for<'a> &'a R: Records,
+{
+ fn change(
+ self,
+ records: &mut R,
+ cfg: &mut ColoredConfig,
+ dims: &mut CompleteDimensionVecRecords<'static>,
+ ) {
+ if records.count_rows() == 0 || records.count_columns() == 0 {
+ return;
+ }
+
+ let width = self.width.measure(&*records, cfg);
+ let (widths, total) = get_table_widths_with_total(&*records, cfg);
+ if total <= width {
+ return;
+ }
+
+ let suffix = self.suffix.as_ref().map(|s| TruncateSuffix {
+ text: Cow::Borrowed(&s.text),
+ limit: s.limit,
+ #[cfg(feature = "color")]
+ try_color: s.try_color,
+ });
+
+ let priority = P::create();
+ let multiline = self.multiline;
+ let widths = truncate_total_width(
+ records, cfg, widths, total, width, priority, suffix, multiline,
+ );
+
+ let _ = dims.set_widths(widths);
+ }
+}
+
+#[allow(clippy::too_many_arguments)]
+fn truncate_total_width<P, R>(
+ records: &mut R,
+ cfg: &mut ColoredConfig,
+ mut widths: Vec<usize>,
+ total: usize,
+ width: usize,
+ priority: P,
+ suffix: Option<TruncateSuffix<'_>>,
+ multiline: bool,
+) -> Vec<usize>
+where
+ for<'a> &'a R: Records,
+ P: Peaker,
+ R: Records + PeekableRecords + ExactRecords + RecordsMut<String>,
+{
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ let min_widths = get_table_widths(EmptyRecords::new(count_rows, count_columns), cfg);
+
+ decrease_widths(&mut widths, &min_widths, total, width, priority);
+
+ let points = get_decrease_cell_list(cfg, &widths, &min_widths, (count_rows, count_columns));
+
+ for ((row, col), width) in points {
+ let mut truncate = Truncate::new(width);
+ truncate.suffix = suffix.clone();
+ truncate.multiline = multiline;
+ CellOption::change(truncate, records, cfg, (row, col).into());
+ }
+
+ widths
+}
+
+fn truncate_text<'a>(
+ text: &'a str,
+ width: usize,
+ suffix: &str,
+ _suffix_color: bool,
+) -> Cow<'a, str> {
+ let content = cut_str(text, width);
+ if suffix.is_empty() {
+ return content;
+ }
+
+ #[cfg(feature = "color")]
+ {
+ if _suffix_color {
+ if let Some(block) = ansi_str::get_blocks(text).last() {
+ if block.has_ansi() {
+ let style = block.style();
+ Cow::Owned(format!(
+ "{}{}{}{}",
+ content,
+ style.start(),
+ suffix,
+ style.end()
+ ))
+ } else {
+ let mut content = content.into_owned();
+ content.push_str(suffix);
+ Cow::Owned(content)
+ }
+ } else {
+ let mut content = content.into_owned();
+ content.push_str(suffix);
+ Cow::Owned(content)
+ }
+ } else {
+ let mut content = content.into_owned();
+ content.push_str(suffix);
+ Cow::Owned(content)
+ }
+ }
+
+ #[cfg(not(feature = "color"))]
+ {
+ let mut content = content.into_owned();
+ content.push_str(suffix);
+ Cow::Owned(content)
+ }
+}
+
+fn get_decrease_cell_list(
+ cfg: &SpannedConfig,
+ widths: &[usize],
+ min_widths: &[usize],
+ shape: (usize, usize),
+) -> Vec<((usize, usize), usize)> {
+ let mut points = Vec::new();
+ (0..shape.1).for_each(|col| {
+ (0..shape.0)
+ .filter(|&row| cfg.is_cell_visible((row, col)))
+ .for_each(|row| {
+ let (width, width_min) = match cfg.get_column_span((row, col)) {
+ Some(span) => {
+ let width = (col..col + span).map(|i| widths[i]).sum::<usize>();
+ let min_width = (col..col + span).map(|i| min_widths[i]).sum::<usize>();
+ let count_borders = count_borders(cfg, col, col + span, shape.1);
+ (width + count_borders, min_width + count_borders)
+ }
+ None => (widths[col], min_widths[col]),
+ };
+
+ if width >= width_min {
+ let padding = cfg.get_padding((row, col).into());
+ let width = width.saturating_sub(padding.left.size + padding.right.size);
+
+ points.push(((row, col), width));
+ }
+ });
+ });
+
+ points
+}
+
+fn decrease_widths<F>(
+ widths: &mut [usize],
+ min_widths: &[usize],
+ total_width: usize,
+ mut width: usize,
+ mut peeaker: F,
+) where
+ F: Peaker,
+{
+ let mut empty_list = 0;
+ for col in 0..widths.len() {
+ if widths[col] == 0 || widths[col] <= min_widths[col] {
+ empty_list += 1;
+ }
+ }
+
+ while width != total_width {
+ if empty_list == widths.len() {
+ break;
+ }
+
+ let col = match peeaker.peak(min_widths, widths) {
+ Some(col) => col,
+ None => break,
+ };
+
+ if widths[col] == 0 || widths[col] <= min_widths[col] {
+ continue;
+ }
+
+ widths[col] -= 1;
+
+ if widths[col] == 0 || widths[col] <= min_widths[col] {
+ empty_list += 1;
+ }
+
+ width += 1;
+ }
+}
+
+fn count_borders(cfg: &SpannedConfig, start: usize, end: usize, count_columns: usize) -> usize {
+ (start..end)
+ .skip(1)
+ .filter(|&i| cfg.has_vertical(i, count_columns))
+ .count()
+}
diff --git a/vendor/tabled/src/settings/width/util.rs b/vendor/tabled/src/settings/width/util.rs
new file mode 100644
index 000000000..92cc48c41
--- /dev/null
+++ b/vendor/tabled/src/settings/width/util.rs
@@ -0,0 +1,265 @@
+use std::borrow::Cow;
+
+use crate::{
+ grid::config::SpannedConfig, grid::dimension::SpannedGridDimension, grid::records::Records,
+};
+
+pub(crate) fn get_table_widths<R: Records>(records: R, cfg: &SpannedConfig) -> Vec<usize> {
+ SpannedGridDimension::width(records, cfg)
+}
+
+pub(crate) fn get_table_widths_with_total<R: Records>(
+ records: R,
+ cfg: &SpannedConfig,
+) -> (Vec<usize>, usize) {
+ let widths = SpannedGridDimension::width(records, cfg);
+ let total_width = get_table_total_width(&widths, cfg);
+ (widths, total_width)
+}
+
+fn get_table_total_width(list: &[usize], cfg: &SpannedConfig) -> usize {
+ let margin = cfg.get_margin();
+ list.iter().sum::<usize>()
+ + cfg.count_vertical(list.len())
+ + margin.left.size
+ + margin.right.size
+}
+
+/// The function cuts the string to a specific width.
+///
+/// BE AWARE: width is expected to be in bytes.
+pub(crate) fn cut_str(s: &str, width: usize) -> Cow<'_, str> {
+ #[cfg(feature = "color")]
+ {
+ const REPLACEMENT: char = '\u{FFFD}';
+
+ let stripped = ansi_str::AnsiStr::ansi_strip(s);
+ let (length, count_unknowns, _) = split_at_pos(&stripped, width);
+
+ let mut buf = ansi_str::AnsiStr::ansi_cut(s, ..length);
+ if count_unknowns > 0 {
+ let mut b = buf.into_owned();
+ b.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns));
+ buf = Cow::Owned(b);
+ }
+
+ buf
+ }
+
+ #[cfg(not(feature = "color"))]
+ {
+ cut_str_basic(s, width)
+ }
+}
+
+/// The function cuts the string to a specific width.
+///
+/// BE AWARE: width is expected to be in bytes.
+#[cfg(not(feature = "color"))]
+pub(crate) fn cut_str_basic(s: &str, width: usize) -> Cow<'_, str> {
+ const REPLACEMENT: char = '\u{FFFD}';
+
+ let (length, count_unknowns, _) = split_at_pos(s, width);
+ let buf = &s[..length];
+ if count_unknowns == 0 {
+ return Cow::Borrowed(buf);
+ }
+
+ let mut buf = buf.to_owned();
+ buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns));
+
+ Cow::Owned(buf)
+}
+
+/// The function splits a string in the position and
+/// returns a exact number of bytes before the position and in case of a split in an unicode grapheme
+/// a width of a character which was tried to be splited in.
+///
+/// BE AWARE: pos is expected to be in bytes.
+pub(crate) fn split_at_pos(s: &str, pos: usize) -> (usize, usize, usize) {
+ let mut length = 0;
+ let mut i = 0;
+ for c in s.chars() {
+ if i == pos {
+ break;
+ };
+
+ let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or_default();
+
+ // We cut the chars which takes more then 1 symbol to display,
+ // in order to archive the necessary width.
+ if i + c_width > pos {
+ let count = pos - i;
+ return (length, count, c.len_utf8());
+ }
+
+ i += c_width;
+ length += c.len_utf8();
+ }
+
+ (length, 0, 0)
+}
+
+/// Strip OSC codes from `s`. If `s` is a single OSC8 hyperlink, with no other text, then return
+/// (s_with_all_hyperlinks_removed, Some(url)). If `s` does not meet this description, then return
+/// (s_with_all_hyperlinks_removed, None). Any ANSI color sequences in `s` will be retained. See
+/// <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>
+///
+/// The function is based on Dan Davison <https://github.com/dandavison> delta <https://github.com/dandavison/delta> ansi library.
+#[cfg(feature = "color")]
+pub(crate) fn strip_osc(text: &str) -> (String, Option<String>) {
+ #[derive(Debug)]
+ enum ExtractOsc8HyperlinkState {
+ ExpectOsc8Url,
+ ExpectFirstText,
+ ExpectMoreTextOrTerminator,
+ SeenOneHyperlink,
+ WillNotReturnUrl,
+ }
+
+ use ExtractOsc8HyperlinkState::*;
+
+ let mut url = None;
+ let mut state = ExpectOsc8Url;
+ let mut buf = String::with_capacity(text.len());
+
+ for el in ansitok::parse_ansi(text) {
+ match el.kind() {
+ ansitok::ElementKind::Osc => match state {
+ ExpectOsc8Url => {
+ url = Some(&text[el.start()..el.end()]);
+ state = ExpectFirstText;
+ }
+ ExpectMoreTextOrTerminator => state = SeenOneHyperlink,
+ _ => state = WillNotReturnUrl,
+ },
+ ansitok::ElementKind::Sgr => buf.push_str(&text[el.start()..el.end()]),
+ ansitok::ElementKind::Csi => buf.push_str(&text[el.start()..el.end()]),
+ ansitok::ElementKind::Esc => {}
+ ansitok::ElementKind::Text => {
+ buf.push_str(&text[el.start()..el.end()]);
+ match state {
+ ExpectFirstText => state = ExpectMoreTextOrTerminator,
+ ExpectMoreTextOrTerminator => {}
+ _ => state = WillNotReturnUrl,
+ }
+ }
+ }
+ }
+
+ match state {
+ WillNotReturnUrl => (buf, None),
+ _ => {
+ let url = url.and_then(|s| {
+ s.strip_prefix("\x1b]8;;")
+ .and_then(|s| s.strip_suffix('\x1b'))
+ });
+ if let Some(url) = url {
+ (buf, Some(url.to_string()))
+ } else {
+ (buf, None)
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::grid::util::string::string_width;
+
+ #[cfg(feature = "color")]
+ use owo_colors::{colors::Yellow, OwoColorize};
+
+ #[test]
+ fn strip_test() {
+ assert_eq!(cut_str("123456", 0), "");
+ assert_eq!(cut_str("123456", 3), "123");
+ assert_eq!(cut_str("123456", 10), "123456");
+
+ assert_eq!(cut_str("a week ago", 4), "a we");
+
+ assert_eq!(cut_str("😳😳😳😳😳", 0), "");
+ assert_eq!(cut_str("😳😳😳😳😳", 3), "😳�");
+ assert_eq!(cut_str("😳😳😳😳😳", 4), "😳😳");
+ assert_eq!(cut_str("😳😳😳😳😳", 20), "😳😳😳😳😳");
+
+ assert_eq!(cut_str("🏳️🏳️", 0), "");
+ assert_eq!(cut_str("🏳️🏳️", 1), "🏳");
+ assert_eq!(cut_str("🏳️🏳️", 2), "🏳\u{fe0f}🏳");
+ assert_eq!(string_width("🏳️🏳️"), string_width("🏳\u{fe0f}🏳"));
+
+ assert_eq!(cut_str("🎓", 1), "�");
+ assert_eq!(cut_str("🎓", 2), "🎓");
+
+ assert_eq!(cut_str("🥿", 1), "�");
+ assert_eq!(cut_str("🥿", 2), "🥿");
+
+ assert_eq!(cut_str("🩰", 1), "�");
+ assert_eq!(cut_str("🩰", 2), "🩰");
+
+ assert_eq!(cut_str("👍🏿", 1), "�");
+ assert_eq!(cut_str("👍🏿", 2), "👍");
+ assert_eq!(cut_str("👍🏿", 3), "👍�");
+ assert_eq!(cut_str("👍🏿", 4), "👍🏿");
+
+ assert_eq!(cut_str("🇻🇬", 1), "🇻");
+ assert_eq!(cut_str("🇻🇬", 2), "🇻🇬");
+ assert_eq!(cut_str("🇻🇬", 3), "🇻🇬");
+ assert_eq!(cut_str("🇻🇬", 4), "🇻🇬");
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn strip_color_test() {
+ let numbers = "123456".red().on_bright_black().to_string();
+
+ assert_eq!(cut_str(&numbers, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m");
+ assert_eq!(
+ cut_str(&numbers, 3),
+ "\u{1b}[31;100m123\u{1b}[39m\u{1b}[49m"
+ );
+ assert_eq!(cut_str(&numbers, 10), "\u{1b}[31;100m123456\u{1b}[0m");
+
+ let emojies = "😳😳😳😳😳".red().on_bright_black().to_string();
+
+ assert_eq!(cut_str(&emojies, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m");
+ assert_eq!(
+ cut_str(&emojies, 3),
+ "\u{1b}[31;100m😳\u{1b}[39m\u{1b}[49m�"
+ );
+ assert_eq!(
+ cut_str(&emojies, 4),
+ "\u{1b}[31;100m😳😳\u{1b}[39m\u{1b}[49m"
+ );
+ assert_eq!(cut_str(&emojies, 20), "\u{1b}[31;100m😳😳😳😳😳\u{1b}[0m");
+
+ let emojies = "🏳️🏳️".red().on_bright_black().to_string();
+
+ assert_eq!(cut_str(&emojies, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m");
+ assert_eq!(cut_str(&emojies, 1), "\u{1b}[31;100m🏳\u{1b}[39m\u{1b}[49m");
+ assert_eq!(
+ cut_str(&emojies, 2),
+ "\u{1b}[31;100m🏳\u{fe0f}🏳\u{1b}[39m\u{1b}[49m"
+ );
+ assert_eq!(
+ string_width(&emojies),
+ string_width("\u{1b}[31;100m🏳\u{fe0f}🏳\u{1b}[39m\u{1b}[49m")
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "color")]
+ fn test_color_strip() {
+ let s = "Collored string"
+ .fg::<Yellow>()
+ .on_truecolor(12, 200, 100)
+ .blink()
+ .to_string();
+ assert_eq!(
+ cut_str(&s, 1),
+ "\u{1b}[5m\u{1b}[48;2;12;200;100m\u{1b}[33mC\u{1b}[25m\u{1b}[39m\u{1b}[49m"
+ )
+ }
+}
diff --git a/vendor/tabled/src/settings/width/width_list.rs b/vendor/tabled/src/settings/width/width_list.rs
new file mode 100644
index 000000000..7547b97f3
--- /dev/null
+++ b/vendor/tabled/src/settings/width/width_list.rs
@@ -0,0 +1,50 @@
+use std::iter::FromIterator;
+
+use crate::{
+ grid::dimension::CompleteDimensionVecRecords, grid::records::Records, settings::TableOption,
+};
+
+/// A structure used to set [`Table`] width via a list of columns widths.
+///
+/// [`Table`]: crate::Table
+#[derive(Debug)]
+pub struct WidthList {
+ list: Vec<usize>,
+}
+
+impl WidthList {
+ /// Creates a new object.
+ pub fn new(list: Vec<usize>) -> Self {
+ Self { list }
+ }
+}
+
+impl From<Vec<usize>> for WidthList {
+ fn from(list: Vec<usize>) -> Self {
+ Self::new(list)
+ }
+}
+
+impl FromIterator<usize> for WidthList {
+ fn from_iter<T: IntoIterator<Item = usize>>(iter: T) -> Self {
+ Self::new(iter.into_iter().collect())
+ }
+}
+
+impl<R, C> TableOption<R, CompleteDimensionVecRecords<'static>, C> for WidthList
+where
+ R: Records,
+{
+ fn change(
+ self,
+ records: &mut R,
+ _: &mut C,
+ dimension: &mut CompleteDimensionVecRecords<'static>,
+ ) {
+ if self.list.len() < records.count_columns() {
+ return;
+ }
+
+ let _ = dimension.set_widths(self.list);
+ }
+}
diff --git a/vendor/tabled/src/settings/width/wrap.rs b/vendor/tabled/src/settings/width/wrap.rs
new file mode 100644
index 000000000..96a370408
--- /dev/null
+++ b/vendor/tabled/src/settings/width/wrap.rs
@@ -0,0 +1,1468 @@
+//! This module contains [`Wrap`] structure, used to decrease width of a [`Table`]s or a cell on a [`Table`] by wrapping it's content
+//! to a new line.
+//!
+//! [`Table`]: crate::Table
+
+use std::marker::PhantomData;
+
+use crate::{
+ grid::config::ColoredConfig,
+ grid::dimension::CompleteDimensionVecRecords,
+ grid::records::{EmptyRecords, ExactRecords, PeekableRecords, Records, RecordsMut},
+ grid::{config::Entity, config::SpannedConfig, util::string::string_width_multiline},
+ settings::{
+ measurement::Measurement,
+ peaker::{Peaker, PriorityNone},
+ width::Width,
+ CellOption, TableOption,
+ },
+};
+
+use super::util::{get_table_widths, get_table_widths_with_total, split_at_pos};
+
+/// Wrap wraps a string to a new line in case it exceeds the provided max boundary.
+/// Otherwise keeps the content of a cell untouched.
+///
+/// The function is color aware if a `color` feature is on.
+///
+/// Be aware that it doesn't consider padding.
+/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0.
+///
+/// ## Example
+///
+/// ```
+/// use tabled::{Table, settings::{object::Segment, width::Width, Modify}};
+///
+/// let table = Table::new(&["Hello World!"])
+/// .with(Modify::new(Segment::all()).with(Width::wrap(3)));
+/// ```
+///
+/// [`Padding`]: crate::settings::Padding
+#[derive(Debug, Clone)]
+pub struct Wrap<W = usize, P = PriorityNone> {
+ width: W,
+ keep_words: bool,
+ _priority: PhantomData<P>,
+}
+
+impl<W> Wrap<W> {
+ /// Creates a [`Wrap`] object
+ pub fn new(width: W) -> Self
+ where
+ W: Measurement<Width>,
+ {
+ Wrap {
+ width,
+ keep_words: false,
+ _priority: PhantomData,
+ }
+ }
+}
+
+impl<W, P> Wrap<W, P> {
+ /// Priority defines the logic by which a truncate will be applied when is done for the whole table.
+ ///
+ /// - [`PriorityNone`] which cuts the columns one after another.
+ /// - [`PriorityMax`] cuts the biggest columns first.
+ /// - [`PriorityMin`] cuts the lowest columns first.
+ ///
+ /// Be aware that it doesn't consider padding.
+ /// So if you want to set a exact width you might need to use [`Padding`] to set it to 0.
+ ///
+ /// [`Padding`]: crate::settings::Padding
+ /// [`PriorityMax`]: crate::settings::peaker::PriorityMax
+ /// [`PriorityMin`]: crate::settings::peaker::PriorityMin
+ pub fn priority<PP>(self) -> Wrap<W, PP> {
+ Wrap {
+ width: self.width,
+ keep_words: self.keep_words,
+ _priority: PhantomData,
+ }
+ }
+
+ /// Set the keep words option.
+ ///
+ /// If a wrapping point will be in a word, [`Wrap`] will
+ /// preserve a word (if possible) and wrap the string before it.
+ pub fn keep_words(mut self) -> Self {
+ self.keep_words = true;
+ self
+ }
+}
+
+impl Wrap<(), ()> {
+ /// Wrap a given string
+ pub fn wrap_text(text: &str, width: usize, keeping_words: bool) -> String {
+ wrap_text(text, width, keeping_words)
+ }
+}
+
+impl<W, P, R> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> for Wrap<W, P>
+where
+ W: Measurement<Width>,
+ P: Peaker,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+ for<'a> &'a R: Records,
+{
+ fn change(
+ self,
+ records: &mut R,
+ cfg: &mut ColoredConfig,
+ dims: &mut CompleteDimensionVecRecords<'static>,
+ ) {
+ if records.count_rows() == 0 || records.count_columns() == 0 {
+ return;
+ }
+
+ let width = self.width.measure(&*records, cfg);
+ let (widths, total) = get_table_widths_with_total(&*records, cfg);
+ if width >= total {
+ return;
+ }
+
+ let priority = P::create();
+ let keep_words = self.keep_words;
+ let widths = wrap_total_width(records, cfg, widths, total, width, keep_words, priority);
+
+ let _ = dims.set_widths(widths);
+ }
+}
+
+impl<W, R> CellOption<R, ColoredConfig> for Wrap<W>
+where
+ W: Measurement<Width>,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+ for<'a> &'a R: Records,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
+ let width = self.width.measure(&*records, cfg);
+
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ for pos in entity.iter(count_rows, count_columns) {
+ let is_valid_pos = pos.0 < records.count_rows() && pos.1 < records.count_columns();
+ if !is_valid_pos {
+ continue;
+ }
+
+ let text = records.get_text(pos);
+ let cell_width = string_width_multiline(text);
+ if cell_width <= width {
+ continue;
+ }
+
+ let wrapped = wrap_text(text, width, self.keep_words);
+ records.set(pos, wrapped);
+ }
+ }
+}
+
+fn wrap_total_width<R, P>(
+ records: &mut R,
+ cfg: &mut ColoredConfig,
+ mut widths: Vec<usize>,
+ total_width: usize,
+ width: usize,
+ keep_words: bool,
+ priority: P,
+) -> Vec<usize>
+where
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+ P: Peaker,
+ for<'a> &'a R: Records,
+{
+ let shape = (records.count_rows(), records.count_columns());
+ let min_widths = get_table_widths(EmptyRecords::from(shape), cfg);
+
+ decrease_widths(&mut widths, &min_widths, total_width, width, priority);
+
+ let points = get_decrease_cell_list(cfg, &widths, &min_widths, shape);
+
+ for ((row, col), width) in points {
+ let mut wrap = Wrap::new(width);
+ wrap.keep_words = keep_words;
+ <Wrap as CellOption<_, _>>::change(wrap, records, cfg, (row, col).into());
+ }
+
+ widths
+}
+
+#[cfg(not(feature = "color"))]
+pub(crate) fn wrap_text(text: &str, width: usize, keep_words: bool) -> String {
+ if width == 0 {
+ return String::new();
+ }
+
+ if keep_words {
+ split_keeping_words(text, width, "\n")
+ } else {
+ chunks(text, width).join("\n")
+ }
+}
+
+#[cfg(feature = "color")]
+pub(crate) fn wrap_text(text: &str, width: usize, keep_words: bool) -> String {
+ use super::util::strip_osc;
+
+ if width == 0 {
+ return String::new();
+ }
+
+ let (text, url): (String, Option<String>) = strip_osc(text);
+ let (prefix, suffix) = build_link_prefix_suffix(url);
+
+ if keep_words {
+ split_keeping_words(&text, width, &prefix, &suffix)
+ } else {
+ chunks(&text, width, &prefix, &suffix).join("\n")
+ }
+}
+
+#[cfg(feature = "color")]
+fn build_link_prefix_suffix(url: Option<String>) -> (String, String) {
+ match url {
+ Some(url) => {
+ // https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
+ let osc8 = "\x1b]8;;";
+ let st = "\x1b\\";
+
+ (format!("{osc8}{url}{st}"), format!("{osc8}{st}"))
+ }
+ None => ("".to_string(), "".to_string()),
+ }
+}
+
+#[cfg(not(feature = "color"))]
+fn chunks(s: &str, width: usize) -> Vec<String> {
+ if width == 0 {
+ return Vec::new();
+ }
+
+ const REPLACEMENT: char = '\u{FFFD}';
+
+ let mut buf = String::with_capacity(width);
+ let mut list = Vec::new();
+ let mut i = 0;
+ for c in s.chars() {
+ let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or_default();
+ if i + c_width > width {
+ let count_unknowns = width - i;
+ buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns));
+ i += count_unknowns;
+ } else {
+ buf.push(c);
+ i += c_width;
+ }
+
+ if i == width {
+ list.push(buf);
+ buf = String::with_capacity(width);
+ i = 0;
+ }
+ }
+
+ if !buf.is_empty() {
+ list.push(buf);
+ }
+
+ list
+}
+
+#[cfg(feature = "color")]
+fn chunks(s: &str, width: usize, prefix: &str, suffix: &str) -> Vec<String> {
+ use std::fmt::Write;
+
+ if width == 0 {
+ return Vec::new();
+ }
+
+ let mut list = Vec::new();
+ let mut line = String::with_capacity(width);
+ let mut line_width = 0;
+
+ for b in ansi_str::get_blocks(s) {
+ let text_style = b.style();
+ let mut text_slice = b.text();
+ if text_slice.is_empty() {
+ continue;
+ }
+
+ let available_space = width - line_width;
+ if available_space == 0 {
+ list.push(line);
+ line = String::with_capacity(width);
+ line_width = 0;
+ }
+
+ line.push_str(prefix);
+ let _ = write!(&mut line, "{}", text_style.start());
+
+ while !text_slice.is_empty() {
+ let available_space = width - line_width;
+
+ let part_width = unicode_width::UnicodeWidthStr::width(text_slice);
+ if part_width <= available_space {
+ line.push_str(text_slice);
+ line_width += part_width;
+
+ if available_space == 0 {
+ let _ = write!(&mut line, "{}", text_style.end());
+ line.push_str(suffix);
+ list.push(line);
+ line = String::with_capacity(width);
+ line.push_str(prefix);
+ line_width = 0;
+ let _ = write!(&mut line, "{}", text_style.start());
+ }
+
+ break;
+ }
+
+ let (lhs, rhs, (unknowns, split_char)) = split_string_at(text_slice, available_space);
+
+ text_slice = &rhs[split_char..];
+
+ line.push_str(lhs);
+ line_width += unicode_width::UnicodeWidthStr::width(lhs);
+
+ const REPLACEMENT: char = '\u{FFFD}';
+ line.extend(std::iter::repeat(REPLACEMENT).take(unknowns));
+ line_width += unknowns;
+
+ if line_width == width {
+ let _ = write!(&mut line, "{}", text_style.end());
+ line.push_str(suffix);
+ list.push(line);
+ line = String::with_capacity(width);
+ line.push_str(prefix);
+ line_width = 0;
+ let _ = write!(&mut line, "{}", text_style.start());
+ }
+ }
+
+ if line_width > 0 {
+ let _ = write!(&mut line, "{}", text_style.end());
+ }
+ }
+
+ if line_width > 0 {
+ line.push_str(suffix);
+ list.push(line);
+ }
+
+ list
+}
+
+#[cfg(not(feature = "color"))]
+fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
+ const REPLACEMENT: char = '\u{FFFD}';
+
+ let mut lines = Vec::new();
+ let mut line = String::with_capacity(width);
+ let mut line_width = 0;
+
+ let mut is_first_word = true;
+
+ for word in s.split(' ') {
+ if !is_first_word {
+ let line_has_space = line_width < width;
+ if line_has_space {
+ line.push(' ');
+ line_width += 1;
+ is_first_word = false;
+ }
+ }
+
+ if is_first_word {
+ is_first_word = false;
+ }
+
+ let word_width = unicode_width::UnicodeWidthStr::width(word);
+
+ let line_has_space = line_width + word_width <= width;
+ if line_has_space {
+ line.push_str(word);
+ line_width += word_width;
+ continue;
+ }
+
+ if word_width <= width {
+ // the word can be fit to 'width' so we put it on new line
+
+ line.extend(std::iter::repeat(' ').take(width - line_width));
+ lines.push(line);
+
+ line = String::with_capacity(width);
+ line_width = 0;
+
+ line.push_str(word);
+ line_width += word_width;
+ is_first_word = false;
+ } else {
+ // the word is too long any way so we split it
+
+ let mut word_part = word;
+ while !word_part.is_empty() {
+ let available_space = width - line_width;
+ let (lhs, rhs, (unknowns, split_char)) =
+ split_string_at(word_part, available_space);
+
+ word_part = &rhs[split_char..];
+ line_width += unicode_width::UnicodeWidthStr::width(lhs) + unknowns;
+ is_first_word = false;
+
+ line.push_str(lhs);
+ line.extend(std::iter::repeat(REPLACEMENT).take(unknowns));
+
+ if line_width == width {
+ lines.push(line);
+ line = String::with_capacity(width);
+ line_width = 0;
+ is_first_word = true;
+ }
+ }
+ }
+ }
+
+ if line_width > 0 {
+ line.extend(std::iter::repeat(' ').take(width - line_width));
+ lines.push(line);
+ }
+
+ lines.join(sep)
+}
+
+#[cfg(feature = "color")]
+fn split_keeping_words(text: &str, width: usize, prefix: &str, suffix: &str) -> String {
+ if text.is_empty() || width == 0 {
+ return String::new();
+ }
+
+ let stripped_text = ansi_str::AnsiStr::ansi_strip(text);
+ let mut word_width = 0;
+ let mut word_chars = 0;
+ let mut blocks = parsing::Blocks::new(ansi_str::get_blocks(text));
+ let mut buf = parsing::MultilineBuffer::new(width);
+ buf.set_prefix(prefix);
+ buf.set_suffix(suffix);
+
+ for c in stripped_text.chars() {
+ match c {
+ ' ' => {
+ parsing::handle_word(&mut buf, &mut blocks, word_chars, word_width, 1);
+ word_chars = 0;
+ word_width = 0;
+ }
+ '\n' => {
+ parsing::handle_word(&mut buf, &mut blocks, word_chars, word_width, 1);
+ word_chars = 0;
+ word_width = 0;
+ }
+ _ => {
+ word_width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
+ word_chars += 1;
+ }
+ }
+ }
+
+ if word_chars > 0 {
+ parsing::handle_word(&mut buf, &mut blocks, word_chars, word_width, 0);
+ buf.finish_line(&blocks);
+ }
+
+ buf.into_string()
+}
+
+#[cfg(feature = "color")]
+mod parsing {
+ use ansi_str::{AnsiBlock, AnsiBlockIter, Style};
+ use std::fmt::Write;
+
+ pub(super) struct Blocks<'a> {
+ iter: AnsiBlockIter<'a>,
+ current: Option<RelativeBlock<'a>>,
+ }
+
+ impl<'a> Blocks<'a> {
+ pub(super) fn new(iter: AnsiBlockIter<'a>) -> Self {
+ Self {
+ iter,
+ current: None,
+ }
+ }
+
+ pub(super) fn next_block(&mut self) -> Option<RelativeBlock<'a>> {
+ self.current
+ .take()
+ .or_else(|| self.iter.next().map(RelativeBlock::new))
+ }
+ }
+
+ pub(super) struct RelativeBlock<'a> {
+ block: AnsiBlock<'a>,
+ pos: usize,
+ }
+
+ impl<'a> RelativeBlock<'a> {
+ pub(super) fn new(block: AnsiBlock<'a>) -> Self {
+ Self { block, pos: 0 }
+ }
+
+ pub(super) fn get_text(&self) -> &str {
+ &self.block.text()[self.pos..]
+ }
+
+ pub(super) fn get_origin(&self) -> &str {
+ self.block.text()
+ }
+
+ pub(super) fn get_style(&self) -> &Style {
+ self.block.style()
+ }
+ }
+
+ pub(super) struct MultilineBuffer<'a> {
+ buf: String,
+ width_last: usize,
+ width: usize,
+ prefix: &'a str,
+ suffix: &'a str,
+ }
+
+ impl<'a> MultilineBuffer<'a> {
+ pub(super) fn new(width: usize) -> Self {
+ Self {
+ buf: String::new(),
+ width_last: 0,
+ prefix: "",
+ suffix: "",
+ width,
+ }
+ }
+
+ pub(super) fn into_string(self) -> String {
+ self.buf
+ }
+
+ pub(super) fn set_suffix(&mut self, suffix: &'a str) {
+ self.suffix = suffix;
+ }
+
+ pub(super) fn set_prefix(&mut self, prefix: &'a str) {
+ self.prefix = prefix;
+ }
+
+ pub(super) fn max_width(&self) -> usize {
+ self.width
+ }
+
+ pub(super) fn available_width(&self) -> usize {
+ self.width - self.width_last
+ }
+
+ pub(super) fn fill(&mut self, c: char) -> usize {
+ debug_assert_eq!(unicode_width::UnicodeWidthChar::width(c), Some(1));
+
+ let rest_width = self.available_width();
+ for _ in 0..rest_width {
+ self.buf.push(c);
+ }
+
+ rest_width
+ }
+
+ pub(super) fn set_next_line(&mut self, blocks: &Blocks<'_>) {
+ if let Some(block) = &blocks.current {
+ let _ = self
+ .buf
+ .write_fmt(format_args!("{}", block.get_style().end()));
+ }
+
+ self.buf.push_str(self.suffix);
+
+ let _ = self.fill(' ');
+ self.buf.push('\n');
+ self.width_last = 0;
+
+ self.buf.push_str(self.prefix);
+
+ if let Some(block) = &blocks.current {
+ let _ = self
+ .buf
+ .write_fmt(format_args!("{}", block.get_style().start()));
+ }
+ }
+
+ pub(super) fn finish_line(&mut self, blocks: &Blocks<'_>) {
+ if let Some(block) = &blocks.current {
+ let _ = self
+ .buf
+ .write_fmt(format_args!("{}", block.get_style().end()));
+ }
+
+ self.buf.push_str(self.suffix);
+
+ let _ = self.fill(' ');
+ self.width_last = 0;
+ }
+
+ pub(super) fn read_chars(&mut self, block: &RelativeBlock<'_>, n: usize) -> (usize, usize) {
+ let mut count_chars = 0;
+ let mut count_bytes = 0;
+ for c in block.get_text().chars() {
+ if count_chars == n {
+ break;
+ }
+
+ count_chars += 1;
+ count_bytes += c.len_utf8();
+
+ let cwidth = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
+
+ let available_space = self.width - self.width_last;
+ if available_space == 0 {
+ let _ = self
+ .buf
+ .write_fmt(format_args!("{}", block.get_style().end()));
+ self.buf.push_str(self.suffix);
+ self.buf.push('\n');
+ self.buf.push_str(self.prefix);
+ let _ = self
+ .buf
+ .write_fmt(format_args!("{}", block.get_style().start()));
+ self.width_last = 0;
+ }
+
+ let is_enough_space = self.width_last + cwidth <= self.width;
+ if !is_enough_space {
+ // thereatically a cwidth can be 2 but buf_width is 1
+ // but it handled here too;
+
+ const REPLACEMENT: char = '\u{FFFD}';
+ let _ = self.fill(REPLACEMENT);
+ self.width_last = self.width;
+ } else {
+ self.buf.push(c);
+ self.width_last += cwidth;
+ }
+ }
+
+ (count_chars, count_bytes)
+ }
+
+ pub(super) fn read_chars_unchecked(
+ &mut self,
+ block: &RelativeBlock<'_>,
+ n: usize,
+ ) -> (usize, usize) {
+ let mut count_chars = 0;
+ let mut count_bytes = 0;
+ for c in block.get_text().chars() {
+ if count_chars == n {
+ break;
+ }
+
+ count_chars += 1;
+ count_bytes += c.len_utf8();
+
+ let cwidth = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
+ self.width_last += cwidth;
+
+ self.buf.push(c);
+ }
+
+ debug_assert!(self.width_last <= self.width);
+
+ (count_chars, count_bytes)
+ }
+ }
+
+ pub(super) fn read_chars(buf: &mut MultilineBuffer<'_>, blocks: &mut Blocks<'_>, n: usize) {
+ let mut n = n;
+ while n > 0 {
+ let is_new_block = blocks.current.is_none();
+ let mut block = blocks.next_block().expect("Must never happen");
+ if is_new_block {
+ buf.buf.push_str(buf.prefix);
+ let _ = buf
+ .buf
+ .write_fmt(format_args!("{}", block.get_style().start()));
+ }
+
+ let (read_count, read_bytes) = buf.read_chars(&block, n);
+
+ if block.pos + read_bytes == block.get_origin().len() {
+ let _ = buf
+ .buf
+ .write_fmt(format_args!("{}", block.get_style().end()));
+ } else {
+ block.pos += read_bytes;
+ blocks.current = Some(block);
+ }
+
+ n -= read_count;
+ }
+ }
+
+ pub(super) fn read_chars_unchecked(
+ buf: &mut MultilineBuffer<'_>,
+ blocks: &mut Blocks<'_>,
+ n: usize,
+ ) {
+ let mut n = n;
+ while n > 0 {
+ let is_new_block = blocks.current.is_none();
+ let mut block = blocks.next_block().expect("Must never happen");
+
+ if is_new_block {
+ buf.buf.push_str(buf.prefix);
+ let _ = buf
+ .buf
+ .write_fmt(format_args!("{}", block.get_style().start()));
+ }
+
+ let (read_count, read_bytes) = buf.read_chars_unchecked(&block, n);
+
+ if block.pos + read_bytes == block.get_origin().len() {
+ let _ = buf
+ .buf
+ .write_fmt(format_args!("{}", block.get_style().end()));
+ } else {
+ block.pos += read_bytes;
+ blocks.current = Some(block);
+ }
+
+ n -= read_count;
+ }
+ }
+
+ pub(super) fn handle_word(
+ buf: &mut MultilineBuffer<'_>,
+ blocks: &mut Blocks<'_>,
+ word_chars: usize,
+ word_width: usize,
+ additional_read: usize,
+ ) {
+ if word_chars > 0 {
+ let has_line_space = word_width <= buf.available_width();
+ let is_word_too_big = word_width > buf.max_width();
+
+ if is_word_too_big {
+ read_chars(buf, blocks, word_chars + additional_read);
+ } else if has_line_space {
+ read_chars_unchecked(buf, blocks, word_chars);
+ if additional_read > 0 {
+ read_chars(buf, blocks, additional_read);
+ }
+ } else {
+ buf.set_next_line(&*blocks);
+ read_chars_unchecked(buf, blocks, word_chars);
+ if additional_read > 0 {
+ read_chars(buf, blocks, additional_read);
+ }
+ }
+
+ return;
+ }
+
+ let has_current_line_space = additional_read <= buf.available_width();
+ if has_current_line_space {
+ read_chars_unchecked(buf, blocks, additional_read);
+ } else {
+ buf.set_next_line(&*blocks);
+ read_chars_unchecked(buf, blocks, additional_read);
+ }
+ }
+}
+
+fn split_string_at(text: &str, at: usize) -> (&str, &str, (usize, usize)) {
+ let (length, count_unknowns, split_char_size) = split_at_pos(text, at);
+ let (lhs, rhs) = text.split_at(length);
+
+ (lhs, rhs, (count_unknowns, split_char_size))
+}
+
+fn decrease_widths<F>(
+ widths: &mut [usize],
+ min_widths: &[usize],
+ total_width: usize,
+ mut width: usize,
+ mut peeaker: F,
+) where
+ F: Peaker,
+{
+ let mut empty_list = 0;
+ for col in 0..widths.len() {
+ if widths[col] == 0 || widths[col] <= min_widths[col] {
+ empty_list += 1;
+ }
+ }
+
+ while width != total_width {
+ if empty_list == widths.len() {
+ break;
+ }
+
+ let col = match peeaker.peak(min_widths, widths) {
+ Some(col) => col,
+ None => break,
+ };
+
+ if widths[col] == 0 || widths[col] <= min_widths[col] {
+ continue;
+ }
+
+ widths[col] -= 1;
+
+ if widths[col] == 0 || widths[col] <= min_widths[col] {
+ empty_list += 1;
+ }
+
+ width += 1;
+ }
+}
+
+fn get_decrease_cell_list(
+ cfg: &SpannedConfig,
+ widths: &[usize],
+ min_widths: &[usize],
+ shape: (usize, usize),
+) -> Vec<((usize, usize), usize)> {
+ let mut points = Vec::new();
+ (0..shape.1).for_each(|col| {
+ (0..shape.0)
+ .filter(|&row| cfg.is_cell_visible((row, col)))
+ .for_each(|row| {
+ let (width, width_min) = match cfg.get_column_span((row, col)) {
+ Some(span) => {
+ let width = (col..col + span).map(|i| widths[i]).sum::<usize>();
+ let min_width = (col..col + span).map(|i| min_widths[i]).sum::<usize>();
+ let count_borders = count_borders(cfg, col, col + span, shape.1);
+ (width + count_borders, min_width + count_borders)
+ }
+ None => (widths[col], min_widths[col]),
+ };
+
+ if width >= width_min {
+ let padding = cfg.get_padding((row, col).into());
+ let width = width.saturating_sub(padding.left.size + padding.right.size);
+
+ points.push(((row, col), width));
+ }
+ });
+ });
+
+ points
+}
+
+fn count_borders(cfg: &SpannedConfig, start: usize, end: usize, count_columns: usize) -> usize {
+ (start..end)
+ .skip(1)
+ .filter(|&i| cfg.has_vertical(i, count_columns))
+ .count()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn split_test() {
+ #[cfg(not(feature = "color"))]
+ let split = |text, width| chunks(text, width).join("\n");
+
+ #[cfg(feature = "color")]
+ let split = |text, width| chunks(text, width, "", "").join("\n");
+
+ assert_eq!(split("123456", 0), "");
+
+ assert_eq!(split("123456", 1), "1\n2\n3\n4\n5\n6");
+ assert_eq!(split("123456", 2), "12\n34\n56");
+ assert_eq!(split("12345", 2), "12\n34\n5");
+ assert_eq!(split("123456", 6), "123456");
+ assert_eq!(split("123456", 10), "123456");
+
+ assert_eq!(split("😳😳😳😳😳", 1), "�\n�\n�\n�\n�");
+ assert_eq!(split("😳😳😳😳😳", 2), "😳\n😳\n😳\n😳\n😳");
+ assert_eq!(split("😳😳😳😳😳", 3), "😳�\n😳�\n😳");
+ assert_eq!(split("😳😳😳😳😳", 6), "😳😳😳\n😳😳");
+ assert_eq!(split("😳😳😳😳😳", 20), "😳😳😳😳😳");
+
+ assert_eq!(split("😳123😳", 1), "�\n1\n2\n3\n�");
+ assert_eq!(split("😳12😳3", 1), "�\n1\n2\n�\n3");
+ }
+
+ #[test]
+ fn chunks_test() {
+ #[allow(clippy::redundant_closure)]
+ #[cfg(not(feature = "color"))]
+ let chunks = |text, width| chunks(text, width);
+
+ #[cfg(feature = "color")]
+ let chunks = |text, width| chunks(text, width, "", "");
+
+ assert_eq!(chunks("123456", 0), [""; 0]);
+
+ assert_eq!(chunks("123456", 1), ["1", "2", "3", "4", "5", "6"]);
+ assert_eq!(chunks("123456", 2), ["12", "34", "56"]);
+ assert_eq!(chunks("12345", 2), ["12", "34", "5"]);
+
+ assert_eq!(chunks("😳😳😳😳😳", 1), ["�", "�", "�", "�", "�"]);
+ assert_eq!(chunks("😳😳😳😳😳", 2), ["😳", "😳", "😳", "😳", "😳"]);
+ assert_eq!(chunks("😳😳😳😳😳", 3), ["😳�", "😳�", "😳"]);
+ }
+
+ #[cfg(not(feature = "color"))]
+ #[test]
+ fn split_by_line_keeping_words_test() {
+ let split_keeping_words = |text, width| split_keeping_words(text, width, "\n");
+
+ assert_eq!(split_keeping_words("123456", 1), "1\n2\n3\n4\n5\n6");
+ assert_eq!(split_keeping_words("123456", 2), "12\n34\n56");
+ assert_eq!(split_keeping_words("12345", 2), "12\n34\n5 ");
+
+ assert_eq!(split_keeping_words("😳😳😳😳😳", 1), "�\n�\n�\n�\n�");
+
+ assert_eq!(split_keeping_words("111 234 1", 4), "111 \n234 \n1 ");
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn split_by_line_keeping_words_test() {
+ #[cfg(feature = "color")]
+ let split_keeping_words = |text, width| split_keeping_words(text, width, "", "");
+
+ assert_eq!(split_keeping_words("123456", 1), "1\n2\n3\n4\n5\n6");
+ assert_eq!(split_keeping_words("123456", 2), "12\n34\n56");
+ assert_eq!(split_keeping_words("12345", 2), "12\n34\n5 ");
+
+ assert_eq!(split_keeping_words("😳😳😳😳😳", 1), "�\n�\n�\n�\n�");
+
+ assert_eq!(split_keeping_words("111 234 1", 4), "111 \n234 \n1 ");
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn split_by_line_keeping_words_color_test() {
+ #[cfg(feature = "color")]
+ let split_keeping_words = |text, width| split_keeping_words(text, width, "", "");
+
+ #[cfg(not(feature = "color"))]
+ let split_keeping_words = |text, width| split_keeping_words(text, width, "\n");
+
+ let text = "\u{1b}[36mJapanese “vacancy” button\u{1b}[0m";
+
+ assert_eq!(split_keeping_words(text, 2), "\u{1b}[36mJa\u{1b}[39m\n\u{1b}[36mpa\u{1b}[39m\n\u{1b}[36mne\u{1b}[39m\n\u{1b}[36mse\u{1b}[39m\n\u{1b}[36m “\u{1b}[39m\n\u{1b}[36mva\u{1b}[39m\n\u{1b}[36mca\u{1b}[39m\n\u{1b}[36mnc\u{1b}[39m\n\u{1b}[36my”\u{1b}[39m\n\u{1b}[36m b\u{1b}[39m\n\u{1b}[36mut\u{1b}[39m\n\u{1b}[36mto\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m ");
+ assert_eq!(split_keeping_words(text, 1), "\u{1b}[36mJ\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mp\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m\n\u{1b}[36me\u{1b}[39m\n\u{1b}[36ms\u{1b}[39m\n\u{1b}[36me\u{1b}[39m\n\u{1b}[36m \u{1b}[39m\n\u{1b}[36m“\u{1b}[39m\n\u{1b}[36mv\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mc\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m\n\u{1b}[36mc\u{1b}[39m\n\u{1b}[36my\u{1b}[39m\n\u{1b}[36m”\u{1b}[39m\n\u{1b}[36m \u{1b}[39m\n\u{1b}[36mb\u{1b}[39m\n\u{1b}[36mu\u{1b}[39m\n\u{1b}[36mt\u{1b}[39m\n\u{1b}[36mt\u{1b}[39m\n\u{1b}[36mo\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m");
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn split_by_line_keeping_words_color_2_test() {
+ use ansi_str::AnsiStr;
+
+ #[cfg(feature = "color")]
+ let split_keeping_words = |text, width| split_keeping_words(text, width, "", "");
+
+ #[cfg(not(feature = "color"))]
+ let split_keeping_words = |text, width| split_keeping_words(text, width, "\n");
+
+ let text = "\u{1b}[37mTigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia\u{1b}[0m";
+
+ assert_eq!(
+ split_keeping_words(text, 2)
+ .ansi_split("\n")
+ .collect::<Vec<_>>(),
+ [
+ "\u{1b}[37mTi\u{1b}[39m",
+ "\u{1b}[37mgr\u{1b}[39m",
+ "\u{1b}[37me \u{1b}[39m",
+ "\u{1b}[37mEc\u{1b}[39m",
+ "\u{1b}[37mua\u{1b}[39m",
+ "\u{1b}[37mdo\u{1b}[39m",
+ "\u{1b}[37mr \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37mOM\u{1b}[39m",
+ "\u{1b}[37mYA\u{1b}[39m",
+ "\u{1b}[37m A\u{1b}[39m",
+ "\u{1b}[37mnd\u{1b}[39m",
+ "\u{1b}[37min\u{1b}[39m",
+ "\u{1b}[37ma \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m38\u{1b}[39m",
+ "\u{1b}[37m24\u{1b}[39m",
+ "\u{1b}[37m90\u{1b}[39m",
+ "\u{1b}[37m99\u{1b}[39m",
+ "\u{1b}[37m99\u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37mCa\u{1b}[39m",
+ "\u{1b}[37mlc\u{1b}[39m",
+ "\u{1b}[37miu\u{1b}[39m",
+ "\u{1b}[37mm \u{1b}[39m",
+ "\u{1b}[37mca\u{1b}[39m",
+ "\u{1b}[37mrb\u{1b}[39m",
+ "\u{1b}[37mon\u{1b}[39m",
+ "\u{1b}[37mat\u{1b}[39m",
+ "\u{1b}[37me \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37mCo\u{1b}[39m",
+ "\u{1b}[37mlo\u{1b}[39m",
+ "\u{1b}[37mmb\u{1b}[39m",
+ "\u{1b}[37mia\u{1b}[39m"
+ ]
+ );
+
+ assert_eq!(
+ split_keeping_words(text, 1)
+ .ansi_split("\n")
+ .collect::<Vec<_>>(),
+ [
+ "\u{1b}[37mT\u{1b}[39m",
+ "\u{1b}[37mi\u{1b}[39m",
+ "\u{1b}[37mg\u{1b}[39m",
+ "\u{1b}[37mr\u{1b}[39m",
+ "\u{1b}[37me\u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37mE\u{1b}[39m",
+ "\u{1b}[37mc\u{1b}[39m",
+ "\u{1b}[37mu\u{1b}[39m",
+ "\u{1b}[37ma\u{1b}[39m",
+ "\u{1b}[37md\u{1b}[39m",
+ "\u{1b}[37mo\u{1b}[39m",
+ "\u{1b}[37mr\u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37mO\u{1b}[39m",
+ "\u{1b}[37mM\u{1b}[39m",
+ "\u{1b}[37mY\u{1b}[39m",
+ "\u{1b}[37mA\u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37mA\u{1b}[39m",
+ "\u{1b}[37mn\u{1b}[39m",
+ "\u{1b}[37md\u{1b}[39m",
+ "\u{1b}[37mi\u{1b}[39m",
+ "\u{1b}[37mn\u{1b}[39m",
+ "\u{1b}[37ma\u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m3\u{1b}[39m",
+ "\u{1b}[37m8\u{1b}[39m",
+ "\u{1b}[37m2\u{1b}[39m",
+ "\u{1b}[37m4\u{1b}[39m",
+ "\u{1b}[37m9\u{1b}[39m",
+ "\u{1b}[37m0\u{1b}[39m",
+ "\u{1b}[37m9\u{1b}[39m",
+ "\u{1b}[37m9\u{1b}[39m",
+ "\u{1b}[37m9\u{1b}[39m",
+ "\u{1b}[37m9\u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37mC\u{1b}[39m",
+ "\u{1b}[37ma\u{1b}[39m",
+ "\u{1b}[37ml\u{1b}[39m",
+ "\u{1b}[37mc\u{1b}[39m",
+ "\u{1b}[37mi\u{1b}[39m",
+ "\u{1b}[37mu\u{1b}[39m",
+ "\u{1b}[37mm\u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37mc\u{1b}[39m",
+ "\u{1b}[37ma\u{1b}[39m",
+ "\u{1b}[37mr\u{1b}[39m",
+ "\u{1b}[37mb\u{1b}[39m",
+ "\u{1b}[37mo\u{1b}[39m",
+ "\u{1b}[37mn\u{1b}[39m",
+ "\u{1b}[37ma\u{1b}[39m",
+ "\u{1b}[37mt\u{1b}[39m",
+ "\u{1b}[37me\u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37m \u{1b}[39m",
+ "\u{1b}[37mC\u{1b}[39m",
+ "\u{1b}[37mo\u{1b}[39m",
+ "\u{1b}[37ml\u{1b}[39m",
+ "\u{1b}[37mo\u{1b}[39m",
+ "\u{1b}[37mm\u{1b}[39m",
+ "\u{1b}[37mb\u{1b}[39m",
+ "\u{1b}[37mi\u{1b}[39m",
+ "\u{1b}[37ma\u{1b}[39m"
+ ]
+ )
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn split_by_line_keeping_words_color_3_test() {
+ let split = |text, width| split_keeping_words(text, width, "", "");
+ assert_eq!(
+ split(
+ "\u{1b}[37m🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻\u{1b}[0m",
+ 3,
+ ),
+ "\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m",
+ );
+ assert_eq!(
+ split("\u{1b}[37mthis is a long sentence\u{1b}[0m", 7),
+ "\u{1b}[37mthis is\u{1b}[39m\n\u{1b}[37m a long\u{1b}[39m\n\u{1b}[37m senten\u{1b}[39m\n\u{1b}[37mce\u{1b}[39m "
+ );
+ assert_eq!(
+ split("\u{1b}[37mHello World\u{1b}[0m", 7),
+ "\u{1b}[37mHello \u{1b}[39m \n\u{1b}[37mWorld\u{1b}[39m "
+ );
+ assert_eq!(
+ split("\u{1b}[37mHello Wo\u{1b}[37mrld\u{1b}[0m", 7),
+ "\u{1b}[37mHello \u{1b}[39m \n\u{1b}[37mWo\u{1b}[39m\u{1b}[37mrld\u{1b}[39m "
+ );
+ assert_eq!(
+ split("\u{1b}[37mHello Wo\u{1b}[37mrld\u{1b}[0m", 8),
+ "\u{1b}[37mHello \u{1b}[39m \n\u{1b}[37mWo\u{1b}[39m\u{1b}[37mrld\u{1b}[39m "
+ );
+ }
+
+ #[cfg(not(feature = "color"))]
+ #[test]
+ fn split_keeping_words_4_test() {
+ let split_keeping_words = |text, width| split_keeping_words(text, width, "\n");
+
+ assert_eq!(split_keeping_words("12345678", 3,), "123\n456\n78 ");
+ assert_eq!(split_keeping_words("12345678", 2,), "12\n34\n56\n78");
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn split_keeping_words_4_test() {
+ let split_keeping_words = |text, width| split_keeping_words(text, width, "", "");
+
+ #[cfg(not(feature = "color"))]
+ let split_keeping_words = |text, width| split_keeping_words(text, width, "\n");
+
+ assert_eq!(split_keeping_words("12345678", 3,), "123\n456\n78 ");
+ assert_eq!(split_keeping_words("12345678", 2,), "12\n34\n56\n78");
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn chunks_test_with_prefix_and_suffix() {
+ assert_eq!(chunks("123456", 0, "^", "$"), ["^$"; 0]);
+
+ assert_eq!(
+ chunks("123456", 1, "^", "$"),
+ ["^1$", "^2$", "^3$", "^4$", "^5$", "^6$"]
+ );
+ assert_eq!(chunks("123456", 2, "^", "$"), ["^12$", "^34$", "^56$"]);
+ assert_eq!(chunks("12345", 2, "^", "$"), ["^12$", "^34$", "^5$"]);
+
+ assert_eq!(
+ chunks("😳😳😳😳😳", 1, "^", "$"),
+ ["^�$", "^�$", "^�$", "^�$", "^�$"]
+ );
+ assert_eq!(
+ chunks("😳😳😳😳😳", 2, "^", "$"),
+ ["^😳$", "^😳$", "^😳$", "^😳$", "^😳$"]
+ );
+ assert_eq!(
+ chunks("😳😳😳😳😳", 3, "^", "$"),
+ ["^😳�$", "^😳�$", "^😳$"]
+ );
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn split_by_line_keeping_words_test_with_prefix_and_suffix() {
+ assert_eq!(
+ split_keeping_words("123456", 1, "^", "$"),
+ "^1$\n^2$\n^3$\n^4$\n^5$\n^6$"
+ );
+ assert_eq!(
+ split_keeping_words("123456", 2, "^", "$"),
+ "^12$\n^34$\n^56$"
+ );
+ assert_eq!(
+ split_keeping_words("12345", 2, "^", "$"),
+ "^12$\n^34$\n^5$ "
+ );
+
+ assert_eq!(
+ split_keeping_words("😳😳😳😳😳", 1, "^", "$"),
+ "^�$\n^�$\n^�$\n^�$\n^�$"
+ );
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn split_by_line_keeping_words_color_2_test_with_prefix_and_suffix() {
+ use ansi_str::AnsiStr;
+
+ let text = "\u{1b}[37mTigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia\u{1b}[0m";
+
+ assert_eq!(
+ split_keeping_words(text, 2, "^", "$")
+ .ansi_split("\n")
+ .collect::<Vec<_>>(),
+ [
+ "^\u{1b}[37mTi\u{1b}[39m$",
+ "^\u{1b}[37mgr\u{1b}[39m$",
+ "^\u{1b}[37me \u{1b}[39m$",
+ "^\u{1b}[37mEc\u{1b}[39m$",
+ "^\u{1b}[37mua\u{1b}[39m$",
+ "^\u{1b}[37mdo\u{1b}[39m$",
+ "^\u{1b}[37mr \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37mOM\u{1b}[39m$",
+ "^\u{1b}[37mYA\u{1b}[39m$",
+ "^\u{1b}[37m A\u{1b}[39m$",
+ "^\u{1b}[37mnd\u{1b}[39m$",
+ "^\u{1b}[37min\u{1b}[39m$",
+ "^\u{1b}[37ma \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m38\u{1b}[39m$",
+ "^\u{1b}[37m24\u{1b}[39m$",
+ "^\u{1b}[37m90\u{1b}[39m$",
+ "^\u{1b}[37m99\u{1b}[39m$",
+ "^\u{1b}[37m99\u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37mCa\u{1b}[39m$",
+ "^\u{1b}[37mlc\u{1b}[39m$",
+ "^\u{1b}[37miu\u{1b}[39m$",
+ "^\u{1b}[37mm \u{1b}[39m$",
+ "^\u{1b}[37mca\u{1b}[39m$",
+ "^\u{1b}[37mrb\u{1b}[39m$",
+ "^\u{1b}[37mon\u{1b}[39m$",
+ "^\u{1b}[37mat\u{1b}[39m$",
+ "^\u{1b}[37me \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37mCo\u{1b}[39m$",
+ "^\u{1b}[37mlo\u{1b}[39m$",
+ "^\u{1b}[37mmb\u{1b}[39m$",
+ "^\u{1b}[37mia\u{1b}[39m$"
+ ]
+ );
+
+ assert_eq!(
+ split_keeping_words(text, 1, "^", "$")
+ .ansi_split("\n")
+ .collect::<Vec<_>>(),
+ [
+ "^\u{1b}[37mT\u{1b}[39m$",
+ "^\u{1b}[37mi\u{1b}[39m$",
+ "^\u{1b}[37mg\u{1b}[39m$",
+ "^\u{1b}[37mr\u{1b}[39m$",
+ "^\u{1b}[37me\u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37mE\u{1b}[39m$",
+ "^\u{1b}[37mc\u{1b}[39m$",
+ "^\u{1b}[37mu\u{1b}[39m$",
+ "^\u{1b}[37ma\u{1b}[39m$",
+ "^\u{1b}[37md\u{1b}[39m$",
+ "^\u{1b}[37mo\u{1b}[39m$",
+ "^\u{1b}[37mr\u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37mO\u{1b}[39m$",
+ "^\u{1b}[37mM\u{1b}[39m$",
+ "^\u{1b}[37mY\u{1b}[39m$",
+ "^\u{1b}[37mA\u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37mA\u{1b}[39m$",
+ "^\u{1b}[37mn\u{1b}[39m$",
+ "^\u{1b}[37md\u{1b}[39m$",
+ "^\u{1b}[37mi\u{1b}[39m$",
+ "^\u{1b}[37mn\u{1b}[39m$",
+ "^\u{1b}[37ma\u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m3\u{1b}[39m$",
+ "^\u{1b}[37m8\u{1b}[39m$",
+ "^\u{1b}[37m2\u{1b}[39m$",
+ "^\u{1b}[37m4\u{1b}[39m$",
+ "^\u{1b}[37m9\u{1b}[39m$",
+ "^\u{1b}[37m0\u{1b}[39m$",
+ "^\u{1b}[37m9\u{1b}[39m$",
+ "^\u{1b}[37m9\u{1b}[39m$",
+ "^\u{1b}[37m9\u{1b}[39m$",
+ "^\u{1b}[37m9\u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37mC\u{1b}[39m$",
+ "^\u{1b}[37ma\u{1b}[39m$",
+ "^\u{1b}[37ml\u{1b}[39m$",
+ "^\u{1b}[37mc\u{1b}[39m$",
+ "^\u{1b}[37mi\u{1b}[39m$",
+ "^\u{1b}[37mu\u{1b}[39m$",
+ "^\u{1b}[37mm\u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37mc\u{1b}[39m$",
+ "^\u{1b}[37ma\u{1b}[39m$",
+ "^\u{1b}[37mr\u{1b}[39m$",
+ "^\u{1b}[37mb\u{1b}[39m$",
+ "^\u{1b}[37mo\u{1b}[39m$",
+ "^\u{1b}[37mn\u{1b}[39m$",
+ "^\u{1b}[37ma\u{1b}[39m$",
+ "^\u{1b}[37mt\u{1b}[39m$",
+ "^\u{1b}[37me\u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37m \u{1b}[39m$",
+ "^\u{1b}[37mC\u{1b}[39m$",
+ "^\u{1b}[37mo\u{1b}[39m$",
+ "^\u{1b}[37ml\u{1b}[39m$",
+ "^\u{1b}[37mo\u{1b}[39m$",
+ "^\u{1b}[37mm\u{1b}[39m$",
+ "^\u{1b}[37mb\u{1b}[39m$",
+ "^\u{1b}[37mi\u{1b}[39m$",
+ "^\u{1b}[37ma\u{1b}[39m$"
+ ]
+ )
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn chunks_wrap_2() {
+ let text = "\u{1b}[30mDebian\u{1b}[0m\u{1b}[31mDebian\u{1b}[0m\u{1b}[32mDebian\u{1b}[0m\u{1b}[33mDebian\u{1b}[0m\u{1b}[34mDebian\u{1b}[0m\u{1b}[35mDebian\u{1b}[0m\u{1b}[36mDebian\u{1b}[0m\u{1b}[37mDebian\u{1b}[0m\u{1b}[40mDebian\u{1b}[0m\u{1b}[41mDebian\u{1b}[0m\u{1b}[42mDebian\u{1b}[0m\u{1b}[43mDebian\u{1b}[0m\u{1b}[44mDebian\u{1b}[0m";
+ assert_eq!(
+ chunks(text, 30, "", ""),
+ [
+ "\u{1b}[30mDebian\u{1b}[39m\u{1b}[31mDebian\u{1b}[39m\u{1b}[32mDebian\u{1b}[39m\u{1b}[33mDebian\u{1b}[39m\u{1b}[34mDebian\u{1b}[39m",
+ "\u{1b}[35mDebian\u{1b}[39m\u{1b}[36mDebian\u{1b}[39m\u{1b}[37mDebian\u{1b}[39m\u{1b}[40mDebian\u{1b}[49m\u{1b}[41mDebian\u{1b}[49m",
+ "\u{1b}[42mDebian\u{1b}[49m\u{1b}[43mDebian\u{1b}[49m\u{1b}[44mDebian\u{1b}[49m",
+ ]
+ );
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn chunks_wrap_3() {
+ let text = "\u{1b}[37mCreate bytes from the \u{1b}[0m\u{1b}[7;34marg\u{1b}[0m\u{1b}[37muments.\u{1b}[0m";
+
+ assert_eq!(
+ chunks(text, 22, "", ""),
+ [
+ "\u{1b}[37mCreate bytes from the \u{1b}[39m",
+ "\u{1b}[7m\u{1b}[34marg\u{1b}[27m\u{1b}[39m\u{1b}[37muments.\u{1b}[39m"
+ ]
+ );
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn chunks_wrap_3_keeping_words() {
+ let text = "\u{1b}[37mCreate bytes from the \u{1b}[0m\u{1b}[7;34marg\u{1b}[0m\u{1b}[37muments.\u{1b}[0m";
+
+ assert_eq!(
+ split_keeping_words(text, 22, "", ""),
+ "\u{1b}[37mCreate bytes from the \u{1b}[39m\n\u{1b}[7m\u{1b}[34marg\u{1b}[27m\u{1b}[39m\u{1b}[37muments.\u{1b}[39m "
+ );
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn chunks_wrap_4() {
+ let text = "\u{1b}[37mReturns the floor of a number (l\u{1b}[0m\u{1b}[41;37marg\u{1b}[0m\u{1b}[37mest integer less than or equal to that number).\u{1b}[0m";
+
+ assert_eq!(
+ chunks(text, 10, "", ""),
+ [
+ "\u{1b}[37mReturns th\u{1b}[39m",
+ "\u{1b}[37me floor of\u{1b}[39m",
+ "\u{1b}[37m a number \u{1b}[39m",
+ "\u{1b}[37m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest i\u{1b}[39m",
+ "\u{1b}[37mnteger les\u{1b}[39m",
+ "\u{1b}[37ms than or \u{1b}[39m",
+ "\u{1b}[37mequal to t\u{1b}[39m",
+ "\u{1b}[37mhat number\u{1b}[39m",
+ "\u{1b}[37m).\u{1b}[39m",
+ ]
+ );
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn chunks_wrap_4_keeping_words() {
+ let text = "\u{1b}[37mReturns the floor of a number (l\u{1b}[0m\u{1b}[41;37marg\u{1b}[0m\u{1b}[37mest integer less than or equal to that number).\u{1b}[0m";
+ assert_eq!(
+ split_keeping_words(text, 10, "", ""),
+ concat!(
+ "\u{1b}[37mReturns \u{1b}[39m \n",
+ "\u{1b}[37mthe floor \u{1b}[39m\n",
+ "\u{1b}[37mof a \u{1b}[39m \n",
+ "\u{1b}[37mnumber \u{1b}[39m \n",
+ "\u{1b}[37m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest \u{1b}[39m \n",
+ "\u{1b}[37minteger \u{1b}[39m \n",
+ "\u{1b}[37mless than \u{1b}[39m\n",
+ "\u{1b}[37mor equal \u{1b}[39m \n",
+ "\u{1b}[37mto that \u{1b}[39m \n",
+ "\u{1b}[37mnumber).\u{1b}[39m ",
+ )
+ );
+ }
+}
+
+// \u{1b}[37mReturns \u{1b}[39m\n
+// \u{1b}[37mthe floor \u{1b}[39m\n
+// \u{1b}[37mof a \u{1b}[39m\n
+// \u{1b}[37mnumber \u{1b}[39m\u{1b}[49m\n
+// \u{1b}[37m\u{1b}[41m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest \u{1b}[39m\n
+// \u{1b}[37minteger \u{1b}[39m\n
+// \u{1b}[37mless than \u{1b}[39m\n
+// \u{1b}[37mor equal \u{1b}[39m\n
+// \u{1b}[37mto that \u{1b}[39m\n
+// \u{1b}[37mnumber).\u{1b}[39m "
+
+//
+//
+
+// \u{1b}[37mReturns \u{1b}[39m\n
+// \u{1b}[37mthe floor \u{1b}[39m\n
+// \u{1b}[37mof a \u{1b}[39m\n
+// \u{1b}[37mnumber \u{1b}[39m\u{1b}[49m\n
+// \u{1b}[37m\u{1b}[41m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest \u{1b}[39m\n
+// \u{1b}[37minteger \u{1b}[39m\n
+// \u{1b}[37mless than \u{1b}[39m\n
+// \u{1b}[37mor equal \u{1b}[39m\n
+// \u{1b}[37mto that \u{1b}[39m\n
+// \u{1b}[37mnumber).\u{1b}[39m "
+
+// "\u{1b}[37mReturns\u{1b}[37m \u{1b}[39m\n
+// \u{1b}[37mthe\u{1b}[37m floor\u{1b}[37m \u{1b}[39m\n
+// \u{1b}[37mof\u{1b}[37m a\u{1b}[37m \u{1b}[39m\n
+// \u{1b}[37mnumber\u{1b}[37m \u{1b}[39m\u{1b}[49m\n
+// \u{1b}[37m\u{1b}[41m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest\u{1b}[37m \u{1b}[39m\n
+// \u{1b}[37minteger\u{1b}[37m \u{1b}[39m\n
+// \u{1b}[37mless\u{1b}[37m than\u{1b}[37m \u{1b}[39m\n
+// \u{1b}[37mor\u{1b}[37m equal\u{1b}[37m \u{1b}[39m\n
+// \u{1b}[37mto\u{1b}[37m that\u{1b}[37m \u{1b}[39m\n
+// \u{1b}[37mnumber).\u{1b}[39m "