From ef24de24a82fe681581cc130f342363c47c0969a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 7 Jun 2024 07:48:48 +0200 Subject: Merging upstream version 1.75.0+dfsg1. Signed-off-by: Daniel Baumann --- vendor/tabled/src/settings/style/border.rs | 159 +++ vendor/tabled/src/settings/style/border_char.rs | 99 ++ vendor/tabled/src/settings/style/border_color.rs | 155 +++ vendor/tabled/src/settings/style/border_text.rs | 253 ++++ vendor/tabled/src/settings/style/builder.rs | 1208 ++++++++++++++++++++ .../tabled/src/settings/style/horizontal_line.rs | 69 ++ vendor/tabled/src/settings/style/line.rs | 91 ++ vendor/tabled/src/settings/style/mod.rs | 127 ++ vendor/tabled/src/settings/style/offset.rs | 19 + vendor/tabled/src/settings/style/raw_style.rs | 438 +++++++ .../src/settings/style/span_border_correction.rs | 216 ++++ vendor/tabled/src/settings/style/vertical_line.rs | 50 + 12 files changed, 2884 insertions(+) create mode 100644 vendor/tabled/src/settings/style/border.rs create mode 100644 vendor/tabled/src/settings/style/border_char.rs create mode 100644 vendor/tabled/src/settings/style/border_color.rs create mode 100644 vendor/tabled/src/settings/style/border_text.rs create mode 100644 vendor/tabled/src/settings/style/builder.rs create mode 100644 vendor/tabled/src/settings/style/horizontal_line.rs create mode 100644 vendor/tabled/src/settings/style/line.rs create mode 100644 vendor/tabled/src/settings/style/mod.rs create mode 100644 vendor/tabled/src/settings/style/offset.rs create mode 100644 vendor/tabled/src/settings/style/raw_style.rs create mode 100644 vendor/tabled/src/settings/style/span_border_correction.rs create mode 100644 vendor/tabled/src/settings/style/vertical_line.rs (limited to 'vendor/tabled/src/settings/style') diff --git a/vendor/tabled/src/settings/style/border.rs b/vendor/tabled/src/settings/style/border.rs new file mode 100644 index 000000000..a8cbefb5d --- /dev/null +++ b/vendor/tabled/src/settings/style/border.rs @@ -0,0 +1,159 @@ +use crate::{ + grid::{ + config::{Border as GBorder, ColoredConfig, Entity}, + records::{ExactRecords, Records}, + }, + settings::CellOption, +}; + +/// Border represents a border of a Cell. +/// +/// ```text +/// top border +/// | +/// V +/// corner top left ------> +_______+ <---- corner top left +/// | | +/// left border ----------> | cell | <---- right border +/// | | +/// corner bottom right --> +_______+ <---- corner bottom right +/// ^ +/// | +/// bottom border +/// ``` +/// +/// ```rust,no_run +/// # use tabled::{Table, settings::{Modify, style::{Style, Border}, object::Rows}}; +/// # let data: Vec<&'static str> = Vec::new(); +/// let table = Table::new(&data) +/// .with(Style::ascii()) +/// .with(Modify::new(Rows::single(0)).with(Border::default().top('x'))); +/// ``` +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Border(GBorder); + +impl Border { + /// This function constructs a cell borders with all sides set. + #[allow(clippy::too_many_arguments)] + pub const fn full( + top: char, + bottom: char, + left: char, + right: char, + top_left: char, + top_right: char, + bottom_left: char, + bottom_right: char, + ) -> Self { + Self(GBorder::full( + top, + bottom, + left, + right, + top_left, + top_right, + bottom_left, + bottom_right, + )) + } + + /// This function constructs a cell borders with all sides's char set to a given character. + /// It behaves like [`Border::full`] with the same character set to each side. + pub const fn filled(c: char) -> Self { + Self::full(c, c, c, c, c, c, c, c) + } + + /// Using this function you deconstruct the existing borders. + pub const fn empty() -> EmptyBorder { + EmptyBorder + } + + /// Set a top border character. + pub const fn top(mut self, c: char) -> Self { + self.0.top = Some(c); + self + } + + /// Set a bottom border character. + pub const fn bottom(mut self, c: char) -> Self { + self.0.bottom = Some(c); + self + } + + /// Set a left border character. + pub const fn left(mut self, c: char) -> Self { + self.0.left = Some(c); + self + } + + /// Set a right border character. + pub const fn right(mut self, c: char) -> Self { + self.0.right = Some(c); + self + } + + /// Set a top left intersection character. + pub const fn corner_top_left(mut self, c: char) -> Self { + self.0.left_top_corner = Some(c); + self + } + + /// Set a top right intersection character. + pub const fn corner_top_right(mut self, c: char) -> Self { + self.0.right_top_corner = Some(c); + self + } + + /// Set a bottom left intersection character. + pub const fn corner_bottom_left(mut self, c: char) -> Self { + self.0.left_bottom_corner = Some(c); + self + } + + /// Set a bottom right intersection character. + pub const fn corner_bottom_right(mut self, c: char) -> Self { + self.0.right_bottom_corner = Some(c); + self + } +} + +impl CellOption for Border +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let shape = (records.count_rows(), records.count_columns()); + + for pos in entity.iter(shape.0, shape.1) { + cfg.set_border(pos, self.0); + } + } +} + +impl From> for Border { + fn from(b: GBorder) -> Border { + Border(b) + } +} + +impl From for GBorder { + fn from(value: Border) -> Self { + value.0 + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct EmptyBorder; + +impl CellOption for EmptyBorder +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let shape = (records.count_rows(), records.count_columns()); + + for pos in entity.iter(shape.0, shape.1) { + cfg.remove_border(pos, shape); + } + } +} diff --git a/vendor/tabled/src/settings/style/border_char.rs b/vendor/tabled/src/settings/style/border_char.rs new file mode 100644 index 000000000..b1e04c579 --- /dev/null +++ b/vendor/tabled/src/settings/style/border_char.rs @@ -0,0 +1,99 @@ +use crate::{ + grid::config::{ColoredConfig, Entity, Position, SpannedConfig}, + grid::records::{ExactRecords, Records}, + settings::CellOption, +}; + +use super::Offset; + +/// [`BorderChar`] sets a char to a specific location on a horizontal line. +/// +/// # Example +/// +/// ```rust +/// use tabled::{Table, settings::{style::{Style, BorderChar, Offset}, Modify, object::{Object, Rows, Columns}}}; +/// +/// let mut table = Table::new(["Hello World"]); +/// table +/// .with(Style::markdown()) +/// .with(Modify::new(Rows::single(1)) +/// .with(BorderChar::horizontal(':', Offset::Begin(0))) +/// .with(BorderChar::horizontal(':', Offset::End(0))) +/// ) +/// .with(Modify::new((1, 0).and((1, 1))).with(BorderChar::vertical('#', Offset::Begin(0)))); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "| &str |\n", +/// "|:-----------:|\n", +/// "# Hello World #", +/// ), +/// ); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct BorderChar { + c: char, + offset: Offset, + horizontal: bool, +} + +impl BorderChar { + /// Creates a [`BorderChar`] which overrides horizontal line. + pub fn horizontal(c: char, offset: Offset) -> Self { + Self { + c, + offset, + horizontal: true, + } + } + + /// Creates a [`BorderChar`] which overrides vertical line. + pub fn vertical(c: char, offset: Offset) -> Self { + Self { + c, + offset, + horizontal: false, + } + } +} + +impl CellOption for BorderChar +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let cells = entity.iter(records.count_rows(), records.count_columns()); + + match self.horizontal { + true => add_char_horizontal(cfg, self.c, self.offset, cells), + false => add_char_vertical(cfg, self.c, self.offset, cells), + } + } +} + +fn add_char_vertical>( + cfg: &mut SpannedConfig, + c: char, + offset: Offset, + cells: I, +) { + let offset = offset.into(); + + for pos in cells { + cfg.set_vertical_char(pos, c, offset); + } +} + +fn add_char_horizontal>( + cfg: &mut SpannedConfig, + c: char, + offset: Offset, + cells: I, +) { + let offset = offset.into(); + + for pos in cells { + cfg.set_horizontal_char(pos, c, offset); + } +} diff --git a/vendor/tabled/src/settings/style/border_color.rs b/vendor/tabled/src/settings/style/border_color.rs new file mode 100644 index 000000000..6b3fa2b1a --- /dev/null +++ b/vendor/tabled/src/settings/style/border_color.rs @@ -0,0 +1,155 @@ +//! This module contains a configuration of a Border to set its color via [`BorderColor`]. + +use crate::{ + grid::{ + color::AnsiColor, + config::{Border, ColoredConfig, Entity}, + records::{ExactRecords, Records}, + }, + settings::{color::Color, CellOption, TableOption}, +}; + +/// BorderColored represents a colored border of a Cell. +/// +/// ```rust,no_run +/// # use tabled::{settings::{style::BorderColor, Style, Color, object::Rows, Modify}, Table}; +/// # +/// # let data: Vec<&'static str> = Vec::new(); +/// # +/// let table = Table::new(&data) +/// .with(Style::ascii()) +/// .with(Modify::new(Rows::single(0)).with(BorderColor::default().top(Color::FG_RED))); +/// ``` +#[derive(Debug, Clone, Default, Eq, PartialEq)] +pub struct BorderColor(Border>); + +impl BorderColor { + /// This function constructs a cell borders with all sides set. + #[allow(clippy::too_many_arguments)] + pub fn full( + top: Color, + bottom: Color, + left: Color, + right: Color, + top_left: Color, + top_right: Color, + bottom_left: Color, + bottom_right: Color, + ) -> Self { + Self(Border::full( + top.into(), + bottom.into(), + left.into(), + right.into(), + top_left.into(), + top_right.into(), + bottom_left.into(), + bottom_right.into(), + )) + } + + /// This function constructs a cell borders with all sides's char set to a given character. + /// It behaves like [`Border::full`] with the same character set to each side. + pub fn filled(c: Color) -> Self { + let c: AnsiColor<'_> = c.into(); + + Self(Border { + top: Some(c.clone()), + bottom: Some(c.clone()), + left: Some(c.clone()), + right: Some(c.clone()), + left_bottom_corner: Some(c.clone()), + left_top_corner: Some(c.clone()), + right_bottom_corner: Some(c.clone()), + right_top_corner: Some(c), + }) + } + + /// Set a top border character. + pub fn top(mut self, c: Color) -> Self { + self.0.top = Some(c.into()); + self + } + + /// Set a bottom border character. + pub fn bottom(mut self, c: Color) -> Self { + self.0.bottom = Some(c.into()); + self + } + + /// Set a left border character. + pub fn left(mut self, c: Color) -> Self { + self.0.left = Some(c.into()); + self + } + + /// Set a right border character. + pub fn right(mut self, c: Color) -> Self { + self.0.right = Some(c.into()); + self + } + + /// Set a top left intersection character. + pub fn corner_top_left(mut self, c: Color) -> Self { + self.0.left_top_corner = Some(c.into()); + self + } + + /// Set a top right intersection character. + pub fn corner_top_right(mut self, c: Color) -> Self { + self.0.right_top_corner = Some(c.into()); + self + } + + /// Set a bottom left intersection character. + pub fn corner_bottom_left(mut self, c: Color) -> Self { + self.0.left_bottom_corner = Some(c.into()); + self + } + + /// Set a bottom right intersection character. + pub fn corner_bottom_right(mut self, c: Color) -> Self { + self.0.right_bottom_corner = Some(c.into()); + self + } +} + +impl CellOption for BorderColor +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let border_color = &self.0; + + for pos in entity.iter(count_rows, count_columns) { + cfg.set_border_color(pos, border_color.clone()); + } + } +} + +impl TableOption for BorderColor +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let border_color = &self.0; + + for row in 0..count_rows { + for col in 0..count_columns { + cfg.set_border_color((row, col), border_color.clone()); + } + } + } +} + +impl From for Border> { + fn from(val: BorderColor) -> Self { + val.0 + } +} diff --git a/vendor/tabled/src/settings/style/border_text.rs b/vendor/tabled/src/settings/style/border_text.rs new file mode 100644 index 000000000..163a19821 --- /dev/null +++ b/vendor/tabled/src/settings/style/border_text.rs @@ -0,0 +1,253 @@ +use crate::{ + grid::{ + color::AnsiColor, + config::{self, ColoredConfig, SpannedConfig}, + dimension::{Dimension, Estimate}, + records::{ExactRecords, Records}, + }, + settings::{ + object::{FirstRow, LastRow}, + Color, TableOption, + }, +}; + +use super::Offset; + +/// [`BorderText`] writes a custom text on a border. +/// +/// # Example +/// +/// ```rust +/// use tabled::{Table, settings::style::BorderText, settings::object::Rows}; +/// +/// let mut table = Table::new(["Hello World"]); +/// table +/// .with(BorderText::new("+-.table").horizontal(Rows::first())); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-.table------+\n\ +/// | &str |\n\ +/// +-------------+\n\ +/// | Hello World |\n\ +/// +-------------+" +/// ); +/// ``` +#[derive(Debug)] +pub struct BorderText { + text: String, + offset: Offset, + color: Option>, + line: L, +} + +impl BorderText<()> { + /// Creates a [`BorderText`] instance. + /// + /// Lines are numbered from 0 to the `count_rows` included + /// (`line >= 0 && line <= count_rows`). + pub fn new>(text: S) -> Self { + BorderText { + text: text.into(), + line: (), + offset: Offset::Begin(0), + color: None, + } + } +} + +impl BorderText { + /// Set a line on which we will set the text. + pub fn horizontal(self, line: L) -> BorderText { + BorderText { + line, + text: self.text, + offset: self.offset, + color: self.color, + } + } + + /// Set an offset from which the text will be started. + pub fn offset(self, offset: Offset) -> Self { + BorderText { + offset, + text: self.text, + line: self.line, + color: self.color, + } + } + + /// Set a color of the text. + pub fn color(self, color: Color) -> Self { + BorderText { + color: Some(color.into()), + text: self.text, + line: self.line, + offset: self.offset, + } + } +} + +impl TableOption for BorderText +where + R: Records + ExactRecords, + for<'a> &'a R: Records, + for<'a> D: Estimate<&'a R, ColoredConfig>, + D: Dimension, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { + dims.estimate(records, cfg); + let shape = (records.count_rows(), records.count_columns()); + let line = self.line; + set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape); + } +} + +impl TableOption for BorderText +where + R: Records + ExactRecords, + for<'a> &'a R: Records, + for<'a> D: Estimate<&'a R, ColoredConfig>, + D: Dimension, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { + dims.estimate(records, cfg); + let shape = (records.count_rows(), records.count_columns()); + let line = 0; + set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape); + } +} + +impl TableOption for BorderText +where + R: Records + ExactRecords, + for<'a> &'a R: Records, + for<'a> D: Estimate<&'a R, ColoredConfig>, + D: Dimension, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { + dims.estimate(records, cfg); + let shape = (records.count_rows(), records.count_columns()); + let line = records.count_rows(); + set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape); + } +} + +fn set_horizontal_chars( + cfg: &mut SpannedConfig, + dims: &D, + offset: Offset, + line: usize, + text: &str, + color: &Option>, + shape: (usize, usize), +) { + let (_, count_columns) = shape; + let pos = get_start_pos(cfg, dims, offset, count_columns); + let pos = match pos { + Some(pos) => pos, + None => return, + }; + + let mut chars = text.chars(); + let mut i = cfg.has_vertical(0, count_columns) as usize; + if i == 1 && pos == 0 { + let c = match chars.next() { + Some(c) => c, + None => return, + }; + + let mut b = cfg.get_border((line, 0), shape); + b.left_top_corner = b.left_top_corner.map(|_| c); + cfg.set_border((line, 0), b); + + if let Some(color) = color.as_ref() { + let mut b = cfg.get_border_color((line, 0), shape).cloned(); + b.left_top_corner = Some(color.clone()); + cfg.set_border_color((line, 0), b); + } + } + + for col in 0..count_columns { + let w = dims.get_width(col); + if i + w > pos { + for off in 0..w { + if i + off < pos { + continue; + } + + let c = match chars.next() { + Some(c) => c, + None => return, + }; + + cfg.set_horizontal_char((line, col), c, config::Offset::Begin(off)); + if let Some(color) = color.as_ref() { + cfg.set_horizontal_color( + (line, col), + color.clone(), + config::Offset::Begin(off), + ); + } + } + } + + i += w; + + if cfg.has_vertical(col + 1, count_columns) { + i += 1; + + if i > pos { + let c = match chars.next() { + Some(c) => c, + None => return, + }; + + let mut b = cfg.get_border((line, col), shape); + b.right_top_corner = b.right_top_corner.map(|_| c); + cfg.set_border((line, col), b); + + if let Some(color) = color.as_ref() { + let mut b = cfg.get_border_color((line, col), shape).cloned(); + b.right_top_corner = Some(color.clone()); + cfg.set_border_color((line, col), b); + } + } + } + } +} + +fn get_start_pos( + cfg: &SpannedConfig, + dims: &D, + offset: Offset, + count_columns: usize, +) -> Option { + let totalw = total_width(cfg, dims, count_columns); + match offset { + Offset::Begin(i) => { + if i > totalw { + None + } else { + Some(i) + } + } + Offset::End(i) => { + if i > totalw { + None + } else { + Some(totalw - i) + } + } + } +} + +fn total_width(cfg: &SpannedConfig, dims: &D, count_columns: usize) -> usize { + let mut totalw = cfg.has_vertical(0, count_columns) as usize; + for col in 0..count_columns { + totalw += dims.get_width(col); + totalw += cfg.has_vertical(col + 1, count_columns) as usize; + } + + totalw +} diff --git a/vendor/tabled/src/settings/style/builder.rs b/vendor/tabled/src/settings/style/builder.rs new file mode 100644 index 000000000..d55c400cc --- /dev/null +++ b/vendor/tabled/src/settings/style/builder.rs @@ -0,0 +1,1208 @@ +//! This module contains a compile time style builder [`Style`]. + +use core::marker::PhantomData; + +use crate::{ + grid::config::{Borders, CompactConfig, CompactMultilineConfig}, + settings::TableOption, +}; + +#[cfg(feature = "std")] +use crate::grid::config::ColoredConfig; + +use super::{HorizontalLine, Line, VerticalLine}; + +/// Style is represents a theme of a [`Table`]. +/// +/// ```text +/// corner top left top intersection corner top right +/// . | . +/// . V . +/// ╭───┬───┬───┬───┬───┬───┬────┬────┬────╮ +/// │ i │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ +/// ├───┼───┼───┼───┼───┼───┼────┼────┼────┤ <- this horizontal line is custom 'horizontals' +/// │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ other lines horizontal lines are not set they called 'horizontal' +/// │ 1 │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ +/// │ 2 │ 0 │ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │ +/// ╰───┴───┴───┴───┴───┴───┴────┴────┴────╯ +/// . ^ ^ . +/// . | | . +/// corner bottom left | bottom intersection corner bottom right +/// | +/// | +/// all this vertical lines are called 'vertical' +/// ``` +/// +/// +/// ```text +/// ┌───┬───┬───┬───┬───┐ +/// │ 0 │ 1 │ 2 │ 3 │ 4 │ +/// intersection left ->├───X───X───X───X───┤ <- all this horizontal lines are called 'horizontal' +/// │ 1 │ 2 │ 3 │ 4 │ 5 │ +/// ├───X───X───X───X───┤ <- intersection right +/// │ 2 │ 3 │ 4 │ 5 │ 6 │ +/// └───┴───┴───┴───┴───┘ +/// +/// All 'X' positions are called 'intersection'. +/// It's a place where 'vertical' and 'horizontal' lines intersect. +/// ``` +/// +/// It tries to limit an controlling a valid state of it. +/// For example, it won't allow to call method [`Style::corner_top_left`] unless [`Style::left`] and [`Style::top`] is set. +/// +/// You can turn [`Style`] into [`RawStyle`] to have more control using [`Into`] implementation. +/// +/// # Example +/// +#[cfg_attr(feature = "std", doc = "```")] +#[cfg_attr(not(feature = "std"), doc = "```ignore")] +/// use tabled::{Table, settings::Style}; +/// +/// let style = Style::ascii() +/// .bottom('*') +/// .intersection(' '); +/// +/// let data = vec!["Hello", "2021"]; +/// let table = Table::new(&data).with(style).to_string(); +/// +/// println!("{}", table); +/// ``` +/// +/// [`Table`]: crate::Table +/// [`RawStyle`]: crate::settings::style::RawStyle +/// [`Style::corner_top_left`]: Style::corner_top_left +/// [`Style::left`]: Style.left +/// [`Style::top`]: Style.function.top +#[derive(Debug, Clone)] +pub struct Style, VLines = VLineArray<0>> { + borders: Borders, + horizontals: HLines, + verticals: VLines, + _top: PhantomData, + _bottom: PhantomData, + _left: PhantomData, + _right: PhantomData, + _horizontal: PhantomData, + _vertical: PhantomData, +} + +type HLineArray = [HorizontalLine; N]; + +type VLineArray = [VerticalLine; N]; + +/// A marker struct which is used in [`Style`]. +#[derive(Debug, Clone)] +pub struct On; + +impl Style<(), (), (), (), (), (), (), ()> { + /// This style is a style with no styling options on, + /// + /// ```text + /// id destribution link + /// 0 Fedora https://getfedora.org/ + /// 2 OpenSUSE https://www.opensuse.org/ + /// 3 Endeavouros https://endeavouros.com/ + /// ``` + /// + /// Note: The cells in the example have 1-left and 1-right indent. + /// + /// This style can be used as a base style to build a custom one. + /// + /// ```rust,no_run + /// # use tabled::settings::Style; + /// let style = Style::empty() + /// .top('*') + /// .bottom('*') + /// .vertical('#') + /// .intersection_top('*'); + /// ``` + pub const fn empty() -> Style<(), (), (), (), (), ()> { + Style::new( + create_borders( + Line::empty(), + Line::empty(), + Line::empty(), + None, + None, + None, + ), + [], + [], + ) + } + + /// This style is analog of `empty` but with a vertical space(' ') line. + /// + /// ```text + /// id destribution link + /// 0 Fedora https://getfedora.org/ + /// 2 OpenSUSE https://www.opensuse.org/ + /// 3 Endeavouros https://endeavouros.com/ + /// ``` + pub const fn blank() -> Style<(), (), (), (), (), On> { + Style::new( + create_borders( + Line::empty(), + Line::empty(), + Line::empty(), + None, + None, + Some(' '), + ), + [], + [], + ) + } + + /// This is a style which relays only on ASCII charset. + /// + /// It has horizontal and vertical lines. + /// + /// ```text + /// +----+--------------+---------------------------+ + /// | id | destribution | link | + /// +----+--------------+---------------------------+ + /// | 0 | Fedora | https://getfedora.org/ | + /// +----+--------------+---------------------------+ + /// | 2 | OpenSUSE | https://www.opensuse.org/ | + /// +----+--------------+---------------------------+ + /// | 3 | Endeavouros | https://endeavouros.com/ | + /// +----+--------------+---------------------------+ + /// ``` + pub const fn ascii() -> Style { + Style::new( + create_borders( + Line::full('-', '+', '+', '+'), + Line::full('-', '+', '+', '+'), + Line::full('-', '+', '+', '+'), + Some('|'), + Some('|'), + Some('|'), + ), + [], + [], + ) + } + + /// `psql` style looks like a table style `PostgreSQL` uses. + /// + /// It has only 1 horizontal line which splits header. + /// And no left and right vertical lines. + /// + /// ```text + /// id | destribution | link + /// ----+--------------+--------------------------- + /// 0 | Fedora | https://getfedora.org/ + /// 2 | OpenSUSE | https://www.opensuse.org/ + /// 3 | Endeavouros | https://endeavouros.com/ + /// ``` + pub const fn psql() -> Style<(), (), (), (), (), On, HLineArray<1>> { + Style::new( + create_borders( + Line::empty(), + Line::empty(), + Line::empty(), + None, + None, + Some('|'), + ), + [HorizontalLine::new(1, Line::empty()) + .main(Some('-')) + .intersection(Some('+'))], + [], + ) + } + + /// `markdown` style mimics a `Markdown` table style. + /// + /// ```text + /// | id | destribution | link | + /// |----|--------------|---------------------------| + /// | 0 | Fedora | https://getfedora.org/ | + /// | 2 | OpenSUSE | https://www.opensuse.org/ | + /// | 3 | Endeavouros | https://endeavouros.com/ | + /// ``` + pub const fn markdown() -> Style<(), (), On, On, (), On, HLineArray<1>> { + Style::new( + create_borders( + Line::empty(), + Line::empty(), + Line::empty(), + Some('|'), + Some('|'), + Some('|'), + ), + [HorizontalLine::new(1, Line::full('-', '|', '|', '|'))], + [], + ) + } + + /// This style is analog of [`Style::ascii`] which uses UTF-8 charset. + /// + /// It has vertical and horizontal split lines. + /// + /// ```text + /// ┌────┬──────────────┬───────────────────────────┐ + /// │ id │ destribution │ link │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 0 │ Fedora │ https://getfedora.org/ │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ + /// └────┴──────────────┴───────────────────────────┘ + /// ``` + pub const fn modern() -> Style { + Style::new( + create_borders( + Line::full('─', '┬', '┌', '┐'), + Line::full('─', '┴', '└', '┘'), + Line::full('─', '┼', '├', '┤'), + Some('│'), + Some('│'), + Some('│'), + ), + [], + [], + ) + } + + /// This style looks like a [`Style::modern`] but without horozizontal lines except a header. + /// + /// Beware: It uses UTF-8 characters. + /// + /// ```text + /// ┌────┬──────────────┬───────────────────────────┐ + /// │ id │ destribution │ link │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 0 │ Fedora │ https://getfedora.org/ │ + /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ + /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ + /// └────┴──────────────┴───────────────────────────┘ + /// ``` + pub const fn sharp() -> Style> { + Style::new( + create_borders( + Line::full('─', '┬', '┌', '┐'), + Line::full('─', '┴', '└', '┘'), + Line::empty(), + Some('│'), + Some('│'), + Some('│'), + ), + [HorizontalLine::new(1, Line::full('─', '┼', '├', '┤'))], + [], + ) + } + + /// This style looks like a [`Style::sharp`] but with rounded corners. + /// + /// Beware: It uses UTF-8 characters. + /// + /// ```text + /// ╭────┬──────────────┬───────────────────────────╮ + /// │ id │ destribution │ link │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 0 │ Fedora │ https://getfedora.org/ │ + /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ + /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ + /// ╰────┴──────────────┴───────────────────────────╯ + /// ``` + pub const fn rounded() -> Style> { + Style::new( + create_borders( + Line::full('─', '┬', '╭', '╮'), + Line::full('─', '┴', '╰', '╯'), + Line::empty(), + Some('│'), + Some('│'), + Some('│'), + ), + [HorizontalLine::new(1, Line::full('─', '┼', '├', '┤'))], + [], + ) + } + + /// This style uses a chars which resembles '2 lines'. + /// + /// Beware: It uses UTF8 characters. + /// + /// ```text + /// ╔════╦══════════════╦═══════════════════════════╗ + /// ║ id ║ destribution ║ link ║ + /// ╠════╬══════════════╬═══════════════════════════╣ + /// ║ 0 ║ Fedora ║ https://getfedora.org/ ║ + /// ╠════╬══════════════╬═══════════════════════════╣ + /// ║ 2 ║ OpenSUSE ║ https://www.opensuse.org/ ║ + /// ╠════╬══════════════╬═══════════════════════════╣ + /// ║ 3 ║ Endeavouros ║ https://endeavouros.com/ ║ + /// ╚════╩══════════════╩═══════════════════════════╝ + /// ``` + pub const fn extended() -> Style { + Style::new( + create_borders( + Line::full('═', '╦', '╔', '╗'), + Line::full('═', '╩', '╚', '╝'), + Line::full('═', '╬', '╠', '╣'), + Some('║'), + Some('║'), + Some('║'), + ), + [], + [], + ) + } + + /// This is a style uses only '.' and ':' chars. + /// It has a vertical and horizontal split lines. + /// + /// ```text + /// ................................................. + /// : id : destribution : link : + /// :....:..............:...........................: + /// : 0 : Fedora : https://getfedora.org/ : + /// :....:..............:...........................: + /// : 2 : OpenSUSE : https://www.opensuse.org/ : + /// :....:..............:...........................: + /// : 3 : Endeavouros : https://endeavouros.com/ : + /// :....:..............:...........................: + /// ``` + pub const fn dots() -> Style { + Style::new( + create_borders( + Line::full('.', '.', '.', '.'), + Line::full('.', ':', ':', ':'), + Line::full('.', ':', ':', ':'), + Some(':'), + Some(':'), + Some(':'), + ), + [], + [], + ) + } + + /// This style is one of table views in `ReStructuredText`. + /// + /// ```text + /// ==== ============== =========================== + /// id destribution link + /// ==== ============== =========================== + /// 0 Fedora https://getfedora.org/ + /// 2 OpenSUSE https://www.opensuse.org/ + /// 3 Endeavouros https://endeavouros.com/ + /// ==== ============== =========================== + /// ``` + pub const fn re_structured_text() -> Style> { + Style::new( + create_borders( + Line::new(Some('='), Some(' '), None, None), + Line::new(Some('='), Some(' '), None, None), + Line::empty(), + None, + None, + Some(' '), + ), + [HorizontalLine::new( + 1, + Line::new(Some('='), Some(' '), None, None), + )], + [], + ) + } + + /// This is a theme analog of [`Style::rounded`], but in using ascii charset and + /// with no horizontal lines. + /// + /// ```text + /// .-----------------------------------------------. + /// | id | destribution | link | + /// | 0 | Fedora | https://getfedora.org/ | + /// | 2 | OpenSUSE | https://www.opensuse.org/ | + /// | 3 | Endeavouros | https://endeavouros.com/ | + /// '-----------------------------------------------' + /// ``` + pub const fn ascii_rounded() -> Style { + Style::new( + create_borders( + Line::full('-', '-', '.', '.'), + Line::full('-', '-', '\'', '\''), + Line::empty(), + Some('|'), + Some('|'), + Some('|'), + ), + [], + [], + ) + } +} + +impl Style { + /// Frame function returns a frame as a border. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::{Style, Highlight, object::Rows}}; + /// + /// let data = [["10:52:19", "Hello"], ["10:52:20", "World"]]; + /// let table = Table::new(data) + /// .with(Highlight::new(Rows::first(), Style::modern().get_frame())) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "┌──────────────────┐\n", + /// "│ 0 | 1 │\n", + /// "└──────────────────┘\n", + /// "| 10:52:19 | Hello |\n", + /// "+----------+-------+\n", + /// "| 10:52:20 | World |\n", + /// "+----------+-------+", + /// ) + /// ); + /// ``` + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub const fn get_frame(&self) -> super::Border { + let mut border = super::Border::filled(' '); + + if let Some(c) = self.borders.top { + border = border.top(c); + } + + if let Some(c) = self.borders.bottom { + border = border.bottom(c); + } + + if let Some(c) = self.borders.left { + border = border.left(c); + } + + if let Some(c) = self.borders.right { + border = border.right(c); + } + + if let Some(c) = self.borders.top_left { + border = border.corner_top_left(c); + } + + if let Some(c) = self.borders.bottom_left { + border = border.corner_bottom_left(c); + } + + if let Some(c) = self.borders.top_right { + border = border.corner_top_right(c); + } + + if let Some(c) = self.borders.bottom_right { + border = border.corner_bottom_right(c); + } + + border + } + + /// Get a [`Style`]'s default horizontal line. + /// + /// It doesn't return an overloaded line via [`Style::horizontals`]. + /// + /// # Example + /// + #[cfg_attr(feature = "std", doc = "```")] + #[cfg_attr(not(feature = "std"), doc = "```ignore")] + /// use tabled::{settings::style::{Style, HorizontalLine, Line}, Table}; + /// + /// let table = Table::new((0..3).map(|i| ("Hello", "World", i))) + /// .with(Style::ascii().remove_horizontal().horizontals([HorizontalLine::new(1, Style::modern().get_horizontal())])) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "+-------+-------+-----+\n", + /// "| &str | &str | i32 |\n", + /// "├───────┼───────┼─────┤\n", + /// "| Hello | World | 0 |\n", + /// "| Hello | World | 1 |\n", + /// "| Hello | World | 2 |\n", + /// "+-------+-------+-----+", + /// ) + /// ) + /// ``` + pub const fn get_horizontal(&self) -> Line { + Line::new( + self.borders.horizontal, + self.borders.intersection, + self.borders.left_intersection, + self.borders.right_intersection, + ) + } + + /// Get a [`Style`]'s default horizontal line. + /// + /// It doesn't return an overloaded line via [`Style::verticals`]. + /// + /// # Example + /// + #[cfg_attr(feature = "std", doc = "```")] + #[cfg_attr(not(feature = "std"), doc = "```ignore")] + /// use tabled::{settings::style::{Style, VerticalLine, Line}, Table}; + /// + /// let table = Table::new((0..3).map(|i| ("Hello", "World", i))) + /// .with(Style::ascii().remove_horizontal().verticals([VerticalLine::new(1, Style::modern().get_vertical())])) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "+-------┬-------+-----+\n", + /// "| &str │ &str | i32 |\n", + /// "| Hello │ World | 0 |\n", + /// "| Hello │ World | 1 |\n", + /// "| Hello │ World | 2 |\n", + /// "+-------┴-------+-----+", + /// ) + /// ) + /// ``` + pub const fn get_vertical(&self) -> Line { + Line::new( + self.borders.vertical, + self.borders.intersection, + self.borders.top_intersection, + self.borders.bottom_intersection, + ) + } + + /// Sets a top border. + /// + /// Any corners and intersections which were set will be overridden. + pub fn top(mut self, c: char) -> Style + where + for<'a> &'a mut VLines: IntoIterator, + { + self.borders.top = Some(c); + + if self.borders.has_left() { + self.borders.top_left = Some(c); + } + + if self.borders.has_right() { + self.borders.top_right = Some(c); + } + + if self.borders.has_vertical() { + self.borders.top_intersection = Some(c); + } + + for vl in &mut self.verticals { + vl.line.connector1 = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a bottom border. + /// + /// Any corners and intersections which were set will be overridden. + pub fn bottom(mut self, c: char) -> Style + where + for<'a> &'a mut VLines: IntoIterator, + { + self.borders.bottom = Some(c); + + if self.borders.has_left() { + self.borders.bottom_left = Some(c); + } + + if self.borders.has_right() { + self.borders.bottom_right = Some(c); + } + + if self.borders.has_vertical() { + self.borders.bottom_intersection = Some(c); + } + + for vl in &mut self.verticals { + vl.line.connector2 = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a left border. + /// + /// Any corners and intersections which were set will be overridden. + pub fn left(mut self, c: char) -> Style + where + for<'a> &'a mut HLines: IntoIterator, + { + self.borders.left = Some(c); + + if self.borders.has_top() { + self.borders.top_left = Some(c); + } + + if self.borders.has_bottom() { + self.borders.bottom_left = Some(c); + } + + if self.borders.has_horizontal() { + self.borders.left_intersection = Some(c); + } + + for hl in &mut self.horizontals { + hl.line.connector1 = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a right border. + /// + /// Any corners and intersections which were set will be overridden. + pub fn right(mut self, c: char) -> Style + where + for<'a> &'a mut HLines: IntoIterator, + { + self.borders.right = Some(c); + + if self.borders.has_top() { + self.borders.top_right = Some(c); + } + + if self.borders.has_bottom() { + self.borders.bottom_right = Some(c); + } + + if self.borders.has_horizontal() { + self.borders.right_intersection = Some(c); + } + + for hl in &mut self.horizontals { + hl.line.connector2 = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a horizontal split line. + /// + /// Any corners and intersections which were set will be overridden. + pub fn horizontal(mut self, c: char) -> Style + where + for<'a> &'a mut VLines: IntoIterator, + { + self.borders.horizontal = Some(c); + + if self.borders.has_vertical() { + self.borders.intersection = Some(c); + } + + if self.borders.has_left() { + self.borders.left_intersection = Some(c); + } + + if self.borders.has_right() { + self.borders.right_intersection = Some(c); + } + + for vl in &mut self.verticals { + vl.line.intersection = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a vertical split line. + /// + /// Any corners and intersections which were set will be overridden. + pub fn vertical(mut self, c: char) -> Style + where + for<'a> &'a mut HLines: IntoIterator, + { + self.borders.vertical = Some(c); + + if self.borders.has_horizontal() { + self.borders.intersection = Some(c); + } + + if self.borders.has_top() { + self.borders.top_intersection = Some(c); + } + + if self.borders.has_bottom() { + self.borders.bottom_intersection = Some(c); + } + + for hl in &mut self.horizontals { + hl.line.intersection = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Set border horizontal lines. + /// + /// # Example + /// + #[cfg_attr(feature = "derive", doc = "```")] + #[cfg_attr(not(feature = "derive"), doc = "```ignore")] + /// use tabled::{settings::style::{Style, HorizontalLine, Line}, Table}; + /// + /// let table = Table::new((0..3).map(|i| ("Hello", i))) + /// .with(Style::rounded().horizontals((1..4).map(|i| HorizontalLine::new(i, Line::filled('#'))))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "╭───────┬─────╮\n", + /// "│ &str │ i32 │\n", + /// "###############\n", + /// "│ Hello │ 0 │\n", + /// "###############\n", + /// "│ Hello │ 1 │\n", + /// "###############\n", + /// "│ Hello │ 2 │\n", + /// "╰───────┴─────╯", + /// ) + /// ) + /// ``` + pub fn horizontals(self, lines: NewLines) -> Style + where + NewLines: IntoIterator + Clone, + { + Style::new(self.borders, lines, self.verticals) + } + + /// Set border vertical lines. + /// + /// # Example + /// + #[cfg_attr(feature = "derive", doc = "```")] + #[cfg_attr(not(feature = "derive"), doc = "```ignore")] + /// use tabled::{Table, settings::style::{Style, VerticalLine, Line}}; + /// + /// let table = Table::new((0..3).map(|i| ("Hello", i))) + /// .with(Style::rounded().verticals((0..3).map(|i| VerticalLine::new(i, Line::filled('#'))))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "#───────#─────#\n", + /// "# &str # i32 #\n", + /// "├───────┼─────┤\n", + /// "# Hello # 0 #\n", + /// "# Hello # 1 #\n", + /// "# Hello # 2 #\n", + /// "#───────#─────#", + /// ) + /// ) + /// ``` + pub fn verticals(self, lines: NewLines) -> Style + where + NewLines: IntoIterator + Clone, + { + Style::new(self.borders, self.horizontals, lines) + } + + /// Removes all horizontal lines set by [`Style::horizontals`] + pub fn remove_horizontals(self) -> Style, VLines> { + Style::new(self.borders, [], self.verticals) + } + + /// Removes all verticals lines set by [`Style::verticals`] + pub fn remove_verticals(self) -> Style> { + Style::new(self.borders, self.horizontals, []) + } +} + +impl Style { + /// Sets a top left corner. + pub fn corner_top_left(mut self, c: char) -> Self { + self.borders.top_left = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl Style { + /// Sets a top right corner. + pub fn corner_top_right(mut self, c: char) -> Self { + self.borders.top_right = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl Style { + /// Sets a bottom right corner. + pub fn corner_bottom_right(mut self, c: char) -> Self { + self.borders.bottom_right = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl Style { + /// Sets a bottom left corner. + pub fn corner_bottom_left(mut self, c: char) -> Self { + self.borders.bottom_left = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl Style { + /// Sets a left intersection char. + pub fn intersection_left(mut self, c: char) -> Self { + self.borders.left_intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl Style { + /// Sets a right intersection char. + pub fn intersection_right(mut self, c: char) -> Self { + self.borders.right_intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl Style { + /// Sets a top intersection char. + pub fn intersection_top(mut self, c: char) -> Self { + self.borders.top_intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl Style { + /// Sets a bottom intersection char. + pub fn intersection_bottom(mut self, c: char) -> Self { + self.borders.bottom_intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl Style { + /// Sets an inner intersection char. + /// A char between horizontal and vertical split lines. + pub fn intersection(mut self, c: char) -> Self { + self.borders.intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl Style { + /// Removes top border. + pub fn remove_top( + mut self, + ) -> Style<(), B, L, R, H, V, HLines, VerticalLineIter> + where + VLines: IntoIterator + Clone, + { + self.borders.top = None; + self.borders.top_intersection = None; + self.borders.top_left = None; + self.borders.top_right = None; + + let iter = VerticalLineIter::new(self.verticals.into_iter(), false, true, false); + Style::new(self.borders, self.horizontals, iter) + } +} + +impl Style { + /// Removes bottom border. + pub fn remove_bottom( + mut self, + ) -> Style> + where + VLines: IntoIterator + Clone, + { + self.borders.bottom = None; + self.borders.bottom_intersection = None; + self.borders.bottom_left = None; + self.borders.bottom_right = None; + + let iter = VerticalLineIter::new(self.verticals.into_iter(), false, false, true); + Style::new(self.borders, self.horizontals, iter) + } +} + +impl Style { + /// Removes left border. + pub fn remove_left( + mut self, + ) -> Style, VLines> + where + HLines: IntoIterator + Clone, + { + self.borders.left = None; + self.borders.left_intersection = None; + self.borders.top_left = None; + self.borders.bottom_left = None; + + let iter = HorizontalLineIter::new(self.horizontals.into_iter(), false, true, false); + Style::new(self.borders, iter, self.verticals) + } +} + +impl Style { + /// Removes right border. + pub fn remove_right( + mut self, + ) -> Style, VLines> + where + HLines: IntoIterator + Clone, + { + self.borders.right = None; + self.borders.right_intersection = None; + self.borders.top_right = None; + self.borders.bottom_right = None; + + let iter = HorizontalLineIter::new(self.horizontals.into_iter(), false, false, true); + Style::new(self.borders, iter, self.verticals) + } +} + +impl Style { + /// Removes horizontal split lines. + /// + /// Not including custom split lines. + pub fn remove_horizontal( + mut self, + ) -> Style> + where + VLines: IntoIterator + Clone, + { + self.borders.horizontal = None; + self.borders.left_intersection = None; + self.borders.right_intersection = None; + self.borders.intersection = None; + + let iter = VerticalLineIter::new(self.verticals.into_iter(), true, false, false); + Style::new(self.borders, self.horizontals, iter) + } +} + +impl Style { + /// Removes vertical split lines. + pub fn remove_vertical( + mut self, + ) -> Style, VLines> + where + HLines: IntoIterator + Clone, + { + self.borders.vertical = None; + self.borders.top_intersection = None; + self.borders.bottom_intersection = None; + self.borders.intersection = None; + + let iter = HorizontalLineIter::new(self.horizontals.into_iter(), true, false, false); + Style::new(self.borders, iter, self.verticals) + } +} + +impl Style { + const fn new(borders: Borders, horizontals: HLines, verticals: VLines) -> Self { + Self { + borders, + horizontals, + verticals, + _top: PhantomData, + _bottom: PhantomData, + _left: PhantomData, + _right: PhantomData, + _horizontal: PhantomData, + _vertical: PhantomData, + } + } + + /// Return borders of a table. + pub const fn get_borders(&self) -> &Borders { + &self.borders + } + + /// Return custom horizontals which were set. + pub const fn get_horizontals(&self) -> &HLines { + &self.horizontals + } + + /// Return custom verticals which were set. + pub const fn get_verticals(&self) -> &VLines { + &self.verticals + } +} + +#[cfg(feature = "std")] +impl TableOption + for Style +where + HLines: IntoIterator + Clone, + VLines: IntoIterator + Clone, +{ + fn change(self, records: &mut I, cfg: &mut ColoredConfig, dimension: &mut D) { + cfg.clear_theme(); + + cfg.set_borders(self.borders); + + for hl in self.horizontals { + hl.change(records, cfg, dimension); + } + + for vl in self.verticals { + vl.change(records, cfg, dimension); + } + } +} + +impl TableOption + for Style +where + HLines: IntoIterator + Clone, +{ + fn change(self, records: &mut I, cfg: &mut CompactConfig, dimension: &mut D) { + *cfg = cfg.set_borders(self.borders); + + let first_line = self.horizontals.into_iter().next(); + if let Some(line) = first_line { + line.change(records, cfg, dimension); + } + } +} + +impl TableOption + for Style +where + HLines: IntoIterator + Clone, +{ + fn change(self, records: &mut I, cfg: &mut CompactMultilineConfig, dimension: &mut D) { + self.change(records, cfg.as_mut(), dimension) + } +} + +/// An iterator which limits [`Line`] influence on iterations over lines for in [`Style`]. +#[derive(Debug, Clone)] +pub struct HorizontalLineIter { + iter: I, + intersection: bool, + left: bool, + right: bool, +} + +impl HorizontalLineIter { + fn new(iter: I, intersection: bool, left: bool, right: bool) -> Self { + Self { + iter, + intersection, + left, + right, + } + } +} + +impl Iterator for HorizontalLineIter +where + I: Iterator, +{ + type Item = HorizontalLine; + + fn next(&mut self) -> Option { + let mut hl = self.iter.next()?; + + if self.intersection { + hl.line.intersection = None; + } + + if self.left { + hl.line.connector1 = None; + } + + if self.right { + hl.line.connector2 = None; + } + + Some(hl) + } +} + +/// An iterator which limits [`Line`] influence on iterations over lines for in [`Style`]. +#[derive(Debug, Clone)] +pub struct VerticalLineIter { + iter: I, + intersection: bool, + top: bool, + bottom: bool, +} + +impl VerticalLineIter { + fn new(iter: I, intersection: bool, top: bool, bottom: bool) -> Self { + Self { + iter, + intersection, + top, + bottom, + } + } +} + +impl Iterator for VerticalLineIter +where + I: Iterator, +{ + type Item = VerticalLine; + + fn next(&mut self) -> Option { + let mut hl = self.iter.next()?; + + if self.intersection { + hl.line.intersection = None; + } + + if self.top { + hl.line.connector1 = None; + } + + if self.bottom { + hl.line.connector2 = None; + } + + Some(hl) + } +} + +const fn create_borders( + top: Line, + bottom: Line, + horizontal: Line, + left: Option, + right: Option, + vertical: Option, +) -> Borders { + Borders { + top: top.main, + bottom: bottom.main, + top_left: top.connector1, + top_right: top.connector2, + bottom_left: bottom.connector1, + bottom_right: bottom.connector2, + top_intersection: top.intersection, + bottom_intersection: bottom.intersection, + left_intersection: horizontal.connector1, + right_intersection: horizontal.connector2, + horizontal: horizontal.main, + intersection: horizontal.intersection, + left, + right, + vertical, + } +} diff --git a/vendor/tabled/src/settings/style/horizontal_line.rs b/vendor/tabled/src/settings/style/horizontal_line.rs new file mode 100644 index 000000000..bbcdc3fb4 --- /dev/null +++ b/vendor/tabled/src/settings/style/horizontal_line.rs @@ -0,0 +1,69 @@ +use crate::{ + grid::config::{CompactConfig, CompactMultilineConfig}, + settings::TableOption, +}; + +#[cfg(feature = "std")] +use crate::grid::config::{ColoredConfig, HorizontalLine as GridLine}; + +use super::Line; + +/// A horizontal split line which can be used to set a border. +#[cfg_attr(not(feature = "std"), allow(dead_code))] +#[derive(Debug, Clone)] +pub struct HorizontalLine { + pub(super) index: usize, + pub(super) line: Line, +} + +impl HorizontalLine { + /// Creates a new horizontal split line. + pub const fn new(index: usize, line: Line) -> Self { + Self { index, line } + } + + /// Sets a horizontal character. + pub const fn main(mut self, c: Option) -> Self { + self.line.main = c; + self + } + + /// Sets a vertical intersection character. + pub const fn intersection(mut self, c: Option) -> Self { + self.line.intersection = c; + self + } + + /// Sets a left character. + pub const fn left(mut self, c: Option) -> Self { + self.line.connector1 = c; + self + } + + /// Sets a right character. + pub const fn right(mut self, c: Option) -> Self { + self.line.connector2 = c; + self + } +} + +#[cfg(feature = "std")] +impl TableOption for HorizontalLine { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + cfg.insert_horizontal_line(self.index, GridLine::from(self.line)) + } +} + +impl TableOption for HorizontalLine { + fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { + if self.index == 1 { + *cfg = cfg.set_first_horizontal_line(papergrid::config::Line::from(self.line)); + } + } +} + +impl TableOption for HorizontalLine { + fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) { + self.change(records, cfg.as_mut(), dimension) + } +} diff --git a/vendor/tabled/src/settings/style/line.rs b/vendor/tabled/src/settings/style/line.rs new file mode 100644 index 000000000..10b3895c4 --- /dev/null +++ b/vendor/tabled/src/settings/style/line.rs @@ -0,0 +1,91 @@ +#[cfg(feature = "std")] +use crate::grid::config::{HorizontalLine, VerticalLine}; + +/// The structure represent a vertical or horizontal line. +#[derive(Debug, Default, Clone, Copy)] +pub struct Line { + pub(crate) main: Option, + pub(crate) intersection: Option, + pub(crate) connector1: Option, + pub(crate) connector2: Option, +} + +impl Line { + /// Creates a new [`Line`] object. + pub const fn new( + main: Option, + intersection: Option, + connector1: Option, + connector2: Option, + ) -> Self { + Self { + main, + intersection, + connector1, + connector2, + } + } + + /// Creates a new [`Line`] object with all chars set. + pub const fn full(main: char, intersection: char, connector1: char, connector2: char) -> Self { + Self::new( + Some(main), + Some(intersection), + Some(connector1), + Some(connector2), + ) + } + + /// Creates a new [`Line`] object with all chars set to the provided one. + pub const fn filled(c: char) -> Self { + Self::full(c, c, c, c) + } + + /// Creates a new [`Line`] object with all chars not set. + pub const fn empty() -> Self { + Self::new(None, None, None, None) + } + + /// Checks if the line has nothing set. + pub const fn is_empty(&self) -> bool { + self.main.is_none() + && self.intersection.is_none() + && self.connector1.is_none() + && self.connector2.is_none() + } +} + +#[cfg(feature = "std")] +impl From for HorizontalLine { + fn from(l: Line) -> Self { + Self { + main: l.main, + intersection: l.intersection, + left: l.connector1, + right: l.connector2, + } + } +} + +#[cfg(feature = "std")] +impl From for VerticalLine { + fn from(l: Line) -> Self { + Self { + main: l.main, + intersection: l.intersection, + top: l.connector1, + bottom: l.connector2, + } + } +} + +impl From for papergrid::config::Line { + fn from(l: Line) -> Self { + Self { + main: l.main.unwrap_or(' '), + intersection: l.intersection, + connect1: l.connector1, + connect2: l.connector2, + } + } +} diff --git a/vendor/tabled/src/settings/style/mod.rs b/vendor/tabled/src/settings/style/mod.rs new file mode 100644 index 000000000..b8d7f688d --- /dev/null +++ b/vendor/tabled/src/settings/style/mod.rs @@ -0,0 +1,127 @@ +//! This module contains a list of primitives which can be applied to change [`Table`] style. +//! +//! ## [`Style`] +//! +//! It is responsible for a table border style. +//! An individual cell border can be set by [`Border`]. +//! +//! ### Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::Style}; +//! +//! let data = vec!["Hello", "2022"]; +//! let mut table = Table::new(&data); +//! table.with(Style::psql()); +//! +//! assert_eq!( +//! table.to_string(), +//! concat!( +//! " &str \n", +//! "-------\n", +//! " Hello \n", +//! " 2022 ", +//! ) +//! ) +//! ``` +//! +//! ## [`BorderText`] +//! +//! It's used to override a border with a custom text. +//! +//! ### Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::style::{BorderText, Style}}; +//! +//! let data = vec!["Hello", "2022"]; +//! let table = Table::new(&data) +//! .with(Style::psql()) +//! .with(BorderText::new("Santa").horizontal(1)) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! " &str \n", +//! "Santa--\n", +//! " Hello \n", +//! " 2022 ", +//! ) +//! ) +//! ``` +//! +//! ## [`Border`] +//! +//! [`Border`] can be used to modify cell's borders. +//! +//! It's possible to set a collored border when `color` feature is on. +//! +//! ### Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::{Modify, Style}}; +//! +//! let data = vec!["Hello", "2022"]; +//! let table = Table::new(&data) +//! .with(Style::psql()) +//! .with(Modify::new((0, 0)).with(Style::modern().get_frame())) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "┌───────┐\n", +//! "│ &str │\n", +//! "└───────┘\n", +//! " Hello \n", +//! " 2022 ", +//! ) +//! ) +//! ``` +//! +//! ## [`RawStyle`] +//! +//! A different representation of [`Style`]. +//! With no checks in place. +//! +//! It also contains a list of types to support colors. +//! +//! [`Table`]: crate::Table +//! [`BorderText`]: crate::settings::style::BorderText +//! [`RawStyle`]: crate::settings::style::RawStyle + +#[cfg(feature = "std")] +mod border; +#[cfg(feature = "std")] +mod border_char; +#[cfg(feature = "std")] +mod border_color; +#[cfg(feature = "std")] +mod border_text; +#[cfg(feature = "std")] +mod offset; +#[cfg(feature = "std")] +mod raw_style; +#[cfg(feature = "std")] +mod span_border_correction; + +mod builder; +mod horizontal_line; +mod line; +mod vertical_line; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use self::{ + border::Border, border_char::BorderChar, border_color::BorderColor, border_text::BorderText, + offset::Offset, raw_style::RawStyle, span_border_correction::BorderSpanCorrection, +}; + +pub use builder::{HorizontalLineIter, On, Style, VerticalLineIter}; +pub use horizontal_line::HorizontalLine; +pub use line::Line; +pub use vertical_line::VerticalLine; diff --git a/vendor/tabled/src/settings/style/offset.rs b/vendor/tabled/src/settings/style/offset.rs new file mode 100644 index 000000000..3f8a4f85e --- /dev/null +++ b/vendor/tabled/src/settings/style/offset.rs @@ -0,0 +1,19 @@ +use crate::grid::config; + +/// The structure represents an offset in a text. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Offset { + /// An offset from the start. + Begin(usize), + /// An offset from the end. + End(usize), +} + +impl From for config::Offset { + fn from(o: Offset) -> Self { + match o { + Offset::Begin(i) => config::Offset::Begin(i), + Offset::End(i) => config::Offset::End(i), + } + } +} diff --git a/vendor/tabled/src/settings/style/raw_style.rs b/vendor/tabled/src/settings/style/raw_style.rs new file mode 100644 index 000000000..eedb48e79 --- /dev/null +++ b/vendor/tabled/src/settings/style/raw_style.rs @@ -0,0 +1,438 @@ +//! This module contains [`RawStyle`] structure, which is analogues to [`Style`] but not generic, +//! so sometimes it can be used more conviently. + +use std::collections::HashMap; + +use crate::{ + grid::{color::AnsiColor, config, config::Borders, config::ColoredConfig, records::Records}, + settings::{Color, TableOption}, +}; + +use super::{Border, HorizontalLine, Line, Style, VerticalLine}; + +/// A raw style data, which can be produced safely from [`Style`]. +/// +/// It can be useful in order to not have a generics and be able to use it as a variable more conveniently. +#[derive(Default, Debug, Clone)] +pub struct RawStyle { + borders: Borders, + colors: Borders>, + horizontals: HashMap, + verticals: HashMap, +} + +impl RawStyle { + /// Set a top border character. + pub fn set_top(&mut self, s: Option) -> &mut Self { + self.borders.top = s; + self + } + + /// Set a top border color. + pub fn set_color_top(&mut self, color: Color) -> &mut Self { + self.colors.top = Some(color.into()); + self + } + + /// Set a bottom border character. + pub fn set_bottom(&mut self, s: Option) -> &mut Self { + self.borders.bottom = s; + self + } + + /// Set a bottom border color. + pub fn set_color_bottom(&mut self, color: Color) -> &mut Self { + self.colors.bottom = Some(color.into()); + self + } + + /// Set a left border character. + pub fn set_left(&mut self, s: Option) -> &mut Self { + self.borders.left = s; + self + } + + /// Set a left border color. + pub fn set_color_left(&mut self, color: Color) -> &mut Self { + self.colors.left = Some(color.into()); + self + } + + /// Set a right border character. + pub fn set_right(&mut self, s: Option) -> &mut Self { + self.borders.right = s; + self + } + + /// Set a right border color. + pub fn set_color_right(&mut self, color: Color) -> &mut Self { + self.colors.right = Some(color.into()); + self + } + + /// Set a top intersection character. + pub fn set_intersection_top(&mut self, s: Option) -> &mut Self { + self.borders.top_intersection = s; + self + } + + /// Set a top intersection color. + pub fn set_color_intersection_top(&mut self, color: Color) -> &mut Self { + self.colors.top_intersection = Some(color.into()); + self + } + + /// Set a bottom intersection character. + pub fn set_intersection_bottom(&mut self, s: Option) -> &mut Self { + self.borders.bottom_intersection = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_intersection_bottom(&mut self, color: Color) -> &mut Self { + self.colors.bottom_intersection = Some(color.into()); + self + } + + /// Set a left split character. + pub fn set_intersection_left(&mut self, s: Option) -> &mut Self { + self.borders.left_intersection = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_intersection_left(&mut self, color: Color) -> &mut Self { + self.colors.left_intersection = Some(color.into()); + self + } + + /// Set a right split character. + pub fn set_intersection_right(&mut self, s: Option) -> &mut Self { + self.borders.right_intersection = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_intersection_right(&mut self, color: Color) -> &mut Self { + self.colors.right_intersection = Some(color.into()); + self + } + + /// Set an internal character. + pub fn set_intersection(&mut self, s: Option) -> &mut Self { + self.borders.intersection = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_intersection(&mut self, color: Color) -> &mut Self { + self.colors.intersection = Some(color.into()); + self + } + + /// Set a vertical character. + pub fn set_vertical(&mut self, s: Option) -> &mut Self { + self.borders.vertical = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_vertical(&mut self, color: Color) -> &mut Self { + self.colors.vertical = Some(color.into()); + self + } + + /// Set a horizontal character. + pub fn set_horizontal(&mut self, s: Option) -> &mut Self { + self.borders.horizontal = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_horizontal(&mut self, color: Color) -> &mut Self { + self.colors.horizontal = Some(color.into()); + self + } + + /// Set a character for a top left corner. + pub fn set_corner_top_left(&mut self, s: Option) -> &mut Self { + self.borders.top_left = s; + self + } + /// Set a bottom intersection color. + pub fn set_color_corner_top_left(&mut self, color: Color) -> &mut Self { + self.colors.top_left = Some(color.into()); + self + } + + /// Set a character for a top right corner. + pub fn set_corner_top_right(&mut self, s: Option) -> &mut Self { + self.borders.top_right = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_corner_top_right(&mut self, color: Color) -> &mut Self { + self.colors.top_right = Some(color.into()); + self + } + + /// Set a character for a bottom left corner. + pub fn set_corner_bottom_left(&mut self, s: Option) -> &mut Self { + self.borders.bottom_left = s; + self + } + /// Set a bottom intersection color. + pub fn set_color_corner_bottom_left(&mut self, color: Color) -> &mut Self { + self.colors.bottom_left = Some(color.into()); + self + } + + /// Set a character for a bottom right corner. + pub fn set_corner_bottom_right(&mut self, s: Option) -> &mut Self { + self.borders.bottom_right = s; + self + } + /// Set a bottom intersection color. + pub fn set_color_corner_bottom_right(&mut self, color: Color) -> &mut Self { + self.colors.bottom_right = Some(color.into()); + self + } + + /// Set horizontal border lines. + /// + /// # Example + /// + /// ``` + /// use std::collections::HashMap; + /// use tabled::{Table, settings::style::{Style, Line, RawStyle}}; + /// + /// let mut style = RawStyle::from(Style::re_structured_text()); + /// + /// let mut lines = HashMap::new(); + /// lines.insert(1, Style::extended().get_horizontal()); + /// style.set_horizontals(lines); + /// + /// let table = Table::new((0..3).map(|i| ("Hello", i))) + /// .with(style) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// " ======= ===== \n", + /// " &str i32 \n", + /// "╠═══════╬═════╣\n", + /// " Hello 0 \n", + /// " Hello 1 \n", + /// " Hello 2 \n", + /// " ======= ===== ", + /// ), + /// ) + /// ``` + pub fn set_horizontals(&mut self, lines: HashMap) -> &mut Self { + self.horizontals = lines; + self + } + + /// Insert a horizontal line to a specific row location. + pub fn insert_horizontal(&mut self, row: usize, line: Line) -> &mut Self { + let _ = self.horizontals.insert(row, line); + self + } + + /// Insert a horizontal line to a specific row location. + pub fn get_horizontal(&self, row: usize) -> Option { + self.horizontals.get(&row).cloned() + } + + /// Set vertical border lines. + /// + /// # Example + /// + /// ``` + /// use std::collections::HashMap; + /// use tabled::{Table, settings::style::{Style, Line, RawStyle}}; + /// + /// let mut style = RawStyle::from(Style::re_structured_text()); + /// + /// let mut lines = HashMap::new(); + /// lines.insert(1, Style::extended().get_horizontal()); + /// style.set_verticals(lines); + /// + /// let table = Table::new((0..3).map(|i| ("Hello", i))) + /// .with(style) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "=======╠=====\n", + /// " &str ═ i32 \n", + /// "======= =====\n", + /// " Hello ═ 0 \n", + /// " Hello ═ 1 \n", + /// " Hello ═ 2 \n", + /// "=======╣=====", + /// ), + /// ) + /// ``` + pub fn set_verticals(&mut self, lines: HashMap) -> &mut Self { + self.verticals = lines; + self + } + + /// Insert a vertical line into specific column location. + pub fn insert_vertical(&mut self, column: usize, line: Line) -> &mut Self { + let _ = self.verticals.insert(column, line); + self + } + + /// Get a left char. + pub fn get_left(&self) -> Option { + self.borders.left + } + + /// Get a left intersection char. + pub fn get_left_intersection(&self) -> Option { + self.borders.left_intersection + } + + /// Get a right char. + pub fn get_right(&self) -> Option { + self.borders.right + } + + /// Get a right intersection char. + pub fn get_right_intersection(&self) -> Option { + self.borders.right_intersection + } + + /// Get a top char. + pub fn get_top(&self) -> Option { + self.borders.top + } + + /// Get a top left char. + pub fn get_top_left(&self) -> Option { + self.borders.top_left + } + + /// Get a top right char. + pub fn get_top_right(&self) -> Option { + self.borders.top_right + } + + /// Get a top intersection char. + pub fn get_top_intersection(&self) -> Option { + self.borders.top_intersection + } + + /// Get a bottom intersection char. + pub fn get_bottom(&self) -> Option { + self.borders.bottom + } + + /// Get a bottom intersection char. + pub fn get_bottom_left(&self) -> Option { + self.borders.bottom_left + } + + /// Get a bottom intersection char. + pub fn get_bottom_right(&self) -> Option { + self.borders.bottom_right + } + + /// Get a bottom intersection char. + pub fn get_bottom_intersection(&self) -> Option { + self.borders.bottom_intersection + } + + /// Returns an outer border of the style. + pub fn get_frame(&self) -> Border { + Border::from(crate::grid::config::Border { + top: self.borders.top, + bottom: self.borders.bottom, + left: self.borders.left, + right: self.borders.right, + left_top_corner: self.borders.top_left, + right_top_corner: self.borders.top_right, + left_bottom_corner: self.borders.bottom_left, + right_bottom_corner: self.borders.bottom_right, + }) + } + + /// Returns an general borders configuration of the style. + pub fn get_borders(&self) -> Borders { + self.borders + } +} + +impl From> for RawStyle { + fn from(borders: Borders) -> Self { + Self { + borders, + horizontals: HashMap::new(), + verticals: HashMap::new(), + colors: Borders::default(), + } + } +} + +impl TableOption for RawStyle +where + R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) { + (&self).change(records, cfg, dimension) + } +} + +impl TableOption for &RawStyle { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + cfg.clear_theme(); + + cfg.set_borders(self.borders); + + for (&row, line) in &self.horizontals { + cfg.insert_horizontal_line(row, config::HorizontalLine::from(*line)); + } + + for (&col, line) in &self.verticals { + cfg.insert_vertical_line(col, config::VerticalLine::from(*line)); + } + + if !self.colors.is_empty() { + cfg.set_borders_color(self.colors.clone()); + } + } +} + +impl From> for RawStyle +where + HLines: IntoIterator + Clone, + VLines: IntoIterator + Clone, +{ + fn from(style: Style) -> Self { + let horizontals = style + .get_horizontals() + .clone() + .into_iter() + .map(|hr| (hr.index, hr.line)) + .collect(); + + let verticals = style + .get_verticals() + .clone() + .into_iter() + .map(|hr| (hr.index, hr.line)) + .collect(); + + Self { + borders: *style.get_borders(), + horizontals, + verticals, + colors: Borders::default(), + } + } +} diff --git a/vendor/tabled/src/settings/style/span_border_correction.rs b/vendor/tabled/src/settings/style/span_border_correction.rs new file mode 100644 index 000000000..665a12788 --- /dev/null +++ b/vendor/tabled/src/settings/style/span_border_correction.rs @@ -0,0 +1,216 @@ +//! This module contains [`BorderSpanCorrection`] structure, which can be useful when [`Span`] is used, and +//! you want to fix the intersections symbols which are left intact by default. +//! +//! [`Span`]: crate::settings::span::Span + +use crate::{ + grid::{ + config::{ColoredConfig, Position, SpannedConfig}, + records::{ExactRecords, Records}, + }, + settings::TableOption, +}; + +/// A correctness function of style for [`Table`] which has [`Span`]s. +/// +/// Try to fix the style when table contains spans. +/// +/// By default [`Style`] doesn't implies any logic to better render split lines when +/// [`Span`] is used. +/// +/// So this function can be used to set the split lines in regard of spans used. +/// +/// # Example +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{ +/// Modify, style::{Style, BorderSpanCorrection}, +/// Format, Span, object::Cell +/// } +/// }; +/// +/// let data = vec![ +/// ("09", "June", "2022"), +/// ("10", "July", "2022"), +/// ]; +/// +/// let mut table = Table::new(data); +/// table.with(Modify::new((0, 0)).with("date").with(Span::column(3))); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "+----+------+------+\n", +/// "| date |\n", +/// "+----+------+------+\n", +/// "| 09 | June | 2022 |\n", +/// "+----+------+------+\n", +/// "| 10 | July | 2022 |\n", +/// "+----+------+------+", +/// ) +/// ); +/// +/// table.with(BorderSpanCorrection); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "+------------------+\n", +/// "| date |\n", +/// "+----+------+------+\n", +/// "| 09 | June | 2022 |\n", +/// "+----+------+------+\n", +/// "| 10 | July | 2022 |\n", +/// "+----+------+------+", +/// ) +/// ); +/// ``` +/// See [`BorderSpanCorrection`]. +/// +/// [`Table`]: crate::Table +/// [`Span`]: crate::settings::span::Span +/// [`Style`]: crate::settings::Style +/// [`Style::correct_spans`]: crate::settings::style::BorderSpanCorrection +#[derive(Debug)] +pub struct BorderSpanCorrection; + +impl TableOption for BorderSpanCorrection +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let shape = (records.count_rows(), records.count_columns()); + correct_span_styles(cfg, shape); + } +} + +fn correct_span_styles(cfg: &mut SpannedConfig, shape: (usize, usize)) { + for ((row, c), span) in cfg.get_column_spans() { + for col in c..c + span { + if col == 0 { + continue; + } + + let is_first = col == c; + let has_up = row > 0 && has_left(cfg, (row - 1, col), shape); + let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col), shape); + + let borders = cfg.get_borders(); + + let mut border = cfg.get_border((row, col), shape); + + let has_top_border = border.left_top_corner.is_some() && border.top.is_some(); + if has_top_border { + if has_up && is_first { + border.left_top_corner = borders.intersection; + } else if has_up { + border.left_top_corner = borders.bottom_intersection; + } else if is_first { + border.left_top_corner = borders.top_intersection; + } else { + border.left_top_corner = border.top; + } + } + + let has_bottom_border = border.left_bottom_corner.is_some() && border.bottom.is_some(); + if has_bottom_border { + if has_down && is_first { + border.left_bottom_corner = borders.intersection; + } else if has_down { + border.left_bottom_corner = borders.top_intersection; + } else if is_first { + border.left_bottom_corner = borders.bottom_intersection; + } else { + border.left_bottom_corner = border.bottom; + } + } + + cfg.set_border((row, col), border); + } + } + + for ((r, col), span) in cfg.get_row_spans() { + for row in r + 1..r + span { + let mut border = cfg.get_border((row, col), shape); + let borders = cfg.get_borders(); + + let has_left_border = border.left_top_corner.is_some(); + if has_left_border { + let has_left = col > 0 && has_top(cfg, (row, col - 1), shape); + if has_left { + border.left_top_corner = borders.right_intersection; + } else { + border.left_top_corner = borders.vertical; + } + } + + let has_right_border = border.right_top_corner.is_some(); + if has_right_border { + let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1), shape); + if has_right { + border.right_top_corner = borders.left_intersection; + } else { + border.right_top_corner = borders.vertical; + } + } + + cfg.set_border((row, col), border); + } + } + + let cells = iter_totaly_spanned_cells(cfg, shape).collect::>(); + for (row, col) in cells { + if row == 0 { + continue; + } + + let mut border = cfg.get_border((row, col), shape); + let borders = cfg.get_borders(); + + let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1), shape); + let has_up = has_left(cfg, (row - 1, col), shape); + if has_up && !has_right { + border.right_top_corner = borders.right_intersection; + } + + let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col), shape); + if has_down { + border.left_bottom_corner = borders.top_intersection; + } + + cfg.set_border((row, col), border); + } +} + +fn has_left(cfg: &SpannedConfig, pos: Position, shape: (usize, usize)) -> bool { + if cfg.is_cell_covered_by_both_spans(pos) || cfg.is_cell_covered_by_column_span(pos) { + return false; + } + + let border = cfg.get_border(pos, shape); + border.left.is_some() || border.left_top_corner.is_some() || border.left_bottom_corner.is_some() +} + +fn has_top(cfg: &SpannedConfig, pos: Position, shape: (usize, usize)) -> bool { + if cfg.is_cell_covered_by_both_spans(pos) || cfg.is_cell_covered_by_row_span(pos) { + return false; + } + + let border = cfg.get_border(pos, shape); + border.top.is_some() || border.left_top_corner.is_some() || border.right_top_corner.is_some() +} + +fn iter_totaly_spanned_cells( + cfg: &SpannedConfig, + shape: (usize, usize), +) -> impl Iterator + '_ { + // todo: can be optimized + let (count_rows, count_cols) = shape; + (0..count_rows).flat_map(move |row| { + (0..count_cols) + .map(move |col| (row, col)) + .filter(move |&p| cfg.is_cell_covered_by_both_spans(p)) + }) +} diff --git a/vendor/tabled/src/settings/style/vertical_line.rs b/vendor/tabled/src/settings/style/vertical_line.rs new file mode 100644 index 000000000..adbef2c82 --- /dev/null +++ b/vendor/tabled/src/settings/style/vertical_line.rs @@ -0,0 +1,50 @@ +#[cfg(feature = "std")] +use crate::grid::config::{ColoredConfig, VerticalLine as VLine}; + +use super::Line; + +/// A horizontal split line which can be used to set a border. +#[cfg_attr(not(feature = "std"), allow(dead_code))] +#[derive(Debug, Clone)] +pub struct VerticalLine { + pub(crate) index: usize, + pub(crate) line: Line, +} + +impl VerticalLine { + /// Creates a new horizontal split line. + pub const fn new(index: usize, line: Line) -> Self { + Self { index, line } + } + + /// Sets a horizontal character. + pub const fn main(mut self, c: Option) -> Self { + self.line.main = c; + self + } + + /// Sets a vertical intersection character. + pub const fn intersection(mut self, c: Option) -> Self { + self.line.intersection = c; + self + } + + /// Sets a top character. + pub const fn top(mut self, c: Option) -> Self { + self.line.connector1 = c; + self + } + + /// Sets a bottom character. + pub const fn bottom(mut self, c: Option) -> Self { + self.line.connector2 = c; + self + } +} + +#[cfg(feature = "std")] +impl crate::settings::TableOption for VerticalLine { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + cfg.insert_vertical_line(self.index, VLine::from(self.line)); + } +} -- cgit v1.2.3