summaryrefslogtreecommitdiffstats
path: root/vendor/papergrid/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/papergrid/src')
-rw-r--r--vendor/papergrid/src/color/ansi_color.rs86
-rw-r--r--vendor/papergrid/src/color/mod.rs51
-rw-r--r--vendor/papergrid/src/color/static_color.rs49
-rw-r--r--vendor/papergrid/src/colors.rs89
-rw-r--r--vendor/papergrid/src/config/alignment.rs21
-rw-r--r--vendor/papergrid/src/config/border.rs142
-rw-r--r--vendor/papergrid/src/config/borders.rs152
-rw-r--r--vendor/papergrid/src/config/compact/mod.rs141
-rw-r--r--vendor/papergrid/src/config/entity.rs120
-rw-r--r--vendor/papergrid/src/config/indent.rs31
-rw-r--r--vendor/papergrid/src/config/line.rs42
-rw-r--r--vendor/papergrid/src/config/mod.rs23
-rw-r--r--vendor/papergrid/src/config/position.rs13
-rw-r--r--vendor/papergrid/src/config/sides.rs37
-rw-r--r--vendor/papergrid/src/config/spanned/borders_config.rs486
-rw-r--r--vendor/papergrid/src/config/spanned/entity_map.rs108
-rw-r--r--vendor/papergrid/src/config/spanned/formatting.rs21
-rw-r--r--vendor/papergrid/src/config/spanned/mod.rs887
-rw-r--r--vendor/papergrid/src/config/spanned/offset.rs8
-rw-r--r--vendor/papergrid/src/dimension/compact.rs121
-rw-r--r--vendor/papergrid/src/dimension/mod.rs44
-rw-r--r--vendor/papergrid/src/dimension/spanned.rs329
-rw-r--r--vendor/papergrid/src/dimension/spanned_vec_records.rs332
-rw-r--r--vendor/papergrid/src/grid/compact.rs659
-rw-r--r--vendor/papergrid/src/grid/iterable.rs1358
-rw-r--r--vendor/papergrid/src/grid/mod.rs9
-rw-r--r--vendor/papergrid/src/grid/peekable.rs976
-rw-r--r--vendor/papergrid/src/lib.rs87
-rw-r--r--vendor/papergrid/src/records/exact_records.rs31
-rw-r--r--vendor/papergrid/src/records/into_records.rs33
-rw-r--r--vendor/papergrid/src/records/iter_records.rs87
-rw-r--r--vendor/papergrid/src/records/mod.rs31
-rw-r--r--vendor/papergrid/src/records/peekable_records.rs52
-rw-r--r--vendor/papergrid/src/records/vec_records/cell.rs19
-rw-r--r--vendor/papergrid/src/records/vec_records/cell_info.rs170
-rw-r--r--vendor/papergrid/src/records/vec_records/mod.rs124
-rw-r--r--vendor/papergrid/src/util/mod.rs3
-rw-r--r--vendor/papergrid/src/util/string.rs270
38 files changed, 7242 insertions, 0 deletions
diff --git a/vendor/papergrid/src/color/ansi_color.rs b/vendor/papergrid/src/color/ansi_color.rs
new file mode 100644
index 000000000..6fcf4f832
--- /dev/null
+++ b/vendor/papergrid/src/color/ansi_color.rs
@@ -0,0 +1,86 @@
+use std::{
+ borrow::Cow,
+ fmt::{self, Write},
+};
+
+use super::{Color, StaticColor};
+
+/// The structure represents a ANSI color by suffix and prefix.
+#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct AnsiColor<'a> {
+ prefix: Cow<'a, str>,
+ suffix: Cow<'a, str>,
+}
+
+impl<'a> AnsiColor<'a> {
+ /// Constructs a new instance with suffix and prefix.
+ ///
+ /// They are not checked so you should make sure you provide correct ANSI.
+ /// Otherwise you may want to use [`TryFrom`].
+ ///
+ /// [`TryFrom`]: std::convert::TryFrom
+ pub const fn new(prefix: Cow<'a, str>, suffix: Cow<'a, str>) -> Self {
+ Self { prefix, suffix }
+ }
+}
+
+impl AnsiColor<'_> {
+ /// Gets a reference to a prefix.
+ pub fn get_prefix(&self) -> &str {
+ &self.prefix
+ }
+
+ /// Gets a reference to a suffix.
+ pub fn get_suffix(&self) -> &str {
+ &self.suffix
+ }
+}
+
+impl Color for AnsiColor<'_> {
+ fn fmt_prefix<W: Write>(&self, f: &mut W) -> fmt::Result {
+ f.write_str(&self.prefix)
+ }
+
+ fn fmt_suffix<W: Write>(&self, f: &mut W) -> fmt::Result {
+ f.write_str(&self.suffix)
+ }
+}
+
+#[cfg(feature = "color")]
+impl std::convert::TryFrom<&str> for AnsiColor<'static> {
+ type Error = ();
+
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ parse_ansi_color(value).ok_or(())
+ }
+}
+
+#[cfg(feature = "color")]
+impl std::convert::TryFrom<String> for AnsiColor<'static> {
+ type Error = ();
+
+ fn try_from(value: String) -> Result<Self, Self::Error> {
+ Self::try_from(value.as_str())
+ }
+}
+
+#[cfg(feature = "color")]
+fn parse_ansi_color(s: &str) -> Option<AnsiColor<'static>> {
+ let mut blocks = ansi_str::get_blocks(s);
+ let block = blocks.next()?;
+ let style = block.style();
+
+ let start = style.start().to_string();
+ let end = style.end().to_string();
+
+ Some(AnsiColor::new(start.into(), end.into()))
+}
+
+impl From<StaticColor> for AnsiColor<'static> {
+ fn from(value: StaticColor) -> Self {
+ Self::new(
+ Cow::Borrowed(value.get_prefix()),
+ Cow::Borrowed(value.get_suffix()),
+ )
+ }
+}
diff --git a/vendor/papergrid/src/color/mod.rs b/vendor/papergrid/src/color/mod.rs
new file mode 100644
index 000000000..e4618b750
--- /dev/null
+++ b/vendor/papergrid/src/color/mod.rs
@@ -0,0 +1,51 @@
+//! A module which contains [`Color`] trait and its implementation [`AnsiColor`].
+
+#[cfg(feature = "std")]
+mod ansi_color;
+mod static_color;
+
+#[cfg(feature = "std")]
+pub use ansi_color::AnsiColor;
+
+pub use static_color::StaticColor;
+
+use core::fmt::{self, Write};
+
+#[allow(unreachable_pub)]
+/// A trait which prints an ANSI prefix and suffix.
+pub trait Color {
+ /// Print ANSI prefix.
+ fn fmt_prefix<W: Write>(&self, f: &mut W) -> fmt::Result;
+
+ /// Print ANSI suffix.
+ fn fmt_suffix<W: Write>(&self, f: &mut W) -> fmt::Result {
+ f.write_str("\u{1b}[0m")
+ }
+
+ /// Print colored text.
+ ///
+ /// It may not handle `\n` (new lines).
+ fn colorize<W: Write>(&self, f: &mut W, text: &str) -> fmt::Result {
+ self.fmt_prefix(f)?;
+ f.write_str(text)?;
+ self.fmt_suffix(f)?;
+ Ok(())
+ }
+}
+
+impl<C> Color for &C
+where
+ C: Color,
+{
+ fn fmt_prefix<W: Write>(&self, f: &mut W) -> fmt::Result {
+ C::fmt_prefix(self, f)
+ }
+
+ fn fmt_suffix<W: Write>(&self, f: &mut W) -> fmt::Result {
+ C::fmt_suffix(self, f)
+ }
+
+ fn colorize<W: Write>(&self, f: &mut W, text: &str) -> fmt::Result {
+ C::colorize(self, f, text)
+ }
+}
diff --git a/vendor/papergrid/src/color/static_color.rs b/vendor/papergrid/src/color/static_color.rs
new file mode 100644
index 000000000..3f9a38cfd
--- /dev/null
+++ b/vendor/papergrid/src/color/static_color.rs
@@ -0,0 +1,49 @@
+use core::fmt::{self, Write};
+
+use super::Color;
+
+/// The structure represents a ANSI color by suffix and prefix.
+#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
+pub struct StaticColor {
+ prefix: &'static str,
+ suffix: &'static str,
+}
+
+impl StaticColor {
+ /// Constructs a new instance with suffix and prefix.
+ ///
+ /// They are not checked so you should make sure you provide correct ANSI.
+ /// Otherwise you may want to use [`TryFrom`].
+ ///
+ /// [`TryFrom`]: std::convert::TryFrom
+ pub const fn new(prefix: &'static str, suffix: &'static str) -> Self {
+ Self { prefix, suffix }
+ }
+
+ /// Verifies if anything was actually set.
+ pub const fn is_empty(&self) -> bool {
+ self.prefix.is_empty() && self.suffix.is_empty()
+ }
+}
+
+impl StaticColor {
+ /// Gets a reference to a prefix.
+ pub fn get_prefix(&self) -> &'static str {
+ self.prefix
+ }
+
+ /// Gets a reference to a suffix.
+ pub fn get_suffix(&self) -> &'static str {
+ self.suffix
+ }
+}
+
+impl Color for StaticColor {
+ fn fmt_prefix<W: Write>(&self, f: &mut W) -> fmt::Result {
+ f.write_str(self.prefix)
+ }
+
+ fn fmt_suffix<W: Write>(&self, f: &mut W) -> fmt::Result {
+ f.write_str(self.suffix)
+ }
+}
diff --git a/vendor/papergrid/src/colors.rs b/vendor/papergrid/src/colors.rs
new file mode 100644
index 000000000..f671ec005
--- /dev/null
+++ b/vendor/papergrid/src/colors.rs
@@ -0,0 +1,89 @@
+//! A module which contains [Colors] trait and its blanket implementations.
+
+use crate::{color::Color, config::Position};
+
+/// A trait which represents map of colors.
+pub trait Colors {
+ /// Color implementation.
+ type Color: Color;
+
+ /// Returns a color for a given position.
+ fn get_color(&self, pos: (usize, usize)) -> Option<&Self::Color>;
+}
+
+impl<C> Colors for &'_ C
+where
+ C: Colors,
+{
+ type Color = C::Color;
+
+ fn get_color(&self, pos: Position) -> Option<&Self::Color> {
+ C::get_color(self, pos)
+ }
+}
+
+#[cfg(feature = "std")]
+impl<C> Colors for std::collections::HashMap<Position, C>
+where
+ C: Color,
+{
+ type Color = C;
+
+ fn get_color(&self, pos: Position) -> Option<&Self::Color> {
+ self.get(&pos)
+ }
+}
+
+#[cfg(feature = "std")]
+impl<C> Colors for std::collections::BTreeMap<Position, C>
+where
+ C: Color,
+{
+ type Color = C;
+
+ fn get_color(&self, pos: Position) -> Option<&Self::Color> {
+ self.get(&pos)
+ }
+}
+
+#[cfg(feature = "std")]
+impl<C> Colors for crate::config::spanned::EntityMap<Option<C>>
+where
+ C: Color,
+{
+ type Color = C;
+
+ fn get_color(&self, pos: Position) -> Option<&Self::Color> {
+ self.get(pos.into()).as_ref()
+ }
+}
+
+/// The structure represents empty [`Colors`] map.
+#[derive(Debug, Default, Clone)]
+pub struct NoColors;
+
+impl Colors for NoColors {
+ type Color = EmptyColor;
+
+ fn get_color(&self, _: Position) -> Option<&Self::Color> {
+ None
+ }
+}
+
+/// A color which is actually has not value.
+#[derive(Debug)]
+pub struct EmptyColor;
+
+impl Color for EmptyColor {
+ fn fmt_prefix<W: core::fmt::Write>(&self, _: &mut W) -> core::fmt::Result {
+ Ok(())
+ }
+
+ fn colorize<W: core::fmt::Write>(&self, _: &mut W, _: &str) -> core::fmt::Result {
+ Ok(())
+ }
+
+ fn fmt_suffix<W: core::fmt::Write>(&self, _: &mut W) -> core::fmt::Result {
+ Ok(())
+ }
+}
diff --git a/vendor/papergrid/src/config/alignment.rs b/vendor/papergrid/src/config/alignment.rs
new file mode 100644
index 000000000..2b3b5e9db
--- /dev/null
+++ b/vendor/papergrid/src/config/alignment.rs
@@ -0,0 +1,21 @@
+/// [`AlignmentHorizontal`] represents an horizontal alignment of a cell content.
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub enum AlignmentHorizontal {
+ /// Align to the center.
+ Center,
+ /// Align on the left.
+ Left,
+ /// Align on the right.
+ Right,
+}
+
+/// [`AlignmentVertical`] represents an vertical alignment of a cell content.
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub enum AlignmentVertical {
+ /// Align to the center.
+ Center,
+ /// Align to the top.
+ Top,
+ /// Align to the bottom.
+ Bottom,
+}
diff --git a/vendor/papergrid/src/config/border.rs b/vendor/papergrid/src/config/border.rs
new file mode 100644
index 000000000..e803900f7
--- /dev/null
+++ b/vendor/papergrid/src/config/border.rs
@@ -0,0 +1,142 @@
+/// Border is a representation of a cells's borders (left, right, top, bottom, and the corners)
+///
+///
+/// ```text
+/// top border
+/// |
+/// V
+/// corner top left ------> +_______+ <---- corner top left
+/// | |
+/// left border ----------> | cell | <---- right border
+/// | |
+/// corner bottom right --> +_______+ <---- corner bottom right
+/// ^
+/// |
+/// bottom border
+/// ```
+#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
+pub struct Border<T> {
+ /// A character for a top.
+ pub top: Option<T>,
+ /// A character for a bottom.
+ pub bottom: Option<T>,
+ /// A character for a left.
+ pub left: Option<T>,
+ /// A character for a right.
+ pub right: Option<T>,
+ /// A character for a left top corner.
+ pub left_top_corner: Option<T>,
+ /// A character for a left bottom corner.
+ pub left_bottom_corner: Option<T>,
+ /// A character for a right top corner.
+ pub right_top_corner: Option<T>,
+ /// A character for a right bottom corner.
+ pub right_bottom_corner: Option<T>,
+}
+
+impl<T> Border<T> {
+ /// This function constructs a cell borders with all sides set.
+ #[allow(clippy::too_many_arguments)]
+ pub const fn full(
+ top: T,
+ bottom: T,
+ left: T,
+ right: T,
+ top_left: T,
+ top_right: T,
+ bottom_left: T,
+ bottom_right: T,
+ ) -> Self {
+ Self {
+ top: Some(top),
+ bottom: Some(bottom),
+ right: Some(right),
+ right_top_corner: Some(top_right),
+ right_bottom_corner: Some(bottom_right),
+ left: Some(left),
+ left_bottom_corner: Some(bottom_left),
+ left_top_corner: Some(top_left),
+ }
+ }
+
+ /// Checks whether any side is set.
+ pub const fn is_empty(&self) -> bool {
+ self.top.is_none()
+ && self.left_top_corner.is_none()
+ && self.right_top_corner.is_none()
+ && self.bottom.is_none()
+ && self.left_bottom_corner.is_none()
+ && self.left_top_corner.is_none()
+ && self.left.is_none()
+ && self.right.is_none()
+ }
+
+ /// Verifies whether anything is set on the top.
+ pub const fn has_top(&self) -> bool {
+ self.top.is_some() || self.left_top_corner.is_some() || self.right_top_corner.is_some()
+ }
+
+ /// Verifies whether anything is set on the bottom.
+ pub const fn has_bottom(&self) -> bool {
+ self.bottom.is_some()
+ || self.left_bottom_corner.is_some()
+ || self.right_bottom_corner.is_some()
+ }
+
+ /// Verifies whether anything is set on the left.
+ pub const fn has_left(&self) -> bool {
+ self.left.is_some() || self.left_top_corner.is_some() || self.left_bottom_corner.is_some()
+ }
+
+ /// Verifies whether anything is set on the right.
+ pub const fn has_right(&self) -> bool {
+ self.right.is_some()
+ || self.right_top_corner.is_some()
+ || self.right_bottom_corner.is_some()
+ }
+}
+
+impl<T: Copy> Border<T> {
+ /// 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: T) -> Self {
+ Self::full(c, c, c, c, c, c, c, c)
+ }
+}
+
+impl<T: Copy> Border<&T> {
+ /// 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 copied(&self) -> Border<T> {
+ Border {
+ top: self.top.copied(),
+ bottom: self.bottom.copied(),
+ left: self.left.copied(),
+ right: self.right.copied(),
+ left_bottom_corner: self.left_bottom_corner.copied(),
+ left_top_corner: self.left_top_corner.copied(),
+ right_bottom_corner: self.right_bottom_corner.copied(),
+ right_top_corner: self.right_top_corner.copied(),
+ }
+ }
+}
+
+impl<T: Clone> Border<&T> {
+ /// 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 cloned(&self) -> Border<T> {
+ Border {
+ top: self.top.cloned(),
+ bottom: self.bottom.cloned(),
+ left: self.left.cloned(),
+ right: self.right.cloned(),
+ left_bottom_corner: self.left_bottom_corner.cloned(),
+ left_top_corner: self.left_top_corner.cloned(),
+ right_bottom_corner: self.right_bottom_corner.cloned(),
+ right_top_corner: self.right_top_corner.cloned(),
+ }
+ }
+}
diff --git a/vendor/papergrid/src/config/borders.rs b/vendor/papergrid/src/config/borders.rs
new file mode 100644
index 000000000..d242e1f63
--- /dev/null
+++ b/vendor/papergrid/src/config/borders.rs
@@ -0,0 +1,152 @@
+/// Borders represents a Table frame with horizontal and vertical split lines.
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Borders<T> {
+ /// A top horizontal on the frame.
+ pub top: Option<T>,
+ /// A top left on the frame.
+ pub top_left: Option<T>,
+ /// A top right on the frame.
+ pub top_right: Option<T>,
+ /// A top horizontal intersection on the frame.
+ pub top_intersection: Option<T>,
+
+ /// A bottom horizontal on the frame.
+ pub bottom: Option<T>,
+ /// A bottom left on the frame.
+ pub bottom_left: Option<T>,
+ /// A bottom right on the frame.
+ pub bottom_right: Option<T>,
+ /// A bottom horizontal intersection on the frame.
+ pub bottom_intersection: Option<T>,
+
+ /// A horizontal split.
+ pub horizontal: Option<T>,
+ /// A vertical split.
+ pub vertical: Option<T>,
+ /// A top left character on the frame.
+ pub intersection: Option<T>,
+
+ /// A vertical split on the left frame line.
+ pub left: Option<T>,
+ /// A horizontal split on the left frame line.
+ pub left_intersection: Option<T>,
+
+ /// A vertical split on the right frame line.
+ pub right: Option<T>,
+ /// A horizontal split on the right frame line.
+ pub right_intersection: Option<T>,
+}
+
+impl<T> Borders<T> {
+ /// Returns empty borders.
+ pub const fn empty() -> Self {
+ Self {
+ top: None,
+ top_left: None,
+ top_right: None,
+ top_intersection: None,
+ bottom: None,
+ bottom_left: None,
+ bottom_right: None,
+ bottom_intersection: None,
+ horizontal: None,
+ left: None,
+ right: None,
+ vertical: None,
+ left_intersection: None,
+ right_intersection: None,
+ intersection: None,
+ }
+ }
+
+ /// Returns Borders filled in with a supplied value.
+ pub const fn filled(val: T) -> Self
+ where
+ T: Copy,
+ {
+ Self {
+ top: Some(val),
+ top_left: Some(val),
+ top_right: Some(val),
+ top_intersection: Some(val),
+ bottom: Some(val),
+ bottom_left: Some(val),
+ bottom_right: Some(val),
+ bottom_intersection: Some(val),
+ horizontal: Some(val),
+ left: Some(val),
+ right: Some(val),
+ vertical: Some(val),
+ left_intersection: Some(val),
+ right_intersection: Some(val),
+ intersection: Some(val),
+ }
+ }
+
+ /// A verification whether any border was set.
+ pub const fn is_empty(&self) -> bool {
+ !(self.top.is_some()
+ || self.top_left.is_some()
+ || self.top_right.is_some()
+ || self.top_intersection.is_some()
+ || self.bottom.is_some()
+ || self.bottom_left.is_some()
+ || self.bottom_right.is_some()
+ || self.bottom_intersection.is_some()
+ || self.horizontal.is_some()
+ || self.left.is_some()
+ || self.right.is_some()
+ || self.vertical.is_some()
+ || self.left_intersection.is_some()
+ || self.right_intersection.is_some()
+ || self.intersection.is_some())
+ }
+
+ /// Verifies if borders has left line set on the frame.
+ pub const fn has_left(&self) -> bool {
+ self.left.is_some()
+ || self.left_intersection.is_some()
+ || self.top_left.is_some()
+ || self.bottom_left.is_some()
+ }
+
+ /// Verifies if borders has right line set on the frame.
+ pub const fn has_right(&self) -> bool {
+ self.right.is_some()
+ || self.right_intersection.is_some()
+ || self.top_right.is_some()
+ || self.bottom_right.is_some()
+ }
+
+ /// Verifies if borders has top line set on the frame.
+ pub const fn has_top(&self) -> bool {
+ self.top.is_some()
+ || self.top_intersection.is_some()
+ || self.top_left.is_some()
+ || self.top_right.is_some()
+ }
+
+ /// Verifies if borders has bottom line set on the frame.
+ pub const fn has_bottom(&self) -> bool {
+ self.bottom.is_some()
+ || self.bottom_intersection.is_some()
+ || self.bottom_left.is_some()
+ || self.bottom_right.is_some()
+ }
+
+ /// Verifies if borders has horizontal lines set.
+ pub const fn has_horizontal(&self) -> bool {
+ self.horizontal.is_some()
+ || self.left_intersection.is_some()
+ || self.right_intersection.is_some()
+ || self.intersection.is_some()
+ }
+
+ /// Verifies if borders has vertical lines set.
+ pub const fn has_vertical(&self) -> bool {
+ self.intersection.is_some()
+ || self.vertical.is_some()
+ || self.top_intersection.is_some()
+ || self.bottom_intersection.is_some()
+ }
+}
diff --git a/vendor/papergrid/src/config/compact/mod.rs b/vendor/papergrid/src/config/compact/mod.rs
new file mode 100644
index 000000000..099fa44d9
--- /dev/null
+++ b/vendor/papergrid/src/config/compact/mod.rs
@@ -0,0 +1,141 @@
+//! A module which contains configuration of a [`CompactGrid`] which is responsible for grid configuration.
+//!
+//! [`CompactGrid`]: crate::grid::compact::CompactGrid
+
+use crate::color::StaticColor;
+
+use crate::config::{AlignmentHorizontal, Borders, Indent, Line, Sides};
+
+/// This structure represents a settings of a grid.
+///
+/// grid: crate::Grid.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CompactConfig {
+ borders: Borders<char>,
+ horizontal_line1: Option<Line<char>>,
+ border_colors: Borders<StaticColor>,
+ margin: Sides<Indent>,
+ margin_color: Sides<StaticColor>,
+ padding: Sides<Indent>,
+ padding_color: Sides<StaticColor>,
+ halignment: AlignmentHorizontal,
+}
+
+impl Default for CompactConfig {
+ fn default() -> Self {
+ Self::empty()
+ }
+}
+
+impl CompactConfig {
+ /// Returns an standard config.
+ pub const fn empty() -> Self {
+ Self {
+ halignment: AlignmentHorizontal::Left,
+ horizontal_line1: None,
+ borders: Borders::empty(),
+ border_colors: Borders::empty(),
+ margin: Sides::filled(Indent::zero()),
+ margin_color: Sides::filled(StaticColor::new("", "")),
+ padding: Sides::new(
+ Indent::spaced(1),
+ Indent::spaced(1),
+ Indent::zero(),
+ Indent::zero(),
+ ),
+ padding_color: Sides::filled(StaticColor::new("", "")),
+ }
+ }
+
+ /// Set grid margin.
+ pub const fn set_margin(mut self, margin: Sides<Indent>) -> Self {
+ self.margin = margin;
+ self
+ }
+
+ /// Returns a grid margin.
+ pub const fn get_margin(&self) -> &Sides<Indent> {
+ &self.margin
+ }
+
+ /// Set the [`Borders`] value as correct one.
+ pub const fn set_borders(mut self, borders: Borders<char>) -> Self {
+ self.borders = borders;
+ self
+ }
+
+ /// Set the first horizontal line.
+ ///
+ /// It ignores the [`Borders`] horizontal value if set for 1st row.
+ pub const fn set_first_horizontal_line(mut self, line: Line<char>) -> Self {
+ self.horizontal_line1 = Some(line);
+ self
+ }
+
+ /// Set the first horizontal line.
+ ///
+ /// It ignores the [`Borders`] horizontal value if set for 1st row.
+ pub const fn get_first_horizontal_line(&self) -> Option<Line<char>> {
+ self.horizontal_line1
+ }
+
+ /// Returns a current [`Borders`] structure.
+ pub const fn get_borders(&self) -> &Borders<char> {
+ &self.borders
+ }
+
+ /// Returns a current [`Borders`] structure.
+ pub const fn get_borders_color(&self) -> &Borders<StaticColor> {
+ &self.border_colors
+ }
+
+ /// Set a padding to a given cells.
+ pub const fn set_padding(mut self, padding: Sides<Indent>) -> Self {
+ self.padding = padding;
+ self
+ }
+
+ /// Get a padding for a given.
+ pub const fn get_padding(&self) -> &Sides<Indent> {
+ &self.padding
+ }
+
+ /// Set a horizontal alignment.
+ pub const fn set_alignment_horizontal(mut self, alignment: AlignmentHorizontal) -> Self {
+ self.halignment = alignment;
+ self
+ }
+
+ /// Get a alignment horizontal.
+ pub const fn get_alignment_horizontal(&self) -> AlignmentHorizontal {
+ self.halignment
+ }
+
+ /// Sets colors of border carcass on the grid.
+ pub const fn set_borders_color(mut self, borders: Borders<StaticColor>) -> Self {
+ self.border_colors = borders;
+ self
+ }
+
+ /// Set colors for a margin.
+ pub const fn set_margin_color(mut self, color: Sides<StaticColor>) -> Self {
+ self.margin_color = color;
+ self
+ }
+
+ /// Returns a margin color.
+ pub const fn get_margin_color(&self) -> Sides<StaticColor> {
+ self.margin_color
+ }
+
+ /// Set a padding to a given cells.
+ pub const fn set_padding_color(mut self, color: Sides<StaticColor>) -> Self {
+ self.padding_color = color;
+ self
+ }
+
+ /// Set a padding to a given cells.
+ pub const fn get_padding_color(&self) -> Sides<StaticColor> {
+ self.padding_color
+ }
+}
diff --git a/vendor/papergrid/src/config/entity.rs b/vendor/papergrid/src/config/entity.rs
new file mode 100644
index 000000000..0a2d3ba9b
--- /dev/null
+++ b/vendor/papergrid/src/config/entity.rs
@@ -0,0 +1,120 @@
+use super::Position;
+
+/// Entity a structure which represent a set of cells.
+///
+/// For example such table:
+///
+/// ```text
+/// ┌───┬───┐
+/// │ 0 │ 1 │
+/// ├───┼───┤
+/// │ 1 │ 2 │
+/// └───┴───┘
+/// ```
+///
+/// - has 4 cells.
+/// Which indexes are (0, 0), (0, 1), (1, 0), (1, 1).
+///
+/// - has 2 rows.
+/// Which indexes are 0, 1.
+///
+/// - has 2 column.
+/// Which indexes are 0, 1.
+///
+/// In [`Entity`] terms, all cells on the grid we call `Global`.
+#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)]
+pub enum Entity {
+ /// All cells on the grid.
+ Global,
+ /// All cells in a column on the grid.
+ Column(usize),
+ /// All cells in a row on the grid.
+ Row(usize),
+ /// A particular cell (row, column) on the grid.
+ Cell(usize, usize),
+}
+
+impl Entity {
+ /// Iterate over cells which are covered via the [`Entity`].
+ pub fn iter(&self, count_rows: usize, count_cols: usize) -> EntityIterator {
+ EntityIterator {
+ entity: *self,
+ count_rows,
+ count_cols,
+ i: 0,
+ j: 0,
+ }
+ }
+}
+
+impl From<Position> for Entity {
+ fn from((row, col): Position) -> Self {
+ Self::Cell(row, col)
+ }
+}
+
+/// An iterator over cells.
+///
+/// Produced from [`Entity::iter`].
+#[derive(Debug, Clone)]
+pub struct EntityIterator {
+ entity: Entity,
+ count_rows: usize,
+ count_cols: usize,
+ i: usize,
+ j: usize,
+}
+
+impl Iterator for EntityIterator {
+ type Item = Position;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.count_rows == 0 || self.count_cols == 0 {
+ return None;
+ }
+
+ match self.entity {
+ Entity::Cell(row, col) => {
+ self.count_cols = 0;
+ self.count_rows = 0;
+
+ Some((row, col))
+ }
+ Entity::Column(col) => {
+ if self.i >= self.count_rows {
+ return None;
+ }
+
+ let i = self.i;
+ self.i += 1;
+
+ Some((i, col))
+ }
+ Entity::Row(row) => {
+ if self.j >= self.count_cols {
+ return None;
+ }
+
+ let j = self.j;
+ self.j += 1;
+
+ Some((row, j))
+ }
+ Entity::Global => {
+ if self.j >= self.count_cols {
+ self.j = 0;
+ self.i += 1;
+
+ if self.i >= self.count_rows {
+ return None;
+ }
+ }
+
+ let j = self.j;
+ self.j += 1;
+
+ Some((self.i, j))
+ }
+ }
+ }
+}
diff --git a/vendor/papergrid/src/config/indent.rs b/vendor/papergrid/src/config/indent.rs
new file mode 100644
index 000000000..4d987724c
--- /dev/null
+++ b/vendor/papergrid/src/config/indent.rs
@@ -0,0 +1,31 @@
+/// Indent represent a filled space.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Indent {
+ /// A fill character.
+ pub fill: char,
+ /// A number of repeats of a fill character.
+ pub size: usize,
+}
+
+impl Indent {
+ /// Creates a new Indent structure.
+ pub const fn new(size: usize, fill: char) -> Self {
+ Self { fill, size }
+ }
+
+ /// Creates a new Indent structure with space (`' '`) as a fill character.
+ pub const fn spaced(size: usize) -> Self {
+ Self { size, fill: ' ' }
+ }
+
+ /// Creates a new Indent structure with space (`' '`) as a fill character.
+ pub const fn zero() -> Self {
+ Self::new(0, ' ')
+ }
+}
+
+impl Default for Indent {
+ fn default() -> Self {
+ Self { size: 0, fill: ' ' }
+ }
+}
diff --git a/vendor/papergrid/src/config/line.rs b/vendor/papergrid/src/config/line.rs
new file mode 100644
index 000000000..c3089e1c4
--- /dev/null
+++ b/vendor/papergrid/src/config/line.rs
@@ -0,0 +1,42 @@
+/// A line data structure.
+#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
+pub struct Line<T> {
+ /// A horizontal/vertical character.
+ pub main: T,
+ /// A horizontal/vertical intersection.
+ pub intersection: Option<T>,
+ /// A horizontal left / vertical top intersection.
+ pub connect1: Option<T>,
+ /// A horizontal right / vertical bottom intersection.
+ pub connect2: Option<T>,
+}
+
+impl<T> Line<T> {
+ /// Creates a new line.
+ pub const fn new(
+ main: T,
+ intersection: Option<T>,
+ connect1: Option<T>,
+ connect2: Option<T>,
+ ) -> Self {
+ Self {
+ main,
+ intersection,
+ connect1,
+ connect2,
+ }
+ }
+
+ /// Creates a new line.
+ pub const fn filled(val: T) -> Self
+ where
+ T: Copy,
+ {
+ Self {
+ main: val,
+ intersection: Some(val),
+ connect1: Some(val),
+ connect2: Some(val),
+ }
+ }
+}
diff --git a/vendor/papergrid/src/config/mod.rs b/vendor/papergrid/src/config/mod.rs
new file mode 100644
index 000000000..bba0b458e
--- /dev/null
+++ b/vendor/papergrid/src/config/mod.rs
@@ -0,0 +1,23 @@
+//! A module which contains a general settings which might be used in other grid implementations.
+
+mod alignment;
+mod border;
+mod borders;
+mod entity;
+mod indent;
+mod line;
+mod position;
+mod sides;
+
+pub mod compact;
+#[cfg(feature = "std")]
+pub mod spanned;
+
+pub use alignment::{AlignmentHorizontal, AlignmentVertical};
+pub use border::Border;
+pub use borders::Borders;
+pub use entity::{Entity, EntityIterator};
+pub use indent::Indent;
+pub use line::Line;
+pub use position::Position;
+pub use sides::Sides;
diff --git a/vendor/papergrid/src/config/position.rs b/vendor/papergrid/src/config/position.rs
new file mode 100644
index 000000000..3996453cc
--- /dev/null
+++ b/vendor/papergrid/src/config/position.rs
@@ -0,0 +1,13 @@
+/// Position is a (row, col) position on a Grid.
+///
+/// For example such table has 4 cells.
+/// Which indexes are (0, 0), (0, 1), (1, 0), (1, 1).
+///
+/// ```text
+/// ┌───┬───┐
+/// │ 0 │ 1 │
+/// ├───┼───┤
+/// │ 1 │ 2 │
+/// └───┴───┘
+/// ```
+pub type Position = (usize, usize);
diff --git a/vendor/papergrid/src/config/sides.rs b/vendor/papergrid/src/config/sides.rs
new file mode 100644
index 000000000..7aa0fb498
--- /dev/null
+++ b/vendor/papergrid/src/config/sides.rs
@@ -0,0 +1,37 @@
+/// A structure which represents 4 box sides.
+#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Sides<T> {
+ /// Top side.
+ pub top: T,
+ /// Bottom side.
+ pub bottom: T,
+ /// Left side.
+ pub left: T,
+ /// Right side.
+ pub right: T,
+}
+
+impl<T> Sides<T> {
+ /// Creates a new object.
+ pub const fn new(left: T, right: T, top: T, bottom: T) -> Self {
+ Self {
+ top,
+ bottom,
+ left,
+ right,
+ }
+ }
+
+ /// Creates a new object.
+ pub const fn filled(value: T) -> Self
+ where
+ T: Copy,
+ {
+ Self {
+ top: value,
+ bottom: value,
+ left: value,
+ right: value,
+ }
+ }
+}
diff --git a/vendor/papergrid/src/config/spanned/borders_config.rs b/vendor/papergrid/src/config/spanned/borders_config.rs
new file mode 100644
index 000000000..fe7729806
--- /dev/null
+++ b/vendor/papergrid/src/config/spanned/borders_config.rs
@@ -0,0 +1,486 @@
+use std::collections::{HashMap, HashSet};
+
+use crate::config::{Border, Borders, Position};
+
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub(crate) struct BordersConfig<T> {
+ global: Option<T>,
+ borders: Borders<T>,
+ cells: BordersMap<T>,
+ horizontals: HashMap<usize, HorizontalLine<T>>,
+ verticals: HashMap<usize, VerticalLine<T>>,
+ layout: BordersLayout,
+}
+
+impl<T: std::fmt::Debug> BordersConfig<T> {
+ pub(crate) fn insert_border(&mut self, pos: Position, border: Border<T>) {
+ if let Some(c) = border.top {
+ self.cells.horizontal.insert(pos, c);
+ self.layout.horizontals.insert(pos.0);
+ }
+
+ if let Some(c) = border.bottom {
+ self.cells.horizontal.insert((pos.0 + 1, pos.1), c);
+ self.layout.horizontals.insert(pos.0 + 1);
+ }
+
+ if let Some(c) = border.left {
+ self.cells.vertical.insert(pos, c);
+ self.layout.verticals.insert(pos.1);
+ }
+
+ if let Some(c) = border.right {
+ self.cells.vertical.insert((pos.0, pos.1 + 1), c);
+ self.layout.verticals.insert(pos.1 + 1);
+ }
+
+ if let Some(c) = border.left_top_corner {
+ self.cells.intersection.insert((pos.0, pos.1), c);
+ self.layout.horizontals.insert(pos.0);
+ self.layout.verticals.insert(pos.1);
+ }
+
+ if let Some(c) = border.right_top_corner {
+ self.cells.intersection.insert((pos.0, pos.1 + 1), c);
+ self.layout.horizontals.insert(pos.0);
+ self.layout.verticals.insert(pos.1 + 1);
+ }
+
+ if let Some(c) = border.left_bottom_corner {
+ self.cells.intersection.insert((pos.0 + 1, pos.1), c);
+ self.layout.horizontals.insert(pos.0 + 1);
+ self.layout.verticals.insert(pos.1);
+ }
+
+ if let Some(c) = border.right_bottom_corner {
+ self.cells.intersection.insert((pos.0 + 1, pos.1 + 1), c);
+ self.layout.horizontals.insert(pos.0 + 1);
+ self.layout.verticals.insert(pos.1 + 1);
+ }
+ }
+
+ pub(crate) fn remove_border(&mut self, pos: Position, shape: (usize, usize)) {
+ let (count_rows, count_cols) = shape;
+
+ self.cells.horizontal.remove(&pos);
+ self.cells.horizontal.remove(&(pos.0 + 1, pos.1));
+ self.cells.vertical.remove(&pos);
+ self.cells.vertical.remove(&(pos.0, pos.1 + 1));
+ self.cells.intersection.remove(&pos);
+ self.cells.intersection.remove(&(pos.0 + 1, pos.1));
+ self.cells.intersection.remove(&(pos.0, pos.1 + 1));
+ self.cells.intersection.remove(&(pos.0 + 1, pos.1 + 1));
+
+ // clean up the layout.
+
+ if !self.check_is_horizontal_set(pos.0, count_rows) {
+ self.layout.horizontals.remove(&pos.0);
+ }
+
+ if !self.check_is_horizontal_set(pos.0 + 1, count_rows) {
+ self.layout.horizontals.remove(&(pos.0 + 1));
+ }
+
+ if !self.check_is_vertical_set(pos.1, count_cols) {
+ self.layout.verticals.remove(&pos.1);
+ }
+
+ if !self.check_is_vertical_set(pos.1 + 1, count_cols) {
+ self.layout.verticals.remove(&(pos.1 + 1));
+ }
+ }
+
+ pub(crate) fn get_border(&self, pos: Position, shape: (usize, usize)) -> Border<&T> {
+ Border {
+ top: self.get_horizontal(pos, shape.0),
+ bottom: self.get_horizontal((pos.0 + 1, pos.1), shape.0),
+ left: self.get_vertical(pos, shape.1),
+ left_top_corner: self.get_intersection(pos, shape),
+ left_bottom_corner: self.get_intersection((pos.0 + 1, pos.1), shape),
+ right: self.get_vertical((pos.0, pos.1 + 1), shape.1),
+ right_top_corner: self.get_intersection((pos.0, pos.1 + 1), shape),
+ right_bottom_corner: self.get_intersection((pos.0 + 1, pos.1 + 1), shape),
+ }
+ }
+
+ pub(crate) fn insert_horizontal_line(&mut self, row: usize, line: HorizontalLine<T>) {
+ if line.left.is_some() {
+ self.layout.left = true;
+ }
+
+ // todo: when we delete lines these are still left set; so has_horizontal/vertical return true in some cases;
+ // it shall be fixed, but maybe we can improve the logic as it got a bit complicated.
+ if line.right.is_some() {
+ self.layout.right = true;
+ }
+
+ if line.intersection.is_some() {
+ self.layout.inner_verticals = true;
+ }
+
+ self.horizontals.insert(row, line);
+ self.layout.horizontals.insert(row);
+ }
+
+ pub(crate) fn get_horizontal_line(&self, row: usize) -> Option<&HorizontalLine<T>> {
+ self.horizontals.get(&row)
+ }
+
+ pub(crate) fn remove_horizontal_line(&mut self, row: usize, count_rows: usize) {
+ self.horizontals.remove(&row);
+ self.layout.horizontals.remove(&row);
+
+ if self.has_horizontal(row, count_rows) {
+ self.layout.horizontals.insert(row);
+ }
+ }
+
+ pub(crate) fn insert_vertical_line(&mut self, row: usize, line: VerticalLine<T>) {
+ if line.top.is_some() {
+ self.layout.top = true;
+ }
+
+ if line.bottom.is_some() {
+ self.layout.bottom = true;
+ }
+
+ self.verticals.insert(row, line);
+ self.layout.verticals.insert(row);
+ }
+
+ pub(crate) fn get_vertical_line(&self, row: usize) -> Option<&VerticalLine<T>> {
+ self.verticals.get(&row)
+ }
+
+ pub(crate) fn remove_vertical_line(&mut self, col: usize, count_columns: usize) {
+ self.verticals.remove(&col);
+ self.layout.verticals.remove(&col);
+
+ if self.has_vertical(col, count_columns) {
+ self.layout.verticals.insert(col);
+ }
+ }
+
+ pub(crate) fn set_borders(&mut self, borders: Borders<T>) {
+ self.borders = borders;
+ }
+
+ pub(crate) fn get_borders(&self) -> &Borders<T> {
+ &self.borders
+ }
+
+ pub(crate) fn get_global(&self) -> Option<&T> {
+ self.global.as_ref()
+ }
+
+ pub(crate) fn set_global(&mut self, value: T) {
+ self.global = Some(value);
+ }
+
+ pub(crate) fn get_vertical(&self, pos: Position, count_cols: usize) -> Option<&T> {
+ self.cells
+ .vertical
+ .get(&pos)
+ .or_else(|| self.verticals.get(&pos.1).and_then(|l| l.main.as_ref()))
+ .or({
+ if pos.1 == count_cols {
+ self.borders.right.as_ref()
+ } else if pos.1 == 0 {
+ self.borders.left.as_ref()
+ } else {
+ self.borders.vertical.as_ref()
+ }
+ })
+ .or(self.global.as_ref())
+ }
+
+ pub(crate) fn get_horizontal(&self, pos: Position, count_rows: usize) -> Option<&T> {
+ self.cells
+ .horizontal
+ .get(&pos)
+ .or_else(|| self.horizontals.get(&pos.0).and_then(|l| l.main.as_ref()))
+ .or({
+ if pos.0 == 0 {
+ self.borders.top.as_ref()
+ } else if pos.0 == count_rows {
+ self.borders.bottom.as_ref()
+ } else {
+ self.borders.horizontal.as_ref()
+ }
+ })
+ .or(self.global.as_ref())
+ }
+
+ pub(crate) fn get_intersection(
+ &self,
+ pos: Position,
+ (count_rows, count_cols): (usize, usize),
+ ) -> Option<&T> {
+ let use_top = pos.0 == 0;
+ let use_bottom = pos.0 == count_rows;
+ let use_left = pos.1 == 0;
+ let use_right = pos.1 == count_cols;
+
+ if let Some(c) = self.cells.intersection.get(&pos) {
+ return Some(c);
+ }
+
+ let hl_c = self.horizontals.get(&pos.0).and_then(|l| {
+ if use_left && l.left.is_some() {
+ l.left.as_ref()
+ } else if use_right && l.right.is_some() {
+ l.right.as_ref()
+ } else if !use_right && !use_left && l.intersection.is_some() {
+ l.intersection.as_ref()
+ } else {
+ None
+ }
+ });
+
+ if let Some(c) = hl_c {
+ return Some(c);
+ }
+
+ let vl_c = self.verticals.get(&pos.1).and_then(|l| {
+ if use_top && l.top.is_some() {
+ l.top.as_ref()
+ } else if use_bottom && l.bottom.is_some() {
+ l.bottom.as_ref()
+ } else if !use_top && !use_bottom && l.intersection.is_some() {
+ l.intersection.as_ref()
+ } else {
+ None
+ }
+ });
+
+ if let Some(c) = vl_c {
+ return Some(c);
+ }
+
+ let borders_c = {
+ if use_top && use_left {
+ self.borders.top_left.as_ref()
+ } else if use_top && use_right {
+ self.borders.top_right.as_ref()
+ } else if use_bottom && use_left {
+ self.borders.bottom_left.as_ref()
+ } else if use_bottom && use_right {
+ self.borders.bottom_right.as_ref()
+ } else if use_top {
+ self.borders.top_intersection.as_ref()
+ } else if use_bottom {
+ self.borders.bottom_intersection.as_ref()
+ } else if use_left {
+ self.borders.left_intersection.as_ref()
+ } else if use_right {
+ self.borders.right_intersection.as_ref()
+ } else {
+ self.borders.intersection.as_ref()
+ }
+ };
+
+ if let Some(c) = borders_c {
+ return Some(c);
+ }
+
+ self.global.as_ref()
+ }
+
+ pub(crate) fn has_horizontal(&self, row: usize, count_rows: usize) -> bool {
+ self.global.is_some()
+ || (row == 0 && self.borders.has_top())
+ || (row == count_rows && self.borders.has_bottom())
+ || (row > 0 && row < count_rows && self.borders.has_horizontal())
+ || self.is_horizontal_set(row, count_rows)
+ }
+
+ pub(crate) fn has_vertical(&self, col: usize, count_cols: usize) -> bool {
+ self.global.is_some()
+ || (col == 0 && self.borders.has_left())
+ || (col == count_cols && self.borders.has_right())
+ || (col > 0 && col < count_cols && self.borders.has_vertical())
+ || self.is_vertical_set(col, count_cols)
+ }
+
+ fn is_horizontal_set(&self, row: usize, count_rows: usize) -> bool {
+ (row == 0 && self.layout.top)
+ || (row == count_rows && self.layout.bottom)
+ || (row > 0 && row < count_rows && self.layout.inner_horizontals)
+ || self.layout.horizontals.contains(&row)
+ }
+
+ fn is_vertical_set(&self, col: usize, count_cols: usize) -> bool {
+ (col == 0 && self.layout.left)
+ || (col == count_cols && self.layout.right)
+ || (col > 0 && col < count_cols && self.layout.inner_verticals)
+ || self.layout.verticals.contains(&col)
+ }
+
+ fn check_is_horizontal_set(&self, row: usize, count_rows: usize) -> bool {
+ (row == 0 && self.layout.top)
+ || (row == count_rows && self.layout.bottom)
+ || (row > 0 && row < count_rows && self.layout.inner_horizontals)
+ || self.cells.horizontal.keys().any(|&p| p.0 == row)
+ || self.cells.intersection.keys().any(|&p| p.0 == row)
+ }
+
+ fn check_is_vertical_set(&self, col: usize, count_cols: usize) -> bool {
+ (col == 0 && self.layout.left)
+ || (col == count_cols && self.layout.right)
+ || (col > 0 && col < count_cols && self.layout.inner_verticals)
+ || self.cells.vertical.keys().any(|&p| p.1 == col)
+ || self.cells.intersection.keys().any(|&p| p.1 == col)
+ }
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Eq)]
+pub(crate) struct BordersMap<T> {
+ vertical: HashMap<Position, T>,
+ horizontal: HashMap<Position, T>,
+ intersection: HashMap<Position, T>,
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Eq)]
+pub(crate) struct BordersLayout {
+ left: bool,
+ right: bool,
+ top: bool,
+ bottom: bool,
+ inner_verticals: bool,
+ inner_horizontals: bool,
+ horizontals: HashSet<usize>,
+ verticals: HashSet<usize>,
+}
+
+/// A structure for a custom horizontal line.
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub struct HorizontalLine<T> {
+ /// Line character.
+ pub main: Option<T>,
+ /// Line intersection character.
+ pub intersection: Option<T>,
+ /// Left intersection character.
+ pub left: Option<T>,
+ /// Right intersection character.
+ pub right: Option<T>,
+}
+
+impl<T> HorizontalLine<T> {
+ /// Verifies if the line has any setting set.
+ pub const fn is_empty(&self) -> bool {
+ self.main.is_none()
+ && self.intersection.is_none()
+ && self.left.is_none()
+ && self.right.is_none()
+ }
+}
+
+/// A structure for a vertical line.
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub struct VerticalLine<T> {
+ /// Line character.
+ pub main: Option<T>,
+ /// Line intersection character.
+ pub intersection: Option<T>,
+ /// Left intersection character.
+ pub top: Option<T>,
+ /// Right intersection character.
+ pub bottom: Option<T>,
+}
+
+impl<T> VerticalLine<T> {
+ /// Verifies if the line has any setting set.
+ pub const fn is_empty(&self) -> bool {
+ self.main.is_none()
+ && self.intersection.is_none()
+ && self.top.is_none()
+ && self.bottom.is_none()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_insert_border() {
+ let mut borders = BordersConfig::<char>::default();
+ borders.insert_border((0, 0), Border::filled('x'));
+
+ assert_eq!(borders.get_border((0, 0), (10, 10)), Border::filled(&'x'));
+ assert_eq!(borders.get_border((0, 0), (0, 0)), Border::filled(&'x'));
+
+ assert!(borders.is_horizontal_set(0, 10));
+ assert!(borders.is_horizontal_set(1, 10));
+ assert!(!borders.is_horizontal_set(2, 10));
+ assert!(borders.is_vertical_set(0, 10));
+ assert!(borders.is_vertical_set(1, 10));
+ assert!(!borders.is_vertical_set(2, 10));
+
+ assert!(borders.is_horizontal_set(0, 0));
+ assert!(borders.is_horizontal_set(1, 0));
+ assert!(!borders.is_horizontal_set(2, 0));
+ assert!(borders.is_vertical_set(0, 0));
+ assert!(borders.is_vertical_set(1, 0));
+ assert!(!borders.is_vertical_set(2, 0));
+ }
+
+ #[test]
+ fn test_insert_border_override() {
+ let mut borders = BordersConfig::<char>::default();
+ borders.insert_border((0, 0), Border::filled('x'));
+ borders.insert_border((1, 0), Border::filled('y'));
+ borders.insert_border((0, 1), Border::filled('w'));
+ borders.insert_border((1, 1), Border::filled('q'));
+
+ assert_eq!(
+ borders.get_border((0, 0), (10, 10)).copied(),
+ Border::full('x', 'y', 'x', 'w', 'x', 'w', 'y', 'q')
+ );
+ assert_eq!(
+ borders.get_border((0, 1), (10, 10)).copied(),
+ Border::full('w', 'q', 'w', 'w', 'w', 'w', 'q', 'q')
+ );
+ assert_eq!(
+ borders.get_border((1, 0), (10, 10)).copied(),
+ Border::full('y', 'y', 'y', 'q', 'y', 'q', 'y', 'q')
+ );
+ assert_eq!(
+ borders.get_border((1, 1), (10, 10)).copied(),
+ Border::filled('q')
+ );
+
+ assert!(borders.is_horizontal_set(0, 10));
+ assert!(borders.is_horizontal_set(1, 10));
+ assert!(borders.is_horizontal_set(2, 10));
+ assert!(!borders.is_horizontal_set(3, 10));
+ assert!(borders.is_vertical_set(0, 10));
+ assert!(borders.is_vertical_set(1, 10));
+ assert!(borders.is_vertical_set(2, 10));
+ assert!(!borders.is_vertical_set(3, 10));
+ }
+
+ #[test]
+ fn test_set_global() {
+ let mut borders = BordersConfig::<char>::default();
+ borders.insert_border((0, 0), Border::filled('x'));
+ borders.set_global('l');
+
+ assert_eq!(borders.get_border((0, 0), (10, 10)), Border::filled(&'x'));
+ assert_eq!(borders.get_border((2, 0), (10, 10)), Border::filled(&'l'));
+
+ assert!(borders.is_horizontal_set(0, 10));
+ assert!(borders.is_horizontal_set(1, 10));
+ assert!(!borders.is_horizontal_set(2, 10));
+ assert!(borders.is_vertical_set(0, 10));
+ assert!(borders.is_vertical_set(1, 10));
+ assert!(!borders.is_vertical_set(2, 10));
+
+ assert!(borders.is_horizontal_set(0, 0));
+ assert!(borders.is_horizontal_set(1, 0));
+ assert!(!borders.is_horizontal_set(2, 0));
+ assert!(borders.is_vertical_set(0, 0));
+ assert!(borders.is_vertical_set(1, 0));
+ assert!(!borders.is_vertical_set(2, 0));
+ }
+}
diff --git a/vendor/papergrid/src/config/spanned/entity_map.rs b/vendor/papergrid/src/config/spanned/entity_map.rs
new file mode 100644
index 000000000..472ea5fb2
--- /dev/null
+++ b/vendor/papergrid/src/config/spanned/entity_map.rs
@@ -0,0 +1,108 @@
+use fnv::FnvHashMap;
+
+use crate::config::{Entity, Position};
+
+/// A structure to keep information for [`Entity`] as a key.
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct EntityMap<T> {
+ // we have a global type to allocate in on stack.
+ // because most of the time no changes are made to the [`EntityMap`].
+ global: T,
+ columns: FnvHashMap<usize, T>,
+ rows: FnvHashMap<usize, T>,
+ cells: FnvHashMap<Position, T>,
+}
+
+impl<T> EntityMap<T> {
+ /// Creates an empty [`EntityMap`].
+ pub fn new(global: T) -> Self {
+ Self {
+ global,
+ rows: FnvHashMap::default(),
+ columns: FnvHashMap::default(),
+ cells: FnvHashMap::default(),
+ }
+ }
+
+ /// Get a value for an [`Entity`].
+ pub fn get(&self, entity: Entity) -> &T {
+ if self.rows.is_empty() && self.columns.is_empty() && self.cells.is_empty() {
+ return &self.global;
+ }
+
+ match entity {
+ Entity::Column(col) => self.columns.get(&col).unwrap_or(&self.global),
+ Entity::Row(row) => self.rows.get(&row).unwrap_or(&self.global),
+ Entity::Cell(row, col) => {
+ // todo: optimize;
+ //
+ // Cause we can change rows/columns/cells separately we need to check them separately.
+ // But we often doing this checks in `Grid::fmt` and I believe if we could optimize it it could be beneficial.
+ //
+ // Haven't found a solution for that yet.
+ //
+ // I was wondering if there is a hash function like.
+ // Apparently it doesn't make sense cause we will reset columns/rows on cell insert which is not what we want.
+ //
+ // ```
+ // hash(column, row) == hash(column) == hash(row)
+ // ```
+ //
+ // ref: https://opendsa-server.cs.vt.edu/ODSA/Books/Everything/html/Sparse.html
+ // ref: https://users.rust-lang.org/t/make-hash-return-same-value-whather-the-order-of-element-of-a-tuple/69932/13
+
+ self.cells
+ .get(&(row, col))
+ .or_else(|| self.columns.get(&col))
+ .or_else(|| self.rows.get(&row))
+ .unwrap_or(&self.global)
+ }
+ Entity::Global => &self.global,
+ }
+ }
+
+ /// Removes a value for an [`Entity`].
+ pub fn remove(&mut self, entity: Entity) {
+ match entity {
+ Entity::Global => {
+ self.cells.clear();
+ self.rows.clear();
+ self.columns.clear();
+ }
+ Entity::Column(col) => self.cells.retain(|&(_, c), _| c != col),
+ Entity::Row(row) => self.cells.retain(|&(r, _), _| r != row),
+ Entity::Cell(row, col) => {
+ self.cells.remove(&(row, col));
+ }
+ }
+ }
+}
+
+impl<T: Clone> EntityMap<T> {
+ /// Set a value for an [`Entity`].
+ pub fn insert(&mut self, entity: Entity, value: T) {
+ match entity {
+ Entity::Column(col) => {
+ for &row in self.rows.keys() {
+ self.cells.insert((row, col), value.clone());
+ }
+
+ self.columns.insert(col, value);
+ }
+ Entity::Row(row) => {
+ for &col in self.columns.keys() {
+ self.cells.insert((row, col), value.clone());
+ }
+
+ self.rows.insert(row, value);
+ }
+ Entity::Cell(row, col) => {
+ self.cells.insert((row, col), value);
+ }
+ Entity::Global => {
+ self.remove(Entity::Global);
+ self.global = value
+ }
+ }
+ }
+}
diff --git a/vendor/papergrid/src/config/spanned/formatting.rs b/vendor/papergrid/src/config/spanned/formatting.rs
new file mode 100644
index 000000000..0865f6c5b
--- /dev/null
+++ b/vendor/papergrid/src/config/spanned/formatting.rs
@@ -0,0 +1,21 @@
+/// Formatting represent a logic of formatting of a cell.
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Formatting {
+ /// An setting to allow horizontal trim.
+ pub horizontal_trim: bool,
+ /// An setting to allow vertical trim.
+ pub vertical_trim: bool,
+ /// An setting to allow alignment per line.
+ pub allow_lines_alignment: bool,
+}
+
+impl Formatting {
+ /// Creates a new [`Formatting`] structure.
+ pub fn new(horizontal_trim: bool, vertical_trim: bool, allow_lines_alignment: bool) -> Self {
+ Self {
+ horizontal_trim,
+ vertical_trim,
+ allow_lines_alignment,
+ }
+ }
+}
diff --git a/vendor/papergrid/src/config/spanned/mod.rs b/vendor/papergrid/src/config/spanned/mod.rs
new file mode 100644
index 000000000..a7f71e11c
--- /dev/null
+++ b/vendor/papergrid/src/config/spanned/mod.rs
@@ -0,0 +1,887 @@
+//! A module which contains configuration options for a [`Grid`].
+//!
+//! [`Grid`]: crate::grid::iterable::Grid
+
+mod borders_config;
+mod entity_map;
+mod formatting;
+mod offset;
+
+use std::collections::HashMap;
+
+use crate::color::{AnsiColor, StaticColor};
+use crate::config::compact::CompactConfig;
+use crate::config::{
+ AlignmentHorizontal, AlignmentVertical, Border, Borders, Entity, Indent, Position, Sides,
+};
+use borders_config::BordersConfig;
+
+pub use self::{entity_map::EntityMap, formatting::Formatting, offset::Offset};
+
+/// This structure represents a settings of a grid.
+///
+/// grid: crate::Grid.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct SpannedConfig {
+ margin: Sides<ColoredMarginIndent>,
+ padding: EntityMap<Sides<ColoredIndent>>,
+ alignment_h: EntityMap<AlignmentHorizontal>,
+ alignment_v: EntityMap<AlignmentVertical>,
+ formatting: EntityMap<Formatting>,
+ span_columns: HashMap<Position, usize>,
+ span_rows: HashMap<Position, usize>,
+ borders: BordersConfig<char>,
+ borders_colors: BordersConfig<AnsiColor<'static>>,
+ borders_missing_char: char,
+ horizontal_chars: HashMap<Position, HashMap<Offset, char>>,
+ horizontal_colors: HashMap<Position, HashMap<Offset, AnsiColor<'static>>>,
+ vertical_chars: HashMap<Position, HashMap<Offset, char>>,
+ vertical_colors: HashMap<Position, HashMap<Offset, AnsiColor<'static>>>,
+ justification: EntityMap<char>,
+ justification_color: EntityMap<Option<AnsiColor<'static>>>,
+}
+
+impl Default for SpannedConfig {
+ fn default() -> Self {
+ Self {
+ margin: Sides::default(),
+ padding: EntityMap::default(),
+ formatting: EntityMap::default(),
+ alignment_h: EntityMap::new(AlignmentHorizontal::Left),
+ alignment_v: EntityMap::new(AlignmentVertical::Top),
+ span_columns: HashMap::default(),
+ span_rows: HashMap::default(),
+ borders: BordersConfig::default(),
+ borders_colors: BordersConfig::default(),
+ borders_missing_char: ' ',
+ horizontal_chars: HashMap::default(),
+ horizontal_colors: HashMap::default(),
+ vertical_chars: HashMap::default(),
+ vertical_colors: HashMap::default(),
+ justification: EntityMap::new(' '),
+ justification_color: EntityMap::default(),
+ }
+ }
+}
+
+impl SpannedConfig {
+ /// Set a margin of a grid.
+ pub fn set_margin(&mut self, margin: Sides<Indent>) {
+ self.margin.left.indent = margin.left;
+ self.margin.right.indent = margin.right;
+ self.margin.top.indent = margin.top;
+ self.margin.bottom.indent = margin.bottom;
+ }
+
+ /// Set a color of margin of a grid.
+ pub fn set_margin_color(&mut self, margin: Sides<Option<AnsiColor<'static>>>) {
+ self.margin.left.color = margin.left;
+ self.margin.right.color = margin.right;
+ self.margin.top.color = margin.top;
+ self.margin.bottom.color = margin.bottom;
+ }
+
+ /// Set an offset of margin of a grid.
+ pub fn set_margin_offset(&mut self, margin: Sides<Offset>) {
+ self.margin.left.offset = margin.left;
+ self.margin.right.offset = margin.right;
+ self.margin.top.offset = margin.top;
+ self.margin.bottom.offset = margin.bottom;
+ }
+
+ /// Returns a margin value currently set.
+ pub fn get_margin(&self) -> Sides<Indent> {
+ Sides::new(
+ self.margin.left.indent,
+ self.margin.right.indent,
+ self.margin.top.indent,
+ self.margin.bottom.indent,
+ )
+ }
+
+ /// Returns a margin color value currently set.
+ pub fn get_margin_color(&self) -> Sides<Option<AnsiColor<'static>>> {
+ Sides::new(
+ self.margin.left.color.clone(),
+ self.margin.right.color.clone(),
+ self.margin.top.color.clone(),
+ self.margin.bottom.color.clone(),
+ )
+ }
+
+ /// Returns a margin offset value currently set.
+ pub fn get_margin_offset(&self) -> Sides<Offset> {
+ Sides::new(
+ self.margin.left.offset,
+ self.margin.right.offset,
+ self.margin.top.offset,
+ self.margin.bottom.offset,
+ )
+ }
+
+ /// Clears all theme changes.
+ /// And sets it to default.
+ pub fn clear_theme(&mut self) {
+ self.borders = BordersConfig::default();
+ self.horizontal_chars.clear();
+ self.vertical_chars.clear();
+ self.horizontal_colors.clear();
+ self.vertical_colors.clear();
+ }
+
+ /// Set the [`Borders`] value as correct one.
+ pub fn set_borders(&mut self, borders: Borders<char>) {
+ self.borders.set_borders(borders);
+ }
+
+ /// Gets a global border value if set.
+ pub fn get_global_border(&self) -> Option<&char> {
+ self.borders.get_global()
+ }
+
+ /// Set the all [`Borders`] values to a char.
+ pub fn set_global_border(&mut self, c: char) {
+ self.borders.set_global(c);
+ }
+
+ /// Returns a current [`Borders`] structure.
+ pub fn get_borders(&self) -> &Borders<char> {
+ self.borders.get_borders()
+ }
+
+ /// Set the border line by row index.
+ ///
+ /// Row `0` means the top row.
+ /// Row `grid.count_rows()` means the bottom row.
+ pub fn insert_horizontal_line(&mut self, line: usize, val: HorizontalLine) {
+ self.borders.insert_horizontal_line(line, val);
+ }
+
+ /// Sets off the border line by row index if any were set
+ ///
+ /// Row `0` means the top row.
+ /// Row `grid.count_rows()` means the bottom row.
+ pub fn remove_horizontal_line(&mut self, line: usize, count_rows: usize) {
+ self.borders.remove_horizontal_line(line, count_rows);
+ }
+
+ /// Gets a overridden vertical line.
+ ///
+ /// Row `0` means the left row.
+ /// Row `grid.count_columns()` means the right most row.
+ pub fn get_vertical_line(&self, line: usize) -> Option<&VerticalLine> {
+ self.borders.get_vertical_line(line)
+ }
+
+ /// Set the border line by column index.
+ ///
+ /// Row `0` means the left row.
+ /// Row `grid.count_columns()` means the right most row.
+ pub fn insert_vertical_line(&mut self, line: usize, val: VerticalLine) {
+ self.borders.insert_vertical_line(line, val);
+ }
+
+ /// Sets off the border line by column index if any were set
+ ///
+ /// Row `0` means the left row.
+ /// Row `grid.count_columns()` means the right most row.
+ pub fn remove_vertical_line(&mut self, line: usize, count_columns: usize) {
+ self.borders.remove_vertical_line(line, count_columns);
+ }
+
+ /// Gets a overridden line.
+ ///
+ /// Row `0` means the top row.
+ /// Row `grid.count_rows()` means the bottom row.
+ pub fn get_horizontal_line(&self, line: usize) -> Option<&HorizontalLine> {
+ self.borders.get_horizontal_line(line)
+ }
+
+ /// Override a character on a horizontal line.
+ ///
+ /// If borders are not set the char won't be used.
+ ///
+ /// It takes not cell position but line as row and column of a cell;
+ /// So its range is line <= count_rows && col < count_columns.
+ pub fn set_horizontal_char(&mut self, pos: Position, c: char, offset: Offset) {
+ let chars = self
+ .horizontal_chars
+ .entry(pos)
+ .or_insert_with(|| HashMap::with_capacity(1));
+
+ chars.insert(offset, c);
+ }
+
+ /// Get a list of overridden chars in a horizontal border.
+ ///
+ /// It takes not cell position but line as row and column of a cell;
+ /// So its range is line <= count_rows && col < count_columns.
+ pub fn lookup_horizontal_char(&self, pos: Position, offset: usize, end: usize) -> Option<char> {
+ self.horizontal_chars
+ .get(&pos)
+ .and_then(|chars| {
+ chars.get(&Offset::Begin(offset)).or_else(|| {
+ if end > offset {
+ if end == 0 {
+ chars.get(&Offset::End(0))
+ } else {
+ chars.get(&Offset::End(end - offset - 1))
+ }
+ } else {
+ None
+ }
+ })
+ })
+ .copied()
+ }
+
+ /// Checks if there any char in a horizontal border being overridden.
+ ///
+ /// It takes not cell position but line as row and column of a cell;
+ /// So its range is line <= count_rows && col < count_columns.
+ pub fn is_overridden_horizontal(&self, pos: Position) -> bool {
+ self.horizontal_chars.get(&pos).is_some()
+ }
+
+ /// Removes a list of overridden chars in a horizontal border.
+ ///
+ /// It takes not cell position but line as row and column of a cell;
+ /// So its range is line <= count_rows && col < count_columns.
+ pub fn remove_overridden_horizontal(&mut self, pos: Position) {
+ self.horizontal_chars.remove(&pos);
+ }
+
+ /// Override a vertical split line.
+ ///
+ /// If borders are not set the char won't be used.
+ ///
+ /// It takes not cell position but cell row and column of a line;
+ /// So its range is row < count_rows && col <= count_columns.
+ pub fn set_vertical_char(&mut self, pos: Position, c: char, offset: Offset) {
+ let chars = self
+ .vertical_chars
+ .entry(pos)
+ .or_insert_with(|| HashMap::with_capacity(1));
+
+ chars.insert(offset, c);
+ }
+
+ /// Get a list of overridden chars in a horizontal border.
+ ///
+ /// It takes not cell position but cell row and column of a line;
+ /// So its range is row < count_rows && col <= count_columns.
+ pub fn lookup_vertical_char(&self, pos: Position, offset: usize, end: usize) -> Option<char> {
+ self.vertical_chars
+ .get(&pos)
+ .and_then(|chars| {
+ chars.get(&Offset::Begin(offset)).or_else(|| {
+ if end > offset {
+ if end == 0 {
+ chars.get(&Offset::End(0))
+ } else {
+ chars.get(&Offset::End(end - offset - 1))
+ }
+ } else {
+ None
+ }
+ })
+ })
+ .copied()
+ }
+
+ /// Checks if there any char in a horizontal border being overridden.
+ ///
+ /// It takes not cell position but cell row and column of a line;
+ /// So its range is row < count_rows && col <= count_columns.
+ pub fn is_overridden_vertical(&self, pos: Position) -> bool {
+ self.vertical_chars.get(&pos).is_some()
+ }
+
+ /// Removes a list of overridden chars in a horizontal border.
+ ///
+ /// It takes not cell position but cell row and column of a line;
+ /// So its range is row < count_rows && col <= count_columns.
+ pub fn remove_overridden_vertical(&mut self, pos: Position) {
+ self.vertical_chars.remove(&pos);
+ }
+
+ /// Override a character color on a horizontal line.
+ pub fn set_horizontal_color(&mut self, pos: Position, c: AnsiColor<'static>, offset: Offset) {
+ let chars = self
+ .horizontal_colors
+ .entry(pos)
+ .or_insert_with(|| HashMap::with_capacity(1));
+
+ chars.insert(offset, c);
+ }
+
+ /// Get a overridden color in a horizontal border.
+ pub fn lookup_horizontal_color(
+ &self,
+ pos: Position,
+ offset: usize,
+ end: usize,
+ ) -> Option<&AnsiColor<'static>> {
+ self.horizontal_colors.get(&pos).and_then(|chars| {
+ chars.get(&Offset::Begin(offset)).or_else(|| {
+ if end > offset {
+ if end == 0 {
+ chars.get(&Offset::End(0))
+ } else {
+ chars.get(&Offset::End(end - offset - 1))
+ }
+ } else {
+ None
+ }
+ })
+ })
+ }
+
+ /// Override a character color on a vertical line.
+ pub fn set_vertical_color(&mut self, pos: Position, c: AnsiColor<'static>, offset: Offset) {
+ let chars = self
+ .vertical_colors
+ .entry(pos)
+ .or_insert_with(|| HashMap::with_capacity(1));
+
+ chars.insert(offset, c);
+ }
+
+ /// Get a overridden color in a vertical border.
+ pub fn lookup_vertical_color(
+ &self,
+ pos: Position,
+ offset: usize,
+ end: usize,
+ ) -> Option<&AnsiColor<'static>> {
+ self.vertical_colors.get(&pos).and_then(|chars| {
+ chars.get(&Offset::Begin(offset)).or_else(|| {
+ if end > offset {
+ if end == 0 {
+ chars.get(&Offset::End(0))
+ } else {
+ chars.get(&Offset::End(end - offset - 1))
+ }
+ } else {
+ None
+ }
+ })
+ })
+ }
+
+ /// Set a padding to a given cells.
+ pub fn set_padding(&mut self, entity: Entity, padding: Sides<Indent>) {
+ let mut pad = self.padding.get(entity).clone();
+ pad.left.indent = padding.left;
+ pad.right.indent = padding.right;
+ pad.top.indent = padding.top;
+ pad.bottom.indent = padding.bottom;
+
+ self.padding.insert(entity, pad);
+ }
+
+ /// Set a padding to a given cells.
+ pub fn set_padding_color(
+ &mut self,
+ entity: Entity,
+ padding: Sides<Option<AnsiColor<'static>>>,
+ ) {
+ let mut pad = self.padding.get(entity).clone();
+ pad.left.color = padding.left;
+ pad.right.color = padding.right;
+ pad.top.color = padding.top;
+ pad.bottom.color = padding.bottom;
+
+ self.padding.insert(entity, pad);
+ }
+
+ /// Get a padding for a given [Entity].
+ pub fn get_padding(&self, entity: Entity) -> Sides<Indent> {
+ let pad = self.padding.get(entity);
+ Sides::new(
+ pad.left.indent,
+ pad.right.indent,
+ pad.top.indent,
+ pad.bottom.indent,
+ )
+ }
+
+ /// Get a padding color for a given [Entity].
+ pub fn get_padding_color(&self, entity: Entity) -> Sides<Option<AnsiColor<'static>>> {
+ let pad = self.padding.get(entity);
+ Sides::new(
+ pad.left.color.clone(),
+ pad.right.color.clone(),
+ pad.top.color.clone(),
+ pad.bottom.color.clone(),
+ )
+ }
+
+ /// Set a formatting to a given cells.
+ pub fn set_formatting(&mut self, entity: Entity, formatting: Formatting) {
+ self.formatting.insert(entity, formatting);
+ }
+
+ /// Get a formatting settings for a given [Entity].
+ pub fn get_formatting(&self, entity: Entity) -> &Formatting {
+ self.formatting.get(entity)
+ }
+
+ /// Set a vertical alignment to a given cells.
+ pub fn set_alignment_vertical(&mut self, entity: Entity, alignment: AlignmentVertical) {
+ self.alignment_v.insert(entity, alignment);
+ }
+
+ /// Get a vertical alignment for a given [Entity].
+ pub fn get_alignment_vertical(&self, entity: Entity) -> &AlignmentVertical {
+ self.alignment_v.get(entity)
+ }
+
+ /// Set a horizontal alignment to a given cells.
+ pub fn set_alignment_horizontal(&mut self, entity: Entity, alignment: AlignmentHorizontal) {
+ self.alignment_h.insert(entity, alignment);
+ }
+
+ /// Get a horizontal alignment for a given [Entity].
+ pub fn get_alignment_horizontal(&self, entity: Entity) -> &AlignmentHorizontal {
+ self.alignment_h.get(entity)
+ }
+
+ /// Set border set a border value to all cells in [`Entity`].
+ pub fn set_border(&mut self, pos: Position, border: Border<char>) {
+ self.borders.insert_border(pos, border);
+ }
+
+ /// Returns a border of a cell.
+ pub fn get_border(&self, pos: Position, shape: (usize, usize)) -> Border<char> {
+ self.borders.get_border(pos, shape).copied()
+ }
+
+ /// Returns a border color of a cell.
+ pub fn get_border_color(
+ &self,
+ pos: Position,
+ shape: (usize, usize),
+ ) -> Border<&AnsiColor<'static>> {
+ self.borders_colors.get_border(pos, shape)
+ }
+
+ /// Set a character which will be used in case any misconfiguration of borders.
+ ///
+ /// It will be usde for example when you set a left char for border frame and top but didn't set a top left corner.
+ pub fn set_borders_missing(&mut self, c: char) {
+ self.borders_missing_char = c;
+ }
+
+ /// Get a character which will be used in case any misconfiguration of borders.
+ pub fn get_borders_missing(&self) -> char {
+ self.borders_missing_char
+ }
+
+ /// Gets a color of all borders on the grid.
+ pub fn get_border_color_global(&self) -> Option<&AnsiColor<'static>> {
+ self.borders_colors.get_global()
+ }
+
+ /// Sets a color of all borders on the grid.
+ pub fn set_border_color_global(&mut self, clr: AnsiColor<'static>) {
+ self.borders_colors = BordersConfig::default();
+ self.borders_colors.set_global(clr);
+ }
+
+ /// Gets colors of a borders carcass on the grid.
+ pub fn get_color_borders(&self) -> &Borders<AnsiColor<'static>> {
+ self.borders_colors.get_borders()
+ }
+
+ /// Sets colors of border carcass on the grid.
+ pub fn set_borders_color(&mut self, clrs: Borders<AnsiColor<'static>>) {
+ self.borders_colors.set_borders(clrs);
+ }
+
+ /// Sets a color of border of a cell on the grid.
+ pub fn set_border_color(&mut self, pos: Position, border: Border<AnsiColor<'static>>) {
+ self.borders_colors.insert_border(pos, border)
+ }
+
+ /// Sets off all borders possible on the [`Entity`].
+ ///
+ /// It doesn't changes globally set borders through [`SpannedConfig::set_borders`].
+ //
+ // todo: would be great to remove a shape
+ pub fn remove_border(&mut self, pos: Position, shape: (usize, usize)) {
+ self.borders.remove_border(pos, shape);
+ }
+
+ /// Gets a color of border of a cell on the grid.
+ //
+ // todo: would be great to remove a shape
+ pub fn remove_border_color(&mut self, pos: Position, shape: (usize, usize)) {
+ self.borders_colors.remove_border(pos, shape);
+ }
+
+ /// Get a justification which will be used while expanding cells width/height.
+ pub fn get_justification(&self, entity: Entity) -> char {
+ *self.justification.get(entity)
+ }
+
+ /// Get a justification color which will be used while expanding cells width/height.
+ ///
+ /// `None` means no color.
+ pub fn get_justification_color(&self, entity: Entity) -> Option<&AnsiColor<'static>> {
+ self.justification_color.get(entity).as_ref()
+ }
+
+ /// Set a justification which will be used while expanding cells width/height.
+ pub fn set_justification(&mut self, entity: Entity, c: char) {
+ self.justification.insert(entity, c);
+ }
+
+ /// Set a justification color which will be used while expanding cells width/height.
+ ///
+ /// `None` removes it.
+ pub fn set_justification_color(&mut self, entity: Entity, color: Option<AnsiColor<'static>>) {
+ self.justification_color.insert(entity, color);
+ }
+
+ /// Get a span value of the cell, if any is set.
+ pub fn get_column_spans(&self) -> HashMap<Position, usize> {
+ self.span_columns.clone()
+ }
+
+ /// Get a span value of the cell, if any is set.
+ pub fn get_row_spans(&self) -> HashMap<Position, usize> {
+ self.span_rows.clone()
+ }
+
+ /// Get a span value of the cell, if any is set.
+ pub fn get_column_span(&self, pos: Position) -> Option<usize> {
+ self.span_columns.get(&pos).copied()
+ }
+
+ /// Get a span value of the cell, if any is set.
+ pub fn get_row_span(&self, pos: Position) -> Option<usize> {
+ self.span_rows.get(&pos).copied()
+ }
+
+ /// Removes column spans.
+ pub fn remove_column_spans(&mut self) {
+ self.span_columns.clear()
+ }
+
+ /// Removes row spans.
+ pub fn remove_row_spans(&mut self) {
+ self.span_rows.clear()
+ }
+
+ /// Set a column span to a given cells.
+ ///
+ /// BEWARE
+ ///
+ /// IT'S CALLER RESPONSIBILITY TO MAKE SURE
+ /// THAT THERE NO INTERSECTIONS IN PLACE AND THE SPAN VALUE IS CORRECT
+ pub fn set_column_span(&mut self, pos: Position, span: usize) {
+ set_cell_column_span(self, pos, span);
+ }
+
+ /// Verifies if there's any spans set.
+ pub fn has_column_spans(&self) -> bool {
+ !self.span_columns.is_empty()
+ }
+
+ /// Set a column span to a given cells.
+ ///
+ /// BEWARE
+ ///
+ /// IT'S CALLER RESPONSIBILITY TO MAKE SURE
+ /// THAT THERE NO INTERSECTIONS IN PLACE AND THE SPAN VALUE IS CORRECT
+ pub fn set_row_span(&mut self, pos: Position, span: usize) {
+ set_cell_row_span(self, pos, span);
+ }
+
+ /// Verifies if there's any spans set.
+ pub fn has_row_spans(&self) -> bool {
+ !self.span_rows.is_empty()
+ }
+
+ /// Gets an intersection character which would be rendered on the grid.
+ ///
+ /// grid: crate::Grid
+ pub fn get_intersection(&self, pos: Position, shape: (usize, usize)) -> Option<char> {
+ let c = self.borders.get_intersection(pos, shape);
+ if let Some(c) = c {
+ return Some(*c);
+ }
+
+ if self.has_horizontal(pos.0, shape.0) && self.has_vertical(pos.1, shape.1) {
+ return Some(self.get_borders_missing());
+ }
+
+ None
+ }
+
+ /// Gets a horizontal character which would be rendered on the grid.
+ ///
+ /// grid: crate::Grid
+ pub fn get_horizontal(&self, pos: Position, count_rows: usize) -> Option<char> {
+ let c = self.borders.get_horizontal(pos, count_rows);
+ if let Some(c) = c {
+ return Some(*c);
+ }
+
+ if self.has_horizontal(pos.0, count_rows) {
+ return Some(self.get_borders_missing());
+ }
+
+ None
+ }
+
+ /// Gets a vertical character which would be rendered on the grid.
+ ///
+ /// grid: crate::Grid
+ pub fn get_vertical(&self, pos: Position, count_columns: usize) -> Option<char> {
+ if let Some(c) = self.borders.get_vertical(pos, count_columns) {
+ return Some(*c);
+ }
+
+ if self.has_vertical(pos.1, count_columns) {
+ return Some(self.get_borders_missing());
+ }
+
+ None
+ }
+
+ /// Gets a color of a cell horizontal.
+ pub fn get_horizontal_color(
+ &self,
+ pos: Position,
+ count_rows: usize,
+ ) -> Option<&AnsiColor<'static>> {
+ self.borders_colors.get_horizontal(pos, count_rows)
+ }
+
+ /// Gets a color of a cell vertical.
+ pub fn get_vertical_color(
+ &self,
+ pos: Position,
+ count_columns: usize,
+ ) -> Option<&AnsiColor<'static>> {
+ self.borders_colors.get_vertical(pos, count_columns)
+ }
+
+ /// Gets a color of a cell vertical.
+ pub fn get_intersection_color(
+ &self,
+ pos: Position,
+ shape: (usize, usize),
+ ) -> Option<&AnsiColor<'static>> {
+ self.borders_colors.get_intersection(pos, shape)
+ }
+
+ /// Checks if grid would have a horizontal border with the current configuration.
+ ///
+ /// grid: crate::Grid
+ pub fn has_horizontal(&self, row: usize, count_rows: usize) -> bool {
+ self.borders.has_horizontal(row, count_rows)
+ }
+
+ /// Checks if grid would have a vertical border with the current configuration.
+ ///
+ /// grid: crate::Grid
+ pub fn has_vertical(&self, col: usize, count_columns: usize) -> bool {
+ self.borders.has_vertical(col, count_columns)
+ }
+
+ /// Calculates an amount of horizontal lines would present on the grid.
+ ///
+ /// grid: crate::Grid
+ pub fn count_horizontal(&self, count_rows: usize) -> usize {
+ (0..=count_rows)
+ .filter(|&row| self.has_horizontal(row, count_rows))
+ .count()
+ }
+
+ /// Calculates an amount of vertical lines would present on the grid.
+ ///
+ /// grid: crate::Grid
+ pub fn count_vertical(&self, count_columns: usize) -> usize {
+ (0..=count_columns)
+ .filter(|&col| self.has_vertical(col, count_columns))
+ .count()
+ }
+
+ /// The function returns whether the cells will be rendered or it will be hidden because of a span.
+ pub fn is_cell_visible(&self, pos: Position) -> bool {
+ !(self.is_cell_covered_by_column_span(pos)
+ || self.is_cell_covered_by_row_span(pos)
+ || self.is_cell_covered_by_both_spans(pos))
+ }
+
+ /// The function checks if a cell is hidden because of a row span.
+ pub fn is_cell_covered_by_row_span(&self, pos: Position) -> bool {
+ is_cell_covered_by_row_span(self, pos)
+ }
+
+ /// The function checks if a cell is hidden because of a column span.
+ pub fn is_cell_covered_by_column_span(&self, pos: Position) -> bool {
+ is_cell_covered_by_column_span(self, pos)
+ }
+
+ /// The function checks if a cell is hidden indirectly because of a row and column span combination.
+ pub fn is_cell_covered_by_both_spans(&self, pos: Position) -> bool {
+ is_cell_covered_by_both_spans(self, pos)
+ }
+}
+
+impl From<CompactConfig> for SpannedConfig {
+ fn from(compact: CompactConfig) -> Self {
+ use Entity::Global;
+
+ let mut cfg = Self::default();
+
+ cfg.set_padding(Global, *compact.get_padding());
+ cfg.set_padding_color(Global, to_ansi_color(compact.get_padding_color()));
+ cfg.set_margin(*compact.get_margin());
+ cfg.set_margin_color(to_ansi_color(compact.get_margin_color()));
+ cfg.set_alignment_horizontal(Global, compact.get_alignment_horizontal());
+ cfg.set_borders(*compact.get_borders());
+ cfg.set_borders_color(borders_static_color_to_ansi_color(
+ *compact.get_borders_color(),
+ ));
+
+ if let Some(line) = compact.get_first_horizontal_line() {
+ cfg.insert_horizontal_line(
+ 1,
+ HorizontalLine {
+ intersection: line.intersection,
+ left: line.connect1,
+ right: line.connect2,
+ main: Some(line.main),
+ },
+ );
+ }
+
+ cfg
+ }
+}
+
+fn to_ansi_color(b: Sides<StaticColor>) -> Sides<Option<AnsiColor<'static>>> {
+ Sides::new(
+ Some(b.left.into()),
+ Some(b.right.into()),
+ Some(b.top.into()),
+ Some(b.bottom.into()),
+ )
+}
+
+fn borders_static_color_to_ansi_color(b: Borders<StaticColor>) -> Borders<AnsiColor<'static>> {
+ Borders {
+ left: b.left.map(|c| c.into()),
+ right: b.right.map(|c| c.into()),
+ top: b.top.map(|c| c.into()),
+ bottom: b.bottom.map(|c| c.into()),
+ bottom_intersection: b.bottom_intersection.map(|c| c.into()),
+ bottom_left: b.bottom_left.map(|c| c.into()),
+ bottom_right: b.bottom_right.map(|c| c.into()),
+ horizontal: b.horizontal.map(|c| c.into()),
+ intersection: b.intersection.map(|c| c.into()),
+ left_intersection: b.left_intersection.map(|c| c.into()),
+ right_intersection: b.right_intersection.map(|c| c.into()),
+ top_intersection: b.top_intersection.map(|c| c.into()),
+ top_left: b.top_left.map(|c| c.into()),
+ top_right: b.top_right.map(|c| c.into()),
+ vertical: b.vertical.map(|c| c.into()),
+ }
+}
+
+fn set_cell_row_span(cfg: &mut SpannedConfig, pos: Position, span: usize) {
+ // such spans aren't supported
+ if span == 0 {
+ return;
+ }
+
+ // It's a default span so we can do nothing.
+ // but we check if it's an override of a span.
+ if span == 1 {
+ cfg.span_rows.remove(&pos);
+ return;
+ }
+
+ cfg.span_rows.insert(pos, span);
+}
+
+fn set_cell_column_span(cfg: &mut SpannedConfig, pos: Position, span: usize) {
+ // such spans aren't supported
+ if span == 0 {
+ return;
+ }
+
+ // It's a default span so we can do nothing.
+ // but we check if it's an override of a span.
+ if span == 1 {
+ cfg.span_columns.remove(&pos);
+ return;
+ }
+
+ cfg.span_columns.insert(pos, span);
+}
+
+fn is_cell_covered_by_column_span(cfg: &SpannedConfig, pos: Position) -> bool {
+ cfg.span_columns
+ .iter()
+ .any(|(&(row, col), span)| pos.1 > col && pos.1 < col + span && row == pos.0)
+}
+
+fn is_cell_covered_by_row_span(cfg: &SpannedConfig, pos: Position) -> bool {
+ cfg.span_rows
+ .iter()
+ .any(|(&(row, col), span)| pos.0 > row && pos.0 < row + span && col == pos.1)
+}
+
+fn is_cell_covered_by_both_spans(cfg: &SpannedConfig, pos: Position) -> bool {
+ if !cfg.has_column_spans() || !cfg.has_row_spans() {
+ return false;
+ }
+
+ cfg.span_rows.iter().any(|(p1, row_span)| {
+ cfg.span_columns
+ .iter()
+ .filter(|(p2, _)| &p1 == p2)
+ .any(|(_, col_span)| {
+ pos.0 > p1.0 && pos.0 < p1.0 + row_span && pos.1 > p1.1 && pos.1 < p1.1 + col_span
+ })
+ })
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Eq)]
+struct ColoredIndent {
+ indent: Indent,
+ color: Option<AnsiColor<'static>>,
+}
+
+/// A colorefull margin indent.
+#[derive(Debug, Clone, PartialEq, Eq)]
+struct ColoredMarginIndent {
+ /// An indent value.
+ indent: Indent,
+ /// An offset value.
+ offset: Offset,
+ /// An color value.
+ color: Option<AnsiColor<'static>>,
+}
+
+impl Default for ColoredMarginIndent {
+ fn default() -> Self {
+ Self {
+ indent: Indent::default(),
+ offset: Offset::Begin(0),
+ color: None,
+ }
+ }
+}
+
+/// HorizontalLine represents a horizontal border line.
+pub type HorizontalLine = borders_config::HorizontalLine<char>;
+
+/// HorizontalLine represents a vertical border line.
+pub type VerticalLine = borders_config::VerticalLine<char>;
diff --git a/vendor/papergrid/src/config/spanned/offset.rs b/vendor/papergrid/src/config/spanned/offset.rs
new file mode 100644
index 000000000..03055ca34
--- /dev/null
+++ b/vendor/papergrid/src/config/spanned/offset.rs
@@ -0,0 +1,8 @@
+/// 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),
+}
diff --git a/vendor/papergrid/src/dimension/compact.rs b/vendor/papergrid/src/dimension/compact.rs
new file mode 100644
index 000000000..e53429ad9
--- /dev/null
+++ b/vendor/papergrid/src/dimension/compact.rs
@@ -0,0 +1,121 @@
+//! The module contains a [`CompactGridDimension`] for [`CompactGrid`] height/width estimation.
+//!
+//! [`CompactGrid`]: crate::grid::compact::CompactGrid
+
+use core::cmp::max;
+
+use crate::{
+ dimension::{Dimension, Estimate},
+ records::Records,
+ util::string::{count_lines, string_width_multiline},
+};
+
+use crate::config::compact::CompactConfig;
+
+/// A [`Dimension`] implementation which calculates exact column/row width/height.
+///
+/// [`Grid`]: crate::grid::iterable::Grid
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct CompactGridDimension {
+ height: usize,
+ width: Vec<usize>,
+}
+
+impl CompactGridDimension {
+ /// Calculates height of rows.
+ pub fn height<R: Records>(records: R, cfg: &CompactConfig) -> Vec<usize> {
+ build_height(records, cfg)
+ }
+
+ /// Calculates width of columns.
+ pub fn width<R: Records>(records: R, cfg: &CompactConfig) -> Vec<usize> {
+ build_width(records, cfg)
+ }
+
+ /// Calculates dimensions of columns.
+ pub fn dimension<R: Records>(records: R, cfg: &CompactConfig) -> (Vec<usize>, Vec<usize>) {
+ build_dims(records, cfg)
+ }
+}
+
+impl Dimension for CompactGridDimension {
+ fn get_width(&self, column: usize) -> usize {
+ self.width[column]
+ }
+
+ fn get_height(&self, _: usize) -> usize {
+ self.height
+ }
+}
+
+impl<R> Estimate<R, CompactConfig> for CompactGridDimension
+where
+ R: Records,
+{
+ fn estimate(&mut self, records: R, cfg: &CompactConfig) {
+ self.width = build_width(records, cfg);
+ let pad = cfg.get_padding();
+ self.height = 1 + pad.top.size + pad.bottom.size;
+ }
+}
+
+fn build_dims<R: Records>(records: R, cfg: &CompactConfig) -> (Vec<usize>, Vec<usize>) {
+ let mut heights = vec![];
+ let mut widths = vec![0; records.count_columns()];
+
+ for columns in records.iter_rows() {
+ let mut row_height = 0;
+ for (col, cell) in columns.into_iter().enumerate() {
+ let height = get_cell_height(cell.as_ref(), cfg);
+ let width = get_cell_width(cell.as_ref(), cfg);
+ row_height = max(row_height, height);
+ widths[col] = max(widths[col], width)
+ }
+
+ heights.push(row_height);
+ }
+
+ (widths, heights)
+}
+
+fn build_height<R: Records>(records: R, cfg: &CompactConfig) -> Vec<usize> {
+ let mut heights = vec![];
+
+ for columns in records.iter_rows() {
+ let mut row_height = 0;
+ for cell in columns.into_iter() {
+ let height = get_cell_height(cell.as_ref(), cfg);
+ row_height = max(row_height, height);
+ }
+
+ heights.push(row_height);
+ }
+
+ heights
+}
+
+fn build_width<R: Records>(records: R, cfg: &CompactConfig) -> Vec<usize> {
+ let mut widths = vec![0; records.count_columns()];
+ for columns in records.iter_rows() {
+ for (col, cell) in columns.into_iter().enumerate() {
+ let width = get_cell_width(cell.as_ref(), cfg);
+ widths[col] = max(widths[col], width);
+ }
+ }
+
+ widths
+}
+
+fn get_cell_height(cell: &str, cfg: &CompactConfig) -> usize {
+ let count_lines = max(1, count_lines(cell));
+ let pad = cfg.get_padding();
+
+ count_lines + pad.top.size + pad.bottom.size
+}
+
+fn get_cell_width(text: &str, cfg: &CompactConfig) -> usize {
+ let width = string_width_multiline(text);
+ let pad = cfg.get_padding();
+
+ width + pad.left.size + pad.right.size
+}
diff --git a/vendor/papergrid/src/dimension/mod.rs b/vendor/papergrid/src/dimension/mod.rs
new file mode 100644
index 000000000..40be17b48
--- /dev/null
+++ b/vendor/papergrid/src/dimension/mod.rs
@@ -0,0 +1,44 @@
+//! The module contains an [`Dimension`] trait and its implementations.
+
+#[cfg(feature = "std")]
+pub mod compact;
+#[cfg(feature = "std")]
+pub mod spanned;
+#[cfg(feature = "std")]
+pub mod spanned_vec_records;
+
+/// Dimension of a [`Grid`]
+///
+/// It's a friend trait of [`Estimate`].
+///
+/// [`Grid`]: crate::grid::iterable::Grid
+pub trait Dimension {
+ /// Get a column width by index.
+ fn get_width(&self, column: usize) -> usize;
+
+ /// Get a row height by index.
+ fn get_height(&self, row: usize) -> usize;
+}
+
+impl<T> Dimension for &T
+where
+ T: Dimension,
+{
+ fn get_height(&self, row: usize) -> usize {
+ T::get_height(self, row)
+ }
+
+ fn get_width(&self, column: usize) -> usize {
+ T::get_width(self, column)
+ }
+}
+
+/// Dimension estimation of a [`Grid`]
+///
+/// It's a friend trait of [`Dimension`].
+///
+/// [`Grid`]: crate::grid::iterable::Grid
+pub trait Estimate<R, C> {
+ /// Estimates a metric.
+ fn estimate(&mut self, records: R, config: &C);
+}
diff --git a/vendor/papergrid/src/dimension/spanned.rs b/vendor/papergrid/src/dimension/spanned.rs
new file mode 100644
index 000000000..6597ba176
--- /dev/null
+++ b/vendor/papergrid/src/dimension/spanned.rs
@@ -0,0 +1,329 @@
+//! The module contains a [`SpannedGridDimension`] for [`Grid`] height/width estimation.
+//!
+//! [`Grid`]: crate::grid::iterable::Grid
+
+use std::{
+ cmp::{max, Ordering},
+ collections::HashMap,
+};
+
+use crate::{
+ config::Position,
+ dimension::{Dimension, Estimate},
+ records::Records,
+ util::string::{count_lines, string_dimension, string_width_multiline},
+};
+
+use crate::config::spanned::SpannedConfig;
+
+/// A [`Dimension`] implementation which calculates exact column/row width/height.
+///
+/// [`Grid`]: crate::grid::iterable::Grid
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct SpannedGridDimension {
+ height: Vec<usize>,
+ width: Vec<usize>,
+}
+
+impl SpannedGridDimension {
+ /// Calculates height of rows.
+ pub fn height<R: Records>(records: R, cfg: &SpannedConfig) -> Vec<usize> {
+ build_height(records, cfg)
+ }
+
+ /// Calculates width of columns.
+ pub fn width<R: Records>(records: R, cfg: &SpannedConfig) -> Vec<usize> {
+ build_width(records, cfg)
+ }
+
+ /// Return width and height lists.
+ pub fn get_values(self) -> (Vec<usize>, Vec<usize>) {
+ (self.width, self.height)
+ }
+}
+
+impl Dimension for SpannedGridDimension {
+ fn get_width(&self, column: usize) -> usize {
+ self.width[column]
+ }
+
+ fn get_height(&self, row: usize) -> usize {
+ self.height[row]
+ }
+}
+
+impl<R> Estimate<R, SpannedConfig> for SpannedGridDimension
+where
+ R: Records,
+{
+ fn estimate(&mut self, records: R, cfg: &SpannedConfig) {
+ let (width, height) = build_dimensions(records, cfg);
+ self.width = width;
+ self.height = height;
+ }
+}
+
+fn build_dimensions<R: Records>(records: R, cfg: &SpannedConfig) -> (Vec<usize>, Vec<usize>) {
+ let count_columns = records.count_columns();
+
+ let mut widths = vec![0; count_columns];
+ let mut heights = vec![];
+
+ let mut vspans = HashMap::new();
+ let mut hspans = HashMap::new();
+
+ for (row, columns) in records.iter_rows().into_iter().enumerate() {
+ let mut row_height = 0;
+ for (col, cell) in columns.into_iter().enumerate() {
+ let pos = (row, col);
+ if !cfg.is_cell_visible(pos) {
+ continue;
+ }
+
+ let text = cell.as_ref();
+ let (height, width) = string_dimension(text);
+ let pad = cfg.get_padding(pos.into());
+ let width = width + pad.left.size + pad.right.size;
+ let height = height + pad.top.size + pad.bottom.size;
+
+ match cfg.get_column_span(pos) {
+ Some(n) if n > 1 => {
+ vspans.insert(pos, (n, width));
+ }
+ _ => widths[col] = max(widths[col], width),
+ }
+
+ match cfg.get_row_span(pos) {
+ Some(n) if n > 1 => {
+ hspans.insert(pos, (n, height));
+ }
+ _ => row_height = max(row_height, height),
+ }
+ }
+
+ heights.push(row_height);
+ }
+
+ let count_rows = heights.len();
+
+ adjust_vspans(cfg, count_columns, &vspans, &mut widths);
+ adjust_hspans(cfg, count_rows, &hspans, &mut heights);
+
+ (widths, heights)
+}
+
+fn adjust_hspans(
+ cfg: &SpannedConfig,
+ len: usize,
+ spans: &HashMap<Position, (usize, usize)>,
+ heights: &mut [usize],
+) {
+ if spans.is_empty() {
+ return;
+ }
+
+ let mut spans_ordered = spans
+ .iter()
+ .map(|(k, v)| ((k.0, k.1), *v))
+ .collect::<Vec<_>>();
+ spans_ordered.sort_unstable_by(|(arow, acol), (brow, bcol)| match arow.cmp(brow) {
+ Ordering::Equal => acol.cmp(bcol),
+ ord => ord,
+ });
+
+ for ((row, _), (span, height)) in spans_ordered {
+ adjust_row_range(cfg, height, len, row, row + span, heights);
+ }
+}
+
+fn adjust_row_range(
+ cfg: &SpannedConfig,
+ max_span_height: usize,
+ len: usize,
+ start: usize,
+ end: usize,
+ heights: &mut [usize],
+) {
+ let range_height = range_height(cfg, len, start, end, heights);
+ if range_height >= max_span_height {
+ return;
+ }
+
+ inc_range(heights, max_span_height - range_height, start, end);
+}
+
+fn range_height(
+ cfg: &SpannedConfig,
+ len: usize,
+ start: usize,
+ end: usize,
+ heights: &[usize],
+) -> usize {
+ let count_borders = count_horizontal_borders(cfg, len, start, end);
+ let range_height = heights[start..end].iter().sum::<usize>();
+ count_borders + range_height
+}
+
+fn count_horizontal_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize {
+ (start..end)
+ .skip(1)
+ .filter(|&i| cfg.has_horizontal(i, len))
+ .count()
+}
+
+fn get_cell_height(cell: &str, cfg: &SpannedConfig, pos: Position) -> usize {
+ let count_lines = max(1, count_lines(cell));
+ let padding = cfg.get_padding(pos.into());
+ count_lines + padding.top.size + padding.bottom.size
+}
+
+fn inc_range(list: &mut [usize], size: usize, start: usize, end: usize) {
+ if list.is_empty() {
+ return;
+ }
+
+ let span = end - start;
+ let one = size / span;
+ let rest = size - span * one;
+
+ let mut i = start;
+ while i < end {
+ if i == start {
+ list[i] += one + rest;
+ } else {
+ list[i] += one;
+ }
+
+ i += 1;
+ }
+}
+
+fn adjust_vspans(
+ cfg: &SpannedConfig,
+ len: usize,
+ spans: &HashMap<Position, (usize, usize)>,
+ widths: &mut [usize],
+) {
+ if spans.is_empty() {
+ return;
+ }
+
+ // The overall width distribution will be different depend on the order.
+ //
+ // We sort spans in order to prioritize the smaller spans first.
+ let mut spans_ordered = spans
+ .iter()
+ .map(|(k, v)| ((k.0, k.1), *v))
+ .collect::<Vec<_>>();
+ spans_ordered.sort_unstable_by(|a, b| match a.1 .0.cmp(&b.1 .0) {
+ Ordering::Equal => a.0.cmp(&b.0),
+ o => o,
+ });
+
+ for ((_, col), (span, width)) in spans_ordered {
+ adjust_column_range(cfg, width, len, col, col + span, widths);
+ }
+}
+
+fn adjust_column_range(
+ cfg: &SpannedConfig,
+ max_span_width: usize,
+ len: usize,
+ start: usize,
+ end: usize,
+ widths: &mut [usize],
+) {
+ let range_width = range_width(cfg, len, start, end, widths);
+ if range_width >= max_span_width {
+ return;
+ }
+
+ inc_range(widths, max_span_width - range_width, start, end);
+}
+
+fn get_cell_width(text: &str, cfg: &SpannedConfig, pos: Position) -> usize {
+ let padding = get_cell_padding(cfg, pos);
+ let width = string_width_multiline(text);
+ width + padding
+}
+
+fn get_cell_padding(cfg: &SpannedConfig, pos: Position) -> usize {
+ let padding = cfg.get_padding(pos.into());
+ padding.left.size + padding.right.size
+}
+
+fn range_width(
+ cfg: &SpannedConfig,
+ len: usize,
+ start: usize,
+ end: usize,
+ widths: &[usize],
+) -> usize {
+ let count_borders = count_vertical_borders(cfg, len, start, end);
+ let range_width = widths[start..end].iter().sum::<usize>();
+ count_borders + range_width
+}
+
+fn count_vertical_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize {
+ (start..end)
+ .skip(1)
+ .filter(|&i| cfg.has_vertical(i, len))
+ .count()
+}
+
+fn build_height<R: Records>(records: R, cfg: &SpannedConfig) -> Vec<usize> {
+ let mut heights = vec![];
+ let mut hspans = HashMap::new();
+
+ for (row, columns) in records.iter_rows().into_iter().enumerate() {
+ let mut row_height = 0;
+ for (col, cell) in columns.into_iter().enumerate() {
+ let pos = (row, col);
+ if !cfg.is_cell_visible(pos) {
+ continue;
+ }
+
+ let height = get_cell_height(cell.as_ref(), cfg, pos);
+ match cfg.get_row_span(pos) {
+ Some(n) if n > 1 => {
+ hspans.insert(pos, (n, height));
+ }
+ _ => row_height = max(row_height, height),
+ }
+ }
+
+ heights.push(row_height);
+ }
+
+ adjust_hspans(cfg, heights.len(), &hspans, &mut heights);
+
+ heights
+}
+
+fn build_width<R: Records>(records: R, cfg: &SpannedConfig) -> Vec<usize> {
+ let count_columns = records.count_columns();
+
+ let mut widths = vec![0; count_columns];
+ let mut vspans = HashMap::new();
+
+ for (row, columns) in records.iter_rows().into_iter().enumerate() {
+ for (col, cell) in columns.into_iter().enumerate() {
+ let pos = (row, col);
+ if !cfg.is_cell_visible(pos) {
+ continue;
+ }
+
+ let width = get_cell_width(cell.as_ref(), cfg, pos);
+ match cfg.get_column_span(pos) {
+ Some(n) if n > 1 => {
+ vspans.insert(pos, (n, width));
+ }
+ _ => widths[col] = max(widths[col], width),
+ }
+ }
+ }
+
+ adjust_vspans(cfg, count_columns, &vspans, &mut widths);
+
+ widths
+}
diff --git a/vendor/papergrid/src/dimension/spanned_vec_records.rs b/vendor/papergrid/src/dimension/spanned_vec_records.rs
new file mode 100644
index 000000000..75e85a850
--- /dev/null
+++ b/vendor/papergrid/src/dimension/spanned_vec_records.rs
@@ -0,0 +1,332 @@
+//! The module contains a [`SpannedVecRecordsDimension`] for [`Grid`] height/width estimation.
+//!
+//! [`Grid`]: crate::grid::iterable::Grid
+
+use std::{
+ cmp::{max, Ordering},
+ collections::HashMap,
+};
+
+use crate::{
+ config::Position,
+ dimension::{Dimension, Estimate},
+ records::{
+ vec_records::{Cell, VecRecords},
+ Records,
+ },
+};
+
+use crate::config::spanned::SpannedConfig;
+
+/// A [`Dimension`] implementation which calculates exact column/row width/height for [`VecRecords`].
+///
+/// It is a specialization of [`SpannedGridDimension`] for [`VecRecords`].
+///
+/// [`SpannedGridDimension`]: crate::dimension::spanned::SpannedGridDimension
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct SpannedVecRecordsDimension {
+ height: Vec<usize>,
+ width: Vec<usize>,
+}
+
+impl SpannedVecRecordsDimension {
+ /// Calculates height of rows.
+ pub fn height<T: Cell + AsRef<str>>(
+ records: &VecRecords<T>,
+ cfg: &SpannedConfig,
+ ) -> Vec<usize> {
+ build_height(records, cfg)
+ }
+
+ /// Calculates width of columns.
+ pub fn width<T: Cell + AsRef<str>>(records: &VecRecords<T>, cfg: &SpannedConfig) -> Vec<usize> {
+ build_width(records, cfg)
+ }
+
+ /// Return width and height lists.
+ pub fn get_values(self) -> (Vec<usize>, Vec<usize>) {
+ (self.width, self.height)
+ }
+}
+
+impl Dimension for SpannedVecRecordsDimension {
+ fn get_width(&self, column: usize) -> usize {
+ self.width[column]
+ }
+
+ fn get_height(&self, row: usize) -> usize {
+ self.height[row]
+ }
+}
+
+impl<T> Estimate<&VecRecords<T>, SpannedConfig> for SpannedVecRecordsDimension
+where
+ T: Cell + AsRef<str>,
+{
+ fn estimate(&mut self, records: &VecRecords<T>, cfg: &SpannedConfig) {
+ let (width, height) = build_dimensions(records, cfg);
+ self.width = width;
+ self.height = height;
+ }
+}
+
+fn build_dimensions<T: Cell + AsRef<str>>(
+ records: &VecRecords<T>,
+ cfg: &SpannedConfig,
+) -> (Vec<usize>, Vec<usize>) {
+ let count_columns = records.count_columns();
+
+ let mut widths = vec![0; count_columns];
+ let mut heights = vec![];
+
+ let mut vspans = HashMap::new();
+ let mut hspans = HashMap::new();
+
+ for (row, columns) in records.iter_rows().enumerate() {
+ let mut row_height = 0;
+ for (col, cell) in columns.iter().enumerate() {
+ let pos = (row, col);
+ if !cfg.is_cell_visible(pos) {
+ continue;
+ }
+
+ let width = cell.width();
+ let height = cell.count_lines();
+ let pad = cfg.get_padding(pos.into());
+ let width = width + pad.left.size + pad.right.size;
+ let height = height + pad.top.size + pad.bottom.size;
+
+ match cfg.get_column_span(pos) {
+ Some(n) if n > 1 => {
+ vspans.insert(pos, (n, width));
+ }
+ _ => widths[col] = max(widths[col], width),
+ }
+
+ match cfg.get_row_span(pos) {
+ Some(n) if n > 1 => {
+ hspans.insert(pos, (n, height));
+ }
+ _ => row_height = max(row_height, height),
+ }
+ }
+
+ heights.push(row_height);
+ }
+
+ let count_rows = heights.len();
+
+ adjust_vspans(cfg, count_columns, &vspans, &mut widths);
+ adjust_hspans(cfg, count_rows, &hspans, &mut heights);
+
+ (widths, heights)
+}
+
+fn adjust_hspans(
+ cfg: &SpannedConfig,
+ len: usize,
+ spans: &HashMap<Position, (usize, usize)>,
+ heights: &mut [usize],
+) {
+ if spans.is_empty() {
+ return;
+ }
+
+ let mut spans_ordered = spans
+ .iter()
+ .map(|(k, v)| ((k.0, k.1), *v))
+ .collect::<Vec<_>>();
+ spans_ordered.sort_unstable_by(|(arow, acol), (brow, bcol)| match arow.cmp(brow) {
+ Ordering::Equal => acol.cmp(bcol),
+ ord => ord,
+ });
+
+ for ((row, _), (span, height)) in spans_ordered {
+ adjust_row_range(cfg, height, len, row, row + span, heights);
+ }
+}
+
+fn adjust_row_range(
+ cfg: &SpannedConfig,
+ max_span_height: usize,
+ len: usize,
+ start: usize,
+ end: usize,
+ heights: &mut [usize],
+) {
+ let range_height = range_height(cfg, len, start, end, heights);
+ if range_height >= max_span_height {
+ return;
+ }
+
+ inc_range(heights, max_span_height - range_height, start, end);
+}
+
+fn range_height(
+ cfg: &SpannedConfig,
+ len: usize,
+ start: usize,
+ end: usize,
+ heights: &[usize],
+) -> usize {
+ let count_borders = count_horizontal_borders(cfg, len, start, end);
+ let range_height = heights[start..end].iter().sum::<usize>();
+ count_borders + range_height
+}
+
+fn count_horizontal_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize {
+ (start..end)
+ .skip(1)
+ .filter(|&i| cfg.has_horizontal(i, len))
+ .count()
+}
+
+fn inc_range(list: &mut [usize], size: usize, start: usize, end: usize) {
+ if list.is_empty() {
+ return;
+ }
+
+ let span = end - start;
+ let one = size / span;
+ let rest = size - span * one;
+
+ let mut i = start;
+ while i < end {
+ if i == start {
+ list[i] += one + rest;
+ } else {
+ list[i] += one;
+ }
+
+ i += 1;
+ }
+}
+
+fn adjust_vspans(
+ cfg: &SpannedConfig,
+ len: usize,
+ spans: &HashMap<Position, (usize, usize)>,
+ widths: &mut [usize],
+) {
+ if spans.is_empty() {
+ return;
+ }
+
+ // The overall width distribution will be different depend on the order.
+ //
+ // We sort spans in order to prioritize the smaller spans first.
+ let mut spans_ordered = spans
+ .iter()
+ .map(|(k, v)| ((k.0, k.1), *v))
+ .collect::<Vec<_>>();
+ spans_ordered.sort_unstable_by(|a, b| match a.1 .0.cmp(&b.1 .0) {
+ Ordering::Equal => a.0.cmp(&b.0),
+ o => o,
+ });
+
+ for ((_, col), (span, width)) in spans_ordered {
+ adjust_column_range(cfg, width, len, col, col + span, widths);
+ }
+}
+
+fn adjust_column_range(
+ cfg: &SpannedConfig,
+ max_span_width: usize,
+ len: usize,
+ start: usize,
+ end: usize,
+ widths: &mut [usize],
+) {
+ let range_width = range_width(cfg, len, start, end, widths);
+ if range_width >= max_span_width {
+ return;
+ }
+
+ inc_range(widths, max_span_width - range_width, start, end);
+}
+
+fn get_cell_padding_horizontal(cfg: &SpannedConfig, pos: Position) -> usize {
+ let padding = cfg.get_padding(pos.into());
+ padding.left.size + padding.right.size
+}
+
+fn get_cell_vertical_padding(cfg: &SpannedConfig, pos: Position) -> usize {
+ let padding = cfg.get_padding(pos.into());
+ padding.top.size + padding.bottom.size
+}
+
+fn range_width(
+ cfg: &SpannedConfig,
+ len: usize,
+ start: usize,
+ end: usize,
+ widths: &[usize],
+) -> usize {
+ let count_borders = count_vertical_borders(cfg, len, start, end);
+ let range_width = widths[start..end].iter().sum::<usize>();
+ count_borders + range_width
+}
+
+fn count_vertical_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize {
+ (start..end)
+ .skip(1)
+ .filter(|&i| cfg.has_vertical(i, len))
+ .count()
+}
+
+fn build_height<T: Cell + AsRef<str>>(records: &VecRecords<T>, cfg: &SpannedConfig) -> Vec<usize> {
+ let mut heights = vec![];
+ let mut hspans = HashMap::new();
+
+ for (row, columns) in records.iter_rows().enumerate() {
+ let mut row_height = 0;
+ for (col, cell) in columns.iter().enumerate() {
+ let pos = (row, col);
+ if !cfg.is_cell_visible(pos) {
+ continue;
+ }
+
+ let height = cell.count_lines() + get_cell_vertical_padding(cfg, pos);
+ match cfg.get_row_span(pos) {
+ Some(n) if n > 1 => {
+ hspans.insert(pos, (n, height));
+ }
+ _ => row_height = max(row_height, height),
+ }
+ }
+
+ heights.push(row_height);
+ }
+
+ adjust_hspans(cfg, heights.len(), &hspans, &mut heights);
+
+ heights
+}
+
+fn build_width<T: Cell + AsRef<str>>(records: &VecRecords<T>, cfg: &SpannedConfig) -> Vec<usize> {
+ let count_columns = records.count_columns();
+
+ let mut widths = vec![0; count_columns];
+ let mut vspans = HashMap::new();
+
+ for (row, columns) in records.iter_rows().enumerate() {
+ for (col, cell) in columns.iter().enumerate() {
+ let pos = (row, col);
+ if !cfg.is_cell_visible(pos) {
+ continue;
+ }
+
+ let width = cell.width() + get_cell_padding_horizontal(cfg, (row, col));
+ match cfg.get_column_span(pos) {
+ Some(n) if n > 1 => {
+ vspans.insert(pos, (n, width));
+ }
+ _ => widths[col] = max(widths[col], width),
+ }
+ }
+ }
+
+ adjust_vspans(cfg, count_columns, &vspans, &mut widths);
+
+ widths
+}
diff --git a/vendor/papergrid/src/grid/compact.rs b/vendor/papergrid/src/grid/compact.rs
new file mode 100644
index 000000000..f9536bd80
--- /dev/null
+++ b/vendor/papergrid/src/grid/compact.rs
@@ -0,0 +1,659 @@
+//! The module contains a [`CompactGrid`] structure,
+//! which is a relatively strict grid.
+
+use core::{
+ borrow::Borrow,
+ fmt::{self, Display, Write},
+};
+
+use crate::{
+ color::{Color, StaticColor},
+ colors::{Colors, NoColors},
+ config::{AlignmentHorizontal, Borders, Indent, Line, Sides},
+ dimension::Dimension,
+ records::Records,
+ util::string::string_width,
+};
+
+use crate::config::compact::CompactConfig;
+
+/// Grid provides a set of methods for building a text-based table.
+#[derive(Debug, Clone)]
+pub struct CompactGrid<R, D, G, C> {
+ records: R,
+ config: G,
+ dimension: D,
+ colors: C,
+}
+
+impl<R, D, G> CompactGrid<R, D, G, NoColors> {
+ /// The new method creates a grid instance with default styles.
+ pub fn new(records: R, dimension: D, config: G) -> Self {
+ CompactGrid {
+ records,
+ config,
+ dimension,
+ colors: NoColors::default(),
+ }
+ }
+}
+
+impl<R, D, G, C> CompactGrid<R, D, G, C> {
+ /// Sets colors map.
+ pub fn with_colors<Colors>(self, colors: Colors) -> CompactGrid<R, D, G, Colors> {
+ CompactGrid {
+ records: self.records,
+ config: self.config,
+ dimension: self.dimension,
+ colors,
+ }
+ }
+
+ /// Builds a table.
+ pub fn build<F>(self, mut f: F) -> fmt::Result
+ where
+ R: Records,
+ D: Dimension,
+ C: Colors,
+ G: Borrow<CompactConfig>,
+ F: Write,
+ {
+ if self.records.count_columns() == 0 {
+ return Ok(());
+ }
+
+ let config = self.config.borrow();
+ print_grid(&mut f, self.records, config, &self.dimension, &self.colors)
+ }
+
+ /// Builds a table into string.
+ ///
+ /// Notice that it consumes self.
+ #[cfg(feature = "std")]
+ #[allow(clippy::inherent_to_string)]
+ pub fn to_string(self) -> String
+ where
+ R: Records,
+ D: Dimension,
+ G: Borrow<CompactConfig>,
+ C: Colors,
+ {
+ let mut buf = String::new();
+ self.build(&mut buf).expect("It's guaranteed to never happen otherwise it's considered an stdlib error or impl error");
+ buf
+ }
+}
+
+impl<R, D, G, C> Display for CompactGrid<R, D, G, C>
+where
+ for<'a> &'a R: Records,
+ D: Dimension,
+ G: Borrow<CompactConfig>,
+ C: Colors,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let records = &self.records;
+ let config = self.config.borrow();
+
+ print_grid(f, records, config, &self.dimension, &self.colors)
+ }
+}
+
+fn print_grid<F: Write, R: Records, D: Dimension, C: Colors>(
+ f: &mut F,
+ records: R,
+ cfg: &CompactConfig,
+ dims: &D,
+ colors: &C,
+) -> fmt::Result {
+ let count_columns = records.count_columns();
+ let count_rows = records.hint_count_rows();
+
+ if count_columns == 0 || matches!(count_rows, Some(0)) {
+ return Ok(());
+ }
+
+ let mut records = records.iter_rows().into_iter();
+ let mut next_columns = records.next();
+
+ if next_columns.is_none() {
+ return Ok(());
+ }
+
+ let wtotal = total_width(cfg, dims, count_columns);
+
+ let borders = cfg.get_borders();
+ let bcolors = cfg.get_borders_color();
+
+ let h_chars = create_horizontal(borders);
+ let h_colors = create_horizontal_colors(bcolors);
+
+ let has_horizontal = borders.has_horizontal();
+ let has_horizontal_colors = bcolors.has_horizontal();
+ let has_horizontal_second = cfg.get_first_horizontal_line().is_some();
+
+ let vert = (
+ borders.left.map(|c| (c, bcolors.left)),
+ borders.vertical.map(|c| (c, bcolors.vertical)),
+ borders.right.map(|c| (c, bcolors.right)),
+ );
+
+ let margin = cfg.get_margin();
+ let margin_color = cfg.get_margin_color();
+ let pad = create_padding(cfg);
+ let align = cfg.get_alignment_horizontal();
+ let mar = (
+ (margin.left, margin_color.left),
+ (margin.right, margin_color.right),
+ );
+
+ let widths = (0..count_columns).map(|col| dims.get_width(col));
+
+ let mut new_line = false;
+
+ if margin.top.size > 0 {
+ let wtotal = wtotal + margin.left.size + margin.right.size;
+ print_indent_lines(f, wtotal, margin.top, margin_color.top)?;
+ new_line = true;
+ }
+
+ if borders.has_top() {
+ if new_line {
+ f.write_char('\n')?
+ }
+
+ print_indent2(f, margin.left, margin_color.left)?;
+
+ let chars = create_horizontal_top(borders);
+ if bcolors.has_top() {
+ let chars_color = create_horizontal_top_colors(bcolors);
+ print_split_line_colored(f, chars, chars_color, dims, count_columns)?;
+ } else {
+ print_split_line(f, chars, dims, count_columns)?;
+ }
+
+ print_indent2(f, margin.right, margin_color.right)?;
+
+ new_line = true;
+ }
+
+ let mut row = 0;
+ while let Some(columns) = next_columns {
+ let columns = columns.into_iter();
+ next_columns = records.next();
+
+ if row > 0 && has_horizontal {
+ if new_line {
+ f.write_char('\n')?;
+ }
+
+ print_indent2(f, margin.left, margin_color.left)?;
+
+ if has_horizontal_colors {
+ print_split_line_colored(f, h_chars, h_colors, dims, count_columns)?;
+ } else {
+ print_split_line(f, h_chars, dims, count_columns)?;
+ }
+
+ print_indent2(f, margin.right, margin_color.right)?;
+ } else if row == 1 && has_horizontal_second {
+ if new_line {
+ f.write_char('\n')?;
+ }
+
+ print_indent2(f, margin.left, margin_color.left)?;
+
+ let h_chars = cfg.get_first_horizontal_line().expect("must be here");
+
+ if has_horizontal_colors {
+ print_split_line_colored(f, h_chars, h_colors, dims, count_columns)?;
+ } else {
+ print_split_line(f, h_chars, dims, count_columns)?;
+ }
+
+ print_indent2(f, margin.right, margin_color.right)?;
+ }
+
+ if new_line {
+ f.write_char('\n')?;
+ }
+
+ let columns = columns
+ .enumerate()
+ .map(|(col, text)| (text, colors.get_color((row, col))));
+
+ let widths = widths.clone();
+ print_grid_row(f, columns, widths, mar, pad, vert, align)?;
+
+ new_line = true;
+ row += 1;
+ }
+
+ if borders.has_bottom() {
+ f.write_char('\n')?;
+
+ print_indent2(f, margin.left, margin_color.left)?;
+
+ let chars = create_horizontal_bottom(borders);
+ if bcolors.has_bottom() {
+ let chars_color = create_horizontal_bottom_colors(bcolors);
+ print_split_line_colored(f, chars, chars_color, dims, count_columns)?;
+ } else {
+ print_split_line(f, chars, dims, count_columns)?;
+ }
+
+ print_indent2(f, margin.right, margin_color.right)?;
+ }
+
+ if cfg.get_margin().bottom.size > 0 {
+ f.write_char('\n')?;
+
+ let wtotal = wtotal + margin.left.size + margin.right.size;
+ print_indent_lines(f, wtotal, margin.bottom, margin_color.bottom)?;
+ }
+
+ Ok(())
+}
+
+type ColoredIndent = (Indent, StaticColor);
+
+#[allow(clippy::too_many_arguments)]
+fn print_grid_row<F, I, T, C, D>(
+ f: &mut F,
+ columns: I,
+ widths: D,
+ mar: (ColoredIndent, ColoredIndent),
+ pad: Sides<ColoredIndent>,
+ vert: (BorderChar, BorderChar, BorderChar),
+ align: AlignmentHorizontal,
+) -> fmt::Result
+where
+ F: Write,
+ I: Iterator<Item = (T, Option<C>)>,
+ T: AsRef<str>,
+ C: Color,
+ D: Iterator<Item = usize> + Clone,
+{
+ if pad.top.0.size > 0 {
+ for _ in 0..pad.top.0.size {
+ print_indent2(f, mar.0 .0, mar.0 .1)?;
+ print_columns_empty_colored(f, widths.clone(), vert, pad.top.1)?;
+ print_indent2(f, mar.1 .0, mar.1 .1)?;
+
+ f.write_char('\n')?;
+ }
+ }
+
+ let mut widths1 = widths.clone();
+ let columns = columns.map(move |(text, color)| {
+ let width = widths1.next().expect("must be here");
+ (text, color, width)
+ });
+
+ print_indent2(f, mar.0 .0, mar.0 .1)?;
+ print_row_columns(f, columns, vert, pad, align)?;
+ print_indent2(f, mar.1 .0, mar.1 .1)?;
+
+ for _ in 0..pad.bottom.0.size {
+ f.write_char('\n')?;
+
+ print_indent2(f, mar.0 .0, mar.0 .1)?;
+ print_columns_empty_colored(f, widths.clone(), vert, pad.bottom.1)?;
+ print_indent2(f, mar.1 .0, mar.1 .1)?;
+ }
+
+ Ok(())
+}
+
+fn create_padding(cfg: &CompactConfig) -> Sides<ColoredIndent> {
+ let pad = cfg.get_padding();
+ let pad_colors = cfg.get_padding_color();
+ Sides::new(
+ (pad.left, pad_colors.left),
+ (pad.right, pad_colors.right),
+ (pad.top, pad_colors.top),
+ (pad.bottom, pad_colors.bottom),
+ )
+}
+
+fn create_horizontal(b: &Borders<char>) -> Line<char> {
+ Line::new(b.horizontal.unwrap_or(' '), b.intersection, b.left, b.right)
+}
+
+fn create_horizontal_top(b: &Borders<char>) -> Line<char> {
+ Line::new(
+ b.top.unwrap_or(' '),
+ b.top_intersection,
+ b.top_left,
+ b.top_right,
+ )
+}
+
+fn create_horizontal_bottom(b: &Borders<char>) -> Line<char> {
+ Line::new(
+ b.bottom.unwrap_or(' '),
+ b.bottom_intersection,
+ b.bottom_left,
+ b.bottom_right,
+ )
+}
+
+fn create_horizontal_colors(
+ b: &Borders<StaticColor>,
+) -> (StaticColor, StaticColor, StaticColor, StaticColor) {
+ (
+ b.horizontal.unwrap_or(StaticColor::default()),
+ b.left.unwrap_or(StaticColor::default()),
+ b.intersection.unwrap_or(StaticColor::default()),
+ b.right.unwrap_or(StaticColor::default()),
+ )
+}
+
+fn create_horizontal_top_colors(
+ b: &Borders<StaticColor>,
+) -> (StaticColor, StaticColor, StaticColor, StaticColor) {
+ (
+ b.top.unwrap_or(StaticColor::default()),
+ b.top_left.unwrap_or(StaticColor::default()),
+ b.top_intersection.unwrap_or(StaticColor::default()),
+ b.top_right.unwrap_or(StaticColor::default()),
+ )
+}
+
+fn create_horizontal_bottom_colors(
+ b: &Borders<StaticColor>,
+) -> (StaticColor, StaticColor, StaticColor, StaticColor) {
+ (
+ b.bottom.unwrap_or(StaticColor::default()),
+ b.bottom_left.unwrap_or(StaticColor::default()),
+ b.bottom_intersection.unwrap_or(StaticColor::default()),
+ b.bottom_right.unwrap_or(StaticColor::default()),
+ )
+}
+
+fn total_width<D: Dimension>(cfg: &CompactConfig, dims: &D, count_columns: usize) -> usize {
+ let content_width = total_columns_width(count_columns, dims);
+ let count_verticals = count_verticals(cfg, count_columns);
+
+ content_width + count_verticals
+}
+
+fn total_columns_width<D: Dimension>(count_columns: usize, dims: &D) -> usize {
+ (0..count_columns).map(|i| dims.get_width(i)).sum::<usize>()
+}
+
+fn count_verticals(cfg: &CompactConfig, count_columns: usize) -> usize {
+ assert!(count_columns > 0);
+
+ let count_verticals = count_columns - 1;
+ let borders = cfg.get_borders();
+ borders.has_vertical() as usize * count_verticals
+ + borders.has_left() as usize
+ + borders.has_right() as usize
+}
+
+type BorderChar = Option<(char, Option<StaticColor>)>;
+
+fn print_row_columns<F, I, T, C>(
+ f: &mut F,
+ mut columns: I,
+ borders: (BorderChar, BorderChar, BorderChar),
+ pad: Sides<ColoredIndent>,
+ align: AlignmentHorizontal,
+) -> Result<(), fmt::Error>
+where
+ F: Write,
+ I: Iterator<Item = (T, Option<C>, usize)>,
+ T: AsRef<str>,
+ C: Color,
+{
+ if let Some((c, color)) = borders.0 {
+ print_char(f, c, color)?;
+ }
+
+ if let Some((text, color, width)) = columns.next() {
+ let text = text.as_ref();
+ let text = text.lines().next().unwrap_or("");
+ print_cell(f, text, width, color, (pad.left, pad.right), align)?;
+ }
+
+ for (text, color, width) in columns {
+ if let Some((c, color)) = borders.1 {
+ print_char(f, c, color)?;
+ }
+
+ let text = text.as_ref();
+ let text = text.lines().next().unwrap_or("");
+ print_cell(f, text, width, color, (pad.left, pad.right), align)?;
+ }
+
+ if let Some((c, color)) = borders.2 {
+ print_char(f, c, color)?;
+ }
+
+ Ok(())
+}
+
+fn print_columns_empty_colored<F: Write, I: Iterator<Item = usize>>(
+ f: &mut F,
+ mut columns: I,
+ borders: (BorderChar, BorderChar, BorderChar),
+ color: StaticColor,
+) -> Result<(), fmt::Error> {
+ if let Some((c, color)) = borders.0 {
+ print_char(f, c, color)?;
+ }
+
+ if let Some(width) = columns.next() {
+ color.fmt_prefix(f)?;
+ repeat_char(f, ' ', width)?;
+ color.fmt_suffix(f)?;
+ }
+
+ for width in columns {
+ if let Some((c, color)) = borders.1 {
+ print_char(f, c, color)?;
+ }
+
+ color.fmt_prefix(f)?;
+ repeat_char(f, ' ', width)?;
+ color.fmt_suffix(f)?;
+ }
+
+ if let Some((c, color)) = borders.2 {
+ print_char(f, c, color)?;
+ }
+
+ Ok(())
+}
+
+fn print_cell<F: Write, C: Color>(
+ f: &mut F,
+ text: &str,
+ width: usize,
+ color: Option<C>,
+ (pad_l, pad_r): (ColoredIndent, ColoredIndent),
+ align: AlignmentHorizontal,
+) -> fmt::Result {
+ let available = width - pad_l.0.size - pad_r.0.size;
+
+ let text_width = string_width(text);
+ let (left, right) = if available < text_width {
+ (0, 0)
+ } else {
+ calculate_indent(align, text_width, available)
+ };
+
+ print_indent(f, pad_l.0.fill, pad_l.0.size, pad_l.1)?;
+
+ repeat_char(f, ' ', left)?;
+ print_text(f, text, color)?;
+ repeat_char(f, ' ', right)?;
+
+ print_indent(f, pad_r.0.fill, pad_r.0.size, pad_r.1)?;
+
+ Ok(())
+}
+
+fn print_split_line_colored<F: Write>(
+ f: &mut F,
+ chars: Line<char>,
+ colors: (StaticColor, StaticColor, StaticColor, StaticColor),
+ dimension: impl Dimension,
+ count_columns: usize,
+) -> fmt::Result {
+ let mut used_color = StaticColor::default();
+
+ if let Some(c) = chars.connect1 {
+ colors.1.fmt_prefix(f)?;
+ f.write_char(c)?;
+ used_color = colors.1;
+ }
+
+ let width = dimension.get_width(0);
+ if width > 0 {
+ prepare_coloring(f, &colors.0, &mut used_color)?;
+ repeat_char(f, chars.main, width)?;
+ }
+
+ for col in 1..count_columns {
+ if let Some(c) = &chars.intersection {
+ prepare_coloring(f, &colors.2, &mut used_color)?;
+ f.write_char(*c)?;
+ }
+
+ let width = dimension.get_width(col);
+ if width > 0 {
+ prepare_coloring(f, &colors.0, &mut used_color)?;
+ repeat_char(f, chars.main, width)?;
+ }
+ }
+
+ if let Some(c) = &chars.connect2 {
+ prepare_coloring(f, &colors.3, &mut used_color)?;
+ f.write_char(*c)?;
+ }
+
+ used_color.fmt_suffix(f)?;
+
+ Ok(())
+}
+
+fn print_split_line<F: Write>(
+ f: &mut F,
+ chars: Line<char>,
+ dimension: impl Dimension,
+ count_columns: usize,
+) -> fmt::Result {
+ if let Some(c) = chars.connect1 {
+ f.write_char(c)?;
+ }
+
+ let width = dimension.get_width(0);
+ if width > 0 {
+ repeat_char(f, chars.main, width)?;
+ }
+
+ for col in 1..count_columns {
+ if let Some(c) = chars.intersection {
+ f.write_char(c)?;
+ }
+
+ let width = dimension.get_width(col);
+ if width > 0 {
+ repeat_char(f, chars.main, width)?;
+ }
+ }
+
+ if let Some(c) = chars.connect2 {
+ f.write_char(c)?;
+ }
+
+ Ok(())
+}
+
+fn print_text<F: Write>(f: &mut F, text: &str, clr: Option<impl Color>) -> fmt::Result {
+ match clr {
+ Some(color) => {
+ color.fmt_prefix(f)?;
+ f.write_str(text)?;
+ color.fmt_suffix(f)
+ }
+ None => f.write_str(text),
+ }
+}
+
+fn prepare_coloring<F: Write>(f: &mut F, clr: &StaticColor, used: &mut StaticColor) -> fmt::Result {
+ if *used != *clr {
+ used.fmt_suffix(f)?;
+ clr.fmt_prefix(f)?;
+ *used = *clr;
+ }
+
+ Ok(())
+}
+
+fn calculate_indent(
+ alignment: AlignmentHorizontal,
+ text_width: usize,
+ available: usize,
+) -> (usize, usize) {
+ let diff = available - text_width;
+ match alignment {
+ AlignmentHorizontal::Left => (0, diff),
+ AlignmentHorizontal::Right => (diff, 0),
+ AlignmentHorizontal::Center => {
+ let left = diff / 2;
+ let rest = diff - left;
+ (left, rest)
+ }
+ }
+}
+
+fn repeat_char<F: Write>(f: &mut F, c: char, n: usize) -> fmt::Result {
+ for _ in 0..n {
+ f.write_char(c)?;
+ }
+
+ Ok(())
+}
+
+fn print_char<F: Write>(f: &mut F, c: char, color: Option<StaticColor>) -> fmt::Result {
+ match color {
+ Some(color) => {
+ color.fmt_prefix(f)?;
+ f.write_char(c)?;
+ color.fmt_suffix(f)
+ }
+ None => f.write_char(c),
+ }
+}
+
+fn print_indent_lines<F: Write>(
+ f: &mut F,
+ width: usize,
+ indent: Indent,
+ color: StaticColor,
+) -> fmt::Result {
+ print_indent(f, indent.fill, width, color)?;
+ f.write_char('\n')?;
+
+ for _ in 1..indent.size {
+ f.write_char('\n')?;
+ print_indent(f, indent.fill, width, color)?;
+ }
+
+ Ok(())
+}
+
+fn print_indent<F: Write>(f: &mut F, c: char, n: usize, color: StaticColor) -> fmt::Result {
+ color.fmt_prefix(f)?;
+ repeat_char(f, c, n)?;
+ color.fmt_suffix(f)?;
+
+ Ok(())
+}
+
+fn print_indent2<F: Write>(f: &mut F, indent: Indent, color: StaticColor) -> fmt::Result {
+ print_indent(f, indent.fill, indent.size, color)
+}
diff --git a/vendor/papergrid/src/grid/iterable.rs b/vendor/papergrid/src/grid/iterable.rs
new file mode 100644
index 000000000..c02998b0b
--- /dev/null
+++ b/vendor/papergrid/src/grid/iterable.rs
@@ -0,0 +1,1358 @@
+//! The module contains a [`Grid`] structure.
+
+use std::{
+ borrow::{Borrow, Cow},
+ cmp,
+ collections::BTreeMap,
+ fmt::{self, Write},
+};
+
+use crate::{
+ color::{AnsiColor, Color},
+ colors::Colors,
+ config::{AlignmentHorizontal, AlignmentVertical, Indent, Position, Sides},
+ dimension::Dimension,
+ records::Records,
+ util::string::{count_lines, get_lines, string_width, string_width_multiline, Lines},
+};
+
+use crate::config::spanned::{Formatting, Offset, SpannedConfig};
+
+/// Grid provides a set of methods for building a text-based table.
+#[derive(Debug, Clone)]
+pub struct Grid<R, D, G, C> {
+ records: R,
+ config: G,
+ dimension: D,
+ colors: C,
+}
+
+impl<R, D, G, C> Grid<R, D, G, C> {
+ /// The new method creates a grid instance with default styles.
+ pub fn new(records: R, dimension: D, config: G, colors: C) -> Self {
+ Grid {
+ records,
+ config,
+ dimension,
+ colors,
+ }
+ }
+}
+
+impl<R, D, G, C> Grid<R, D, G, C> {
+ /// Builds a table.
+ pub fn build<F>(self, mut f: F) -> fmt::Result
+ where
+ R: Records,
+ D: Dimension,
+ C: Colors,
+ G: Borrow<SpannedConfig>,
+ F: Write,
+ {
+ if self.records.count_columns() == 0 || self.records.hint_count_rows() == Some(0) {
+ return Ok(());
+ }
+
+ let config = self.config.borrow();
+ print_grid(&mut f, self.records, config, &self.dimension, &self.colors)
+ }
+
+ /// Builds a table into string.
+ ///
+ /// Notice that it consumes self.
+ #[allow(clippy::inherent_to_string)]
+ pub fn to_string(self) -> String
+ where
+ R: Records,
+ D: Dimension,
+ G: Borrow<SpannedConfig>,
+ C: Colors,
+ {
+ let mut buf = String::new();
+ self.build(&mut buf).expect("It's guaranteed to never happen otherwise it's considered an stdlib error or impl error");
+ buf
+ }
+}
+
+fn print_grid<F: Write, R: Records, D: Dimension, C: Colors>(
+ f: &mut F,
+ records: R,
+ cfg: &SpannedConfig,
+ dimension: &D,
+ colors: &C,
+) -> fmt::Result {
+ // spanned version is a bit more complex and 'supposedly' slower,
+ // because spans are considered to be not a general case we are having 2 versions
+ let grid_has_spans = cfg.has_column_spans() || cfg.has_row_spans();
+ if grid_has_spans {
+ print_grid_spanned(f, records, cfg, dimension, colors)
+ } else {
+ print_grid_general(f, records, cfg, dimension, colors)
+ }
+}
+
+fn print_grid_general<F: Write, R: Records, D: Dimension, C: Colors>(
+ f: &mut F,
+ records: R,
+ cfg: &SpannedConfig,
+ dims: &D,
+ colors: &C,
+) -> fmt::Result {
+ let count_columns = records.count_columns();
+
+ let mut totalw = None;
+ let totalh = records
+ .hint_count_rows()
+ .map(|count_rows| total_height(cfg, dims, count_rows));
+
+ let mut records_iter = records.iter_rows().into_iter();
+ let mut next_columns = records_iter.next();
+
+ if next_columns.is_none() {
+ return Ok(());
+ }
+
+ if cfg.get_margin().top.size > 0 {
+ totalw = Some(output_width(cfg, dims, count_columns));
+
+ print_margin_top(f, cfg, totalw.unwrap())?;
+ f.write_char('\n')?;
+ }
+
+ let mut row = 0;
+ let mut line = 0;
+ let mut is_prev_row_skipped = false;
+ let mut buf = None;
+ while let Some(columns) = next_columns {
+ let columns = columns.into_iter();
+ next_columns = records_iter.next();
+ let is_last_row = next_columns.is_none();
+
+ let height = dims.get_height(row);
+ let count_rows = convert_count_rows(row, is_last_row);
+ let has_horizontal = cfg.has_horizontal(row, count_rows);
+ let shape = (count_rows, count_columns);
+
+ if row > 0 && !is_prev_row_skipped && (has_horizontal || height > 0) {
+ f.write_char('\n')?;
+ }
+
+ if has_horizontal {
+ print_horizontal_line(f, cfg, line, totalh, dims, row, shape)?;
+
+ line += 1;
+
+ if height > 0 {
+ f.write_char('\n')?;
+ }
+ }
+
+ if height == 1 {
+ print_single_line_columns(f, columns, cfg, colors, dims, row, line, totalh, shape)?
+ } else if height > 0 {
+ if buf.is_none() {
+ buf = Some(Vec::with_capacity(count_columns));
+ }
+
+ let buf = buf.as_mut().unwrap();
+ print_multiline_columns(
+ f, columns, cfg, colors, dims, height, row, line, totalh, shape, buf,
+ )?;
+
+ buf.clear();
+ }
+
+ if height == 0 && !has_horizontal {
+ is_prev_row_skipped = true;
+ } else {
+ is_prev_row_skipped = false;
+ }
+
+ line += height;
+ row += 1;
+ }
+
+ if cfg.has_horizontal(row, row) {
+ f.write_char('\n')?;
+ let shape = (row, count_columns);
+ print_horizontal_line(f, cfg, line, totalh, dims, row, shape)?;
+ }
+
+ {
+ let margin = cfg.get_margin();
+ if margin.bottom.size > 0 {
+ let totalw = totalw.unwrap_or_else(|| output_width(cfg, dims, count_columns));
+
+ f.write_char('\n')?;
+ print_margin_bottom(f, cfg, totalw)?;
+ }
+ }
+
+ Ok(())
+}
+
+fn output_width<D: Dimension>(cfg: &SpannedConfig, d: D, count_columns: usize) -> usize {
+ let margin = cfg.get_margin();
+ total_width(cfg, &d, count_columns) + margin.left.size + margin.right.size
+}
+
+#[allow(clippy::too_many_arguments)]
+fn print_horizontal_line<F: Write, D: Dimension>(
+ f: &mut F,
+ cfg: &SpannedConfig,
+ line: usize,
+ totalh: Option<usize>,
+ dimension: &D,
+ row: usize,
+ shape: (usize, usize),
+) -> fmt::Result {
+ print_margin_left(f, cfg, line, totalh)?;
+ print_split_line(f, cfg, dimension, row, shape)?;
+ print_margin_right(f, cfg, line, totalh)?;
+ Ok(())
+}
+
+#[allow(clippy::too_many_arguments)]
+fn print_multiline_columns<'a, F, I, D, C>(
+ f: &mut F,
+ columns: I,
+ cfg: &'a SpannedConfig,
+ colors: &'a C,
+ dimension: &D,
+ height: usize,
+ row: usize,
+ line: usize,
+ totalh: Option<usize>,
+ shape: (usize, usize),
+ buf: &mut Vec<Cell<I::Item, &'a C::Color>>,
+) -> fmt::Result
+where
+ F: Write,
+ I: Iterator,
+ I::Item: AsRef<str>,
+ D: Dimension,
+ C: Colors,
+{
+ collect_columns(buf, columns, cfg, colors, dimension, height, row);
+ print_columns_lines(f, buf, height, cfg, line, row, totalh, shape)?;
+ Ok(())
+}
+
+#[allow(clippy::too_many_arguments)]
+fn print_single_line_columns<F, I, D, C>(
+ f: &mut F,
+ columns: I,
+ cfg: &SpannedConfig,
+ colors: &C,
+ dims: &D,
+ row: usize,
+ line: usize,
+ totalh: Option<usize>,
+ shape: (usize, usize),
+) -> fmt::Result
+where
+ F: Write,
+ I: Iterator,
+ I::Item: AsRef<str>,
+ D: Dimension,
+ C: Colors,
+{
+ print_margin_left(f, cfg, line, totalh)?;
+
+ for (col, cell) in columns.enumerate() {
+ let pos = (row, col);
+ let width = dims.get_width(col);
+ let color = colors.get_color(pos);
+ print_vertical_char(f, cfg, pos, 0, 1, shape.1)?;
+ print_single_line_column(f, cell.as_ref(), cfg, width, color, pos)?;
+ }
+
+ print_vertical_char(f, cfg, (row, shape.1), 0, 1, shape.1)?;
+
+ print_margin_right(f, cfg, line, totalh)?;
+
+ Ok(())
+}
+
+fn print_single_line_column<F: Write, C: Color>(
+ f: &mut F,
+ text: &str,
+ cfg: &SpannedConfig,
+ width: usize,
+ color: Option<&C>,
+ pos: Position,
+) -> fmt::Result {
+ let pos = pos.into();
+ let pad = cfg.get_padding(pos);
+ let pad_color = cfg.get_padding_color(pos);
+ let fmt = cfg.get_formatting(pos);
+ let space = cfg.get_justification(pos);
+ let space_color = cfg.get_justification_color(pos);
+
+ let (text, text_width) = if fmt.horizontal_trim && !text.is_empty() {
+ let text = string_trim(text);
+ let width = string_width(&text);
+
+ (text, width)
+ } else {
+ let text = Cow::Borrowed(text);
+ let width = string_width_multiline(&text);
+
+ (text, width)
+ };
+
+ let alignment = *cfg.get_alignment_horizontal(pos);
+ let available_width = width - pad.left.size - pad.right.size;
+ let (left, right) = calculate_indent(alignment, text_width, available_width);
+
+ print_padding(f, &pad.left, pad_color.left.as_ref())?;
+
+ print_indent(f, space, left, space_color)?;
+ print_text(f, &text, color)?;
+ print_indent(f, space, right, space_color)?;
+
+ print_padding(f, &pad.right, pad_color.right.as_ref())?;
+
+ Ok(())
+}
+
+#[allow(clippy::too_many_arguments)]
+fn print_columns_lines<T, F: Write, C: Color>(
+ f: &mut F,
+ buf: &mut [Cell<T, C>],
+ height: usize,
+ cfg: &SpannedConfig,
+ line: usize,
+ row: usize,
+ totalh: Option<usize>,
+ shape: (usize, usize),
+) -> fmt::Result {
+ for i in 0..height {
+ let exact_line = line + i;
+
+ print_margin_left(f, cfg, exact_line, totalh)?;
+
+ for (col, cell) in buf.iter_mut().enumerate() {
+ print_vertical_char(f, cfg, (row, col), i, height, shape.1)?;
+ cell.display(f)?;
+ }
+
+ print_vertical_char(f, cfg, (row, shape.1), i, height, shape.1)?;
+
+ print_margin_right(f, cfg, exact_line, totalh)?;
+
+ if i + 1 != height {
+ f.write_char('\n')?;
+ }
+ }
+
+ Ok(())
+}
+
+fn collect_columns<'a, I, D, C>(
+ buf: &mut Vec<Cell<I::Item, &'a C::Color>>,
+ iter: I,
+ cfg: &SpannedConfig,
+ colors: &'a C,
+ dimension: &D,
+ height: usize,
+ row: usize,
+) where
+ I: Iterator,
+ I::Item: AsRef<str>,
+ C: Colors,
+ D: Dimension,
+{
+ let iter = iter.enumerate().map(|(col, cell)| {
+ let pos = (row, col);
+ let width = dimension.get_width(col);
+ let color = colors.get_color(pos);
+ Cell::new(cell, width, height, cfg, color, pos)
+ });
+
+ buf.extend(iter);
+}
+
+fn print_split_line<F: Write, D: Dimension>(
+ f: &mut F,
+ cfg: &SpannedConfig,
+ dimension: &D,
+ row: usize,
+ shape: (usize, usize),
+) -> fmt::Result {
+ let mut used_color = None;
+ print_vertical_intersection(f, cfg, (row, 0), shape, &mut used_color)?;
+
+ for col in 0..shape.1 {
+ let width = dimension.get_width(col);
+
+ // general case
+ if width > 0 {
+ let pos = (row, col);
+ let main = cfg.get_horizontal(pos, shape.0);
+ match main {
+ Some(c) => {
+ let clr = cfg.get_horizontal_color(pos, shape.0);
+ prepare_coloring(f, clr, &mut used_color)?;
+ print_horizontal_border(f, cfg, pos, width, c, &used_color)?;
+ }
+ None => repeat_char(f, ' ', width)?,
+ }
+ }
+
+ print_vertical_intersection(f, cfg, (row, col + 1), shape, &mut used_color)?;
+ }
+
+ if let Some(clr) = used_color.take() {
+ clr.fmt_suffix(f)?;
+ }
+
+ Ok(())
+}
+
+fn print_grid_spanned<F: Write, R: Records, D: Dimension, C: Colors>(
+ f: &mut F,
+ records: R,
+ cfg: &SpannedConfig,
+ dims: &D,
+ colors: &C,
+) -> fmt::Result {
+ let count_columns = records.count_columns();
+
+ let total_width = total_width(cfg, dims, count_columns);
+ let margin = cfg.get_margin();
+ let total_width_with_margin = total_width + margin.left.size + margin.right.size;
+
+ let totalh = records
+ .hint_count_rows()
+ .map(|rows| total_height(cfg, dims, rows));
+
+ if margin.top.size > 0 {
+ print_margin_top(f, cfg, total_width_with_margin)?;
+ f.write_char('\n')?;
+ }
+
+ let mut buf = BTreeMap::new();
+
+ let mut records_iter = records.iter_rows().into_iter();
+ let mut next_columns = records_iter.next();
+
+ let mut need_new_line = false;
+ let mut line = 0;
+ let mut row = 0;
+ while let Some(columns) = next_columns {
+ let columns = columns.into_iter();
+ next_columns = records_iter.next();
+ let is_last_row = next_columns.is_none();
+
+ let height = dims.get_height(row);
+ let count_rows = convert_count_rows(row, is_last_row);
+ let shape = (count_rows, count_columns);
+
+ let has_horizontal = cfg.has_horizontal(row, count_rows);
+ if need_new_line && (has_horizontal || height > 0) {
+ f.write_char('\n')?;
+ need_new_line = false;
+ }
+
+ if has_horizontal {
+ print_margin_left(f, cfg, line, totalh)?;
+ print_split_line_spanned(f, &mut buf, cfg, dims, row, shape)?;
+ print_margin_right(f, cfg, line, totalh)?;
+
+ line += 1;
+
+ if height > 0 {
+ f.write_char('\n')?;
+ }
+ }
+
+ print_spanned_columns(
+ f, &mut buf, columns, cfg, colors, dims, height, row, line, totalh, shape,
+ )?;
+
+ if has_horizontal || height > 0 {
+ need_new_line = true;
+ }
+
+ line += height;
+ row += 1;
+ }
+
+ if row > 0 {
+ if cfg.has_horizontal(row, row) {
+ f.write_char('\n')?;
+ let shape = (row, count_columns);
+ print_horizontal_line(f, cfg, line, totalh, dims, row, shape)?;
+ }
+
+ if margin.bottom.size > 0 {
+ f.write_char('\n')?;
+ print_margin_bottom(f, cfg, total_width_with_margin)?;
+ }
+ }
+
+ Ok(())
+}
+
+fn print_split_line_spanned<S, F: Write, D: Dimension, C: Color>(
+ f: &mut F,
+ buf: &mut BTreeMap<usize, (Cell<S, C>, usize, usize)>,
+ cfg: &SpannedConfig,
+ dimension: &D,
+ row: usize,
+ shape: (usize, usize),
+) -> fmt::Result {
+ let mut used_color = None;
+ print_vertical_intersection(f, cfg, (row, 0), shape, &mut used_color)?;
+
+ for col in 0..shape.1 {
+ let pos = (row, col);
+ if cfg.is_cell_covered_by_both_spans(pos) {
+ continue;
+ }
+
+ let width = dimension.get_width(col);
+ let mut col = col;
+ if cfg.is_cell_covered_by_row_span(pos) {
+ // means it's part of other a spanned cell
+ // so. we just need to use line from other cell.
+
+ let (cell, _, _) = buf.get_mut(&col).unwrap();
+ cell.display(f)?;
+
+ // We need to use a correct right split char.
+ let original_row = closest_visible_row(cfg, pos).unwrap();
+ if let Some(span) = cfg.get_column_span((original_row, col)) {
+ col += span - 1;
+ }
+ } else if width > 0 {
+ // general case
+ let main = cfg.get_horizontal(pos, shape.0);
+ match main {
+ Some(c) => {
+ let clr = cfg.get_horizontal_color(pos, shape.0);
+ prepare_coloring(f, clr, &mut used_color)?;
+ print_horizontal_border(f, cfg, pos, width, c, &used_color)?;
+ }
+ None => repeat_char(f, ' ', width)?,
+ }
+ }
+
+ print_vertical_intersection(f, cfg, (row, col + 1), shape, &mut used_color)?;
+ }
+
+ if let Some(clr) = used_color.take() {
+ clr.fmt_suffix(f)?;
+ }
+
+ Ok(())
+}
+
+fn print_vertical_intersection<'a, F: fmt::Write>(
+ f: &mut F,
+ cfg: &'a SpannedConfig,
+ pos: Position,
+ shape: (usize, usize),
+ used_color: &mut Option<&'a AnsiColor<'static>>,
+) -> fmt::Result {
+ match cfg.get_intersection(pos, shape) {
+ Some(c) => {
+ let clr = cfg.get_intersection_color(pos, shape);
+ prepare_coloring(f, clr, used_color)?;
+ f.write_char(c)
+ }
+ None => Ok(()),
+ }
+}
+
+#[allow(clippy::too_many_arguments, clippy::type_complexity)]
+fn print_spanned_columns<'a, F, I, D, C>(
+ f: &mut F,
+ buf: &mut BTreeMap<usize, (Cell<I::Item, &'a C::Color>, usize, usize)>,
+ iter: I,
+ cfg: &SpannedConfig,
+ colors: &'a C,
+ dimension: &D,
+ this_height: usize,
+ row: usize,
+ line: usize,
+ totalh: Option<usize>,
+ shape: (usize, usize),
+) -> fmt::Result
+where
+ F: Write,
+ I: Iterator,
+ I::Item: AsRef<str>,
+ D: Dimension,
+ C: Colors,
+{
+ if this_height == 0 {
+ // it's possible that we dont show row but it contains an actual cell which will be
+ // rendered after all cause it's a rowspanned
+
+ let mut skip = 0;
+ for (col, cell) in iter.enumerate() {
+ if skip > 0 {
+ skip -= 1;
+ continue;
+ }
+
+ if let Some((_, _, colspan)) = buf.get(&col) {
+ skip = *colspan - 1;
+ continue;
+ }
+
+ let pos = (row, col);
+ let rowspan = cfg.get_row_span(pos).unwrap_or(1);
+ if rowspan < 2 {
+ continue;
+ }
+
+ let height = if rowspan > 1 {
+ range_height(cfg, dimension, row, row + rowspan, shape.0)
+ } else {
+ this_height
+ };
+
+ let colspan = cfg.get_column_span(pos).unwrap_or(1);
+ skip = colspan - 1;
+ let width = if colspan > 1 {
+ range_width(cfg, dimension, col, col + colspan, shape.1)
+ } else {
+ dimension.get_width(col)
+ };
+
+ let color = colors.get_color(pos);
+ let cell = Cell::new(cell, width, height, cfg, color, pos);
+
+ buf.insert(col, (cell, rowspan, colspan));
+ }
+
+ buf.retain(|_, (_, rowspan, _)| {
+ *rowspan -= 1;
+ *rowspan != 0
+ });
+
+ return Ok(());
+ }
+
+ let mut skip = 0;
+ for (col, cell) in iter.enumerate() {
+ if skip > 0 {
+ skip -= 1;
+ continue;
+ }
+
+ if let Some((_, _, colspan)) = buf.get(&col) {
+ skip = *colspan - 1;
+ continue;
+ }
+
+ let pos = (row, col);
+ let colspan = cfg.get_column_span(pos).unwrap_or(1);
+ skip = colspan - 1;
+
+ let width = if colspan > 1 {
+ range_width(cfg, dimension, col, col + colspan, shape.1)
+ } else {
+ dimension.get_width(col)
+ };
+
+ let rowspan = cfg.get_row_span(pos).unwrap_or(1);
+ let height = if rowspan > 1 {
+ range_height(cfg, dimension, row, row + rowspan, shape.0)
+ } else {
+ this_height
+ };
+
+ let color = colors.get_color(pos);
+ let cell = Cell::new(cell, width, height, cfg, color, pos);
+
+ buf.insert(col, (cell, rowspan, colspan));
+ }
+
+ for i in 0..this_height {
+ let exact_line = line + i;
+ let cell_line = i;
+
+ print_margin_left(f, cfg, exact_line, totalh)?;
+
+ for (&col, (cell, _, _)) in buf.iter_mut() {
+ print_vertical_char(f, cfg, (row, col), cell_line, this_height, shape.1)?;
+ cell.display(f)?;
+ }
+
+ print_vertical_char(f, cfg, (row, shape.1), cell_line, this_height, shape.1)?;
+
+ print_margin_right(f, cfg, exact_line, totalh)?;
+
+ if i + 1 != this_height {
+ f.write_char('\n')?;
+ }
+ }
+
+ buf.retain(|_, (_, rowspan, _)| {
+ *rowspan -= 1;
+ *rowspan != 0
+ });
+
+ Ok(())
+}
+
+fn print_horizontal_border<F: Write>(
+ f: &mut F,
+ cfg: &SpannedConfig,
+ pos: Position,
+ width: usize,
+ c: char,
+ used_color: &Option<&AnsiColor<'static>>,
+) -> fmt::Result {
+ if !cfg.is_overridden_horizontal(pos) {
+ return repeat_char(f, c, width);
+ }
+
+ for i in 0..width {
+ let c = cfg.lookup_horizontal_char(pos, i, width).unwrap_or(c);
+ match cfg.lookup_horizontal_color(pos, i, width) {
+ Some(color) => match used_color {
+ Some(clr) => {
+ clr.fmt_suffix(f)?;
+ color.fmt_prefix(f)?;
+ f.write_char(c)?;
+ color.fmt_suffix(f)?;
+ clr.fmt_prefix(f)?;
+ }
+ None => {
+ color.fmt_prefix(f)?;
+ f.write_char(c)?;
+ color.fmt_suffix(f)?;
+ }
+ },
+ _ => f.write_char(c)?,
+ }
+ }
+
+ Ok(())
+}
+
+struct Cell<T, C> {
+ lines: LinesIter<T>,
+ width: usize,
+ indent_top: usize,
+ indent_left: Option<usize>,
+ alignh: AlignmentHorizontal,
+ fmt: Formatting,
+ pad: Sides<Indent>,
+ pad_color: Sides<Option<AnsiColor<'static>>>,
+ color: Option<C>,
+ justification: (char, Option<AnsiColor<'static>>),
+}
+
+impl<T, C> Cell<T, C>
+where
+ T: AsRef<str>,
+{
+ fn new(
+ text: T,
+ width: usize,
+ height: usize,
+ cfg: &SpannedConfig,
+ color: Option<C>,
+ pos: Position,
+ ) -> Cell<T, C> {
+ let fmt = *cfg.get_formatting(pos.into());
+ let pad = cfg.get_padding(pos.into());
+ let pad_color = cfg.get_padding_color(pos.into()).clone();
+ let alignh = *cfg.get_alignment_horizontal(pos.into());
+ let alignv = *cfg.get_alignment_vertical(pos.into());
+ let justification = (
+ cfg.get_justification(pos.into()),
+ cfg.get_justification_color(pos.into()).cloned(),
+ );
+
+ let (count_lines, skip) = if fmt.vertical_trim {
+ let (len, top, _) = count_empty_lines(text.as_ref());
+ (len, top)
+ } else {
+ (count_lines(text.as_ref()), 0)
+ };
+
+ let indent_top = top_indent(&pad, alignv, count_lines, height);
+
+ let mut indent_left = None;
+ if !fmt.allow_lines_alignment {
+ let text_width = get_text_width(text.as_ref(), fmt.horizontal_trim);
+ let available = width - pad.left.size - pad.right.size;
+ indent_left = Some(calculate_indent(alignh, text_width, available).0);
+ }
+
+ let mut lines = LinesIter::new(text);
+ for _ in 0..skip {
+ let _ = lines.lines.next();
+ }
+
+ Self {
+ lines,
+ indent_left,
+ indent_top,
+ width,
+ alignh,
+ fmt,
+ pad,
+ pad_color,
+ color,
+ justification,
+ }
+ }
+}
+
+impl<T, C> Cell<T, C>
+where
+ C: Color,
+{
+ fn display<F: Write>(&mut self, f: &mut F) -> fmt::Result {
+ if self.indent_top > 0 {
+ self.indent_top -= 1;
+ print_padding_n(f, &self.pad.top, self.pad_color.top.as_ref(), self.width)?;
+ return Ok(());
+ }
+
+ let line = match self.lines.lines.next() {
+ Some(line) => line,
+ None => {
+ let color = self.pad_color.bottom.as_ref();
+ print_padding_n(f, &self.pad.bottom, color, self.width)?;
+ return Ok(());
+ }
+ };
+
+ let line = if self.fmt.horizontal_trim && !line.is_empty() {
+ string_trim(&line)
+ } else {
+ line
+ };
+
+ let line_width = string_width(&line);
+ let available_width = self.width - self.pad.left.size - self.pad.right.size;
+
+ let (left, right) = if self.fmt.allow_lines_alignment {
+ calculate_indent(self.alignh, line_width, available_width)
+ } else {
+ let left = self.indent_left.expect("must be here");
+ (left, available_width - line_width - left)
+ };
+
+ let (justification, justification_color) =
+ (self.justification.0, self.justification.1.as_ref());
+
+ print_padding(f, &self.pad.left, self.pad_color.left.as_ref())?;
+
+ print_indent(f, justification, left, justification_color)?;
+ print_text(f, &line, self.color.as_ref())?;
+ print_indent(f, justification, right, justification_color)?;
+
+ print_padding(f, &self.pad.right, self.pad_color.right.as_ref())?;
+
+ Ok(())
+ }
+}
+
+struct LinesIter<C> {
+ _cell: C,
+ /// SAFETY: IT'S NOT SAFE TO KEEP THE 'static REFERENCES AROUND AS THEY ARE NOT 'static in reality AND WILL BE DROPPED
+ _text: &'static str,
+ /// SAFETY: IT'S NOT SAFE TO KEEP THE 'static REFERENCES AROUND AS THEY ARE NOT 'static in reality AND WILL BE DROPPED
+ lines: Lines<'static>,
+}
+
+impl<C> LinesIter<C> {
+ fn new(cell: C) -> Self
+ where
+ C: AsRef<str>,
+ {
+ // We want to not allocate a String/Vec.
+ // It's currently not possible due to a lifetime issues. (It's known as self-referential struct)
+ //
+ // Here we change the lifetime of text.
+ //
+ // # Safety
+ //
+ // It must be safe because the referenced string and the references are dropped at the same time.
+ // And the referenced String is guaranteed to not be changed.
+ let text = cell.as_ref();
+ let text = unsafe {
+ std::str::from_utf8_unchecked(std::slice::from_raw_parts(text.as_ptr(), text.len()))
+ };
+
+ let lines = get_lines(text);
+
+ Self {
+ _cell: cell,
+ _text: text,
+ lines,
+ }
+ }
+}
+
+fn print_text<F: Write>(f: &mut F, text: &str, clr: Option<impl Color>) -> fmt::Result {
+ match clr {
+ Some(color) => {
+ color.fmt_prefix(f)?;
+ f.write_str(text)?;
+ color.fmt_suffix(f)
+ }
+ None => f.write_str(text),
+ }
+}
+
+fn prepare_coloring<'a, 'b, F: Write>(
+ f: &mut F,
+ clr: Option<&'a AnsiColor<'b>>,
+ used_color: &mut Option<&'a AnsiColor<'b>>,
+) -> fmt::Result {
+ match clr {
+ Some(clr) => match used_color.as_mut() {
+ Some(used_clr) => {
+ if **used_clr != *clr {
+ used_clr.fmt_suffix(f)?;
+ clr.fmt_prefix(f)?;
+ *used_clr = clr;
+ }
+ }
+ None => {
+ clr.fmt_prefix(f)?;
+ *used_color = Some(clr);
+ }
+ },
+ None => {
+ if let Some(clr) = used_color.take() {
+ clr.fmt_suffix(f)?
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn top_indent(
+ padding: &Sides<Indent>,
+ alignment: AlignmentVertical,
+ cell_height: usize,
+ available: usize,
+) -> usize {
+ let height = available - padding.top.size;
+ let indent = indent_from_top(alignment, height, cell_height);
+
+ indent + padding.top.size
+}
+
+fn indent_from_top(alignment: AlignmentVertical, available: usize, real: usize) -> usize {
+ match alignment {
+ AlignmentVertical::Top => 0,
+ AlignmentVertical::Bottom => available - real,
+ AlignmentVertical::Center => (available - real) / 2,
+ }
+}
+
+fn calculate_indent(
+ alignment: AlignmentHorizontal,
+ text_width: usize,
+ available: usize,
+) -> (usize, usize) {
+ let diff = available - text_width;
+ match alignment {
+ AlignmentHorizontal::Left => (0, diff),
+ AlignmentHorizontal::Right => (diff, 0),
+ AlignmentHorizontal::Center => {
+ let left = diff / 2;
+ let rest = diff - left;
+ (left, rest)
+ }
+ }
+}
+
+fn repeat_char<F: Write>(f: &mut F, c: char, n: usize) -> fmt::Result {
+ for _ in 0..n {
+ f.write_char(c)?;
+ }
+
+ Ok(())
+}
+
+fn print_vertical_char<F: Write>(
+ f: &mut F,
+ cfg: &SpannedConfig,
+ pos: Position,
+ line: usize,
+ count_lines: usize,
+ count_columns: usize,
+) -> fmt::Result {
+ let symbol = match cfg.get_vertical(pos, count_columns) {
+ Some(c) => c,
+ None => return Ok(()),
+ };
+
+ let symbol = cfg
+ .is_overridden_vertical(pos)
+ .then(|| cfg.lookup_vertical_char(pos, line, count_lines))
+ .flatten()
+ .unwrap_or(symbol);
+
+ match cfg.get_vertical_color(pos, count_columns) {
+ Some(clr) => {
+ clr.fmt_prefix(f)?;
+ f.write_char(symbol)?;
+ clr.fmt_suffix(f)?;
+ }
+ None => f.write_char(symbol)?,
+ }
+
+ Ok(())
+}
+
+fn print_margin_top<F: Write>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result {
+ let indent = cfg.get_margin().top;
+ let offset = cfg.get_margin_offset().top;
+ let color = cfg.get_margin_color();
+ let color = color.top.as_ref();
+ print_indent_lines(f, &indent, &offset, color, width)
+}
+
+fn print_margin_bottom<F: Write>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result {
+ let indent = cfg.get_margin().bottom;
+ let offset = cfg.get_margin_offset().bottom;
+ let color = cfg.get_margin_color();
+ let color = color.bottom.as_ref();
+ print_indent_lines(f, &indent, &offset, color, width)
+}
+
+fn print_margin_left<F: Write>(
+ f: &mut F,
+ cfg: &SpannedConfig,
+ line: usize,
+ height: Option<usize>,
+) -> fmt::Result {
+ let indent = cfg.get_margin().left;
+ let offset = cfg.get_margin_offset().left;
+ let color = cfg.get_margin_color();
+ let color = color.left.as_ref();
+ print_margin_vertical(f, indent, offset, color, line, height)
+}
+
+fn print_margin_right<F: Write>(
+ f: &mut F,
+ cfg: &SpannedConfig,
+ line: usize,
+ height: Option<usize>,
+) -> fmt::Result {
+ let indent = cfg.get_margin().right;
+ let offset = cfg.get_margin_offset().right;
+ let color = cfg.get_margin_color();
+ let color = color.right.as_ref();
+ print_margin_vertical(f, indent, offset, color, line, height)
+}
+
+fn print_margin_vertical<F: Write>(
+ f: &mut F,
+ indent: Indent,
+ offset: Offset,
+ color: Option<&AnsiColor<'_>>,
+ line: usize,
+ height: Option<usize>,
+) -> fmt::Result {
+ if indent.size == 0 {
+ return Ok(());
+ }
+
+ match offset {
+ Offset::Begin(mut offset) => {
+ if let Some(max) = height {
+ offset = cmp::min(offset, max);
+ }
+
+ if line >= offset {
+ print_indent(f, indent.fill, indent.size, color)?;
+ } else {
+ repeat_char(f, ' ', indent.size)?;
+ }
+ }
+ Offset::End(mut offset) => {
+ if let Some(max) = height {
+ offset = cmp::min(offset, max);
+ let pos = max - offset;
+
+ if line >= pos {
+ repeat_char(f, ' ', indent.size)?;
+ } else {
+ print_indent(f, indent.fill, indent.size, color)?;
+ }
+ } else {
+ print_indent(f, indent.fill, indent.size, color)?;
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn print_indent_lines<F: Write>(
+ f: &mut F,
+ indent: &Indent,
+ offset: &Offset,
+ color: Option<&AnsiColor<'_>>,
+ width: usize,
+) -> fmt::Result {
+ if indent.size == 0 {
+ return Ok(());
+ }
+
+ let (start_offset, end_offset) = match offset {
+ Offset::Begin(start) => (*start, 0),
+ Offset::End(end) => (0, *end),
+ };
+
+ let start_offset = std::cmp::min(start_offset, width);
+ let end_offset = std::cmp::min(end_offset, width);
+ let indent_size = width - start_offset - end_offset;
+
+ for i in 0..indent.size {
+ if start_offset > 0 {
+ repeat_char(f, ' ', start_offset)?;
+ }
+
+ if indent_size > 0 {
+ print_indent(f, indent.fill, indent_size, color)?;
+ }
+
+ if end_offset > 0 {
+ repeat_char(f, ' ', end_offset)?;
+ }
+
+ if i + 1 != indent.size {
+ f.write_char('\n')?;
+ }
+ }
+
+ Ok(())
+}
+
+fn print_padding<F: Write>(f: &mut F, pad: &Indent, color: Option<&AnsiColor<'_>>) -> fmt::Result {
+ print_indent(f, pad.fill, pad.size, color)
+}
+
+fn print_padding_n<F: Write>(
+ f: &mut F,
+ pad: &Indent,
+ color: Option<&AnsiColor<'_>>,
+ n: usize,
+) -> fmt::Result {
+ print_indent(f, pad.fill, n, color)
+}
+
+fn print_indent<F: Write>(
+ f: &mut F,
+ c: char,
+ n: usize,
+ color: Option<&AnsiColor<'_>>,
+) -> fmt::Result {
+ if n == 0 {
+ return Ok(());
+ }
+
+ match color {
+ Some(color) => {
+ color.fmt_prefix(f)?;
+ repeat_char(f, c, n)?;
+ color.fmt_suffix(f)
+ }
+ None => repeat_char(f, c, n),
+ }
+}
+
+fn range_width(
+ cfg: &SpannedConfig,
+ d: impl Dimension,
+ start: usize,
+ end: usize,
+ max: usize,
+) -> usize {
+ let count_borders = count_verticals_in_range(cfg, start, end, max);
+ let range_width = (start..end).map(|col| d.get_width(col)).sum::<usize>();
+
+ count_borders + range_width
+}
+
+fn range_height(
+ cfg: &SpannedConfig,
+ d: impl Dimension,
+ from: usize,
+ end: usize,
+ max: usize,
+) -> usize {
+ let count_borders = count_horizontals_in_range(cfg, from, end, max);
+ let range_width = (from..end).map(|col| d.get_height(col)).sum::<usize>();
+
+ count_borders + range_width
+}
+
+fn count_horizontals_in_range(cfg: &SpannedConfig, from: usize, end: usize, max: usize) -> usize {
+ (from + 1..end)
+ .map(|i| cfg.has_horizontal(i, max) as usize)
+ .sum()
+}
+
+fn count_verticals_in_range(cfg: &SpannedConfig, start: usize, end: usize, max: usize) -> usize {
+ (start..end)
+ .skip(1)
+ .map(|i| cfg.has_vertical(i, max) as usize)
+ .sum()
+}
+
+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 {
+ return None;
+ }
+
+ pos.0 -= 1;
+ }
+}
+
+fn convert_count_rows(row: usize, is_last: bool) -> usize {
+ if is_last {
+ row + 1
+ } else {
+ row + 2
+ }
+}
+
+/// Trims a string.
+fn string_trim(text: &str) -> Cow<'_, str> {
+ #[cfg(feature = "color")]
+ {
+ ansi_str::AnsiStr::ansi_trim(text)
+ }
+
+ #[cfg(not(feature = "color"))]
+ {
+ text.trim().into()
+ }
+}
+
+fn total_width<D: Dimension>(cfg: &SpannedConfig, dimension: &D, count_columns: usize) -> usize {
+ (0..count_columns)
+ .map(|i| dimension.get_width(i))
+ .sum::<usize>()
+ + cfg.count_vertical(count_columns)
+}
+
+fn total_height<D: Dimension>(cfg: &SpannedConfig, dimension: &D, count_rows: usize) -> usize {
+ (0..count_rows)
+ .map(|i| dimension.get_height(i))
+ .sum::<usize>()
+ + cfg.count_horizontal(count_rows)
+}
+
+fn count_empty_lines(cell: &str) -> (usize, usize, usize) {
+ let mut len = 0;
+ let mut top = 0;
+ let mut bottom = 0;
+ let mut top_check = true;
+
+ for line in get_lines(cell) {
+ let is_empty = line.trim().is_empty();
+ if top_check {
+ if is_empty {
+ top += 1;
+ } else {
+ len = 1;
+ top_check = false;
+ }
+
+ continue;
+ }
+
+ if is_empty {
+ bottom += 1;
+ } else {
+ len += bottom + 1;
+ bottom = 0;
+ }
+ }
+
+ (len, top, bottom)
+}
+
+fn get_text_width(text: &str, trim: bool) -> usize {
+ if trim {
+ get_lines(text)
+ .map(|line| string_width(line.trim()))
+ .max()
+ .unwrap_or(0)
+ } else {
+ string_width_multiline(text)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ // use crate::util::string_width;
+
+ use super::*;
+
+ // #[test]
+ // fn horizontal_alignment_test() {
+ // use std::fmt;
+
+ // struct F<'a>(&'a str, AlignmentHorizontal, usize);
+
+ // impl fmt::Display for F<'_> {
+ // fn fmt(&self, f: &mut impl fmt::Write) -> fmt::Result {
+ // let (left, right) = calculate_indent(self.1, string_width(self.0), self.2);
+ // print_text_formatted(f, &self.0, 4, Option::<&AnsiColor<'_>>::None)
+ // }
+ // }
+
+ // assert_eq!(F("AAA", AlignmentHorizontal::Right, 4).to_string(), " AAA");
+ // assert_eq!(F("AAA", AlignmentHorizontal::Left, 4).to_string(), "AAA ");
+ // assert_eq!(F("AAA", AlignmentHorizontal::Center, 4).to_string(), "AAA ");
+ // assert_eq!(F("🎩", AlignmentHorizontal::Center, 4).to_string(), " 🎩 ");
+ // assert_eq!(F("🎩", AlignmentHorizontal::Center, 3).to_string(), "🎩 ");
+
+ // #[cfg(feature = "color")]
+ // {
+ // use owo_colors::OwoColorize;
+ // let text = "Colored Text".red().to_string();
+ // assert_eq!(
+ // F(&text, AlignmentHorizontal::Center, 15).to_string(),
+ // format!(" {} ", text)
+ // );
+ // }
+ // }
+
+ #[test]
+ fn vertical_alignment_test() {
+ use AlignmentVertical::*;
+
+ assert_eq!(indent_from_top(Bottom, 1, 1), 0);
+ assert_eq!(indent_from_top(Top, 1, 1), 0);
+ assert_eq!(indent_from_top(Center, 1, 1), 0);
+ assert_eq!(indent_from_top(Bottom, 3, 1), 2);
+ assert_eq!(indent_from_top(Top, 3, 1), 0);
+ assert_eq!(indent_from_top(Center, 3, 1), 1);
+ assert_eq!(indent_from_top(Center, 4, 1), 1);
+ }
+
+ #[test]
+ fn count_empty_lines_test() {
+ assert_eq!(count_empty_lines("\n\nsome text\n\n\n"), (1, 2, 3));
+ assert_eq!(count_empty_lines("\n\nsome\ntext\n\n\n"), (2, 2, 3));
+ assert_eq!(count_empty_lines("\n\nsome\nsome\ntext\n\n\n"), (3, 2, 3));
+ assert_eq!(count_empty_lines("\n\n\n\n"), (0, 5, 0));
+ }
+}
diff --git a/vendor/papergrid/src/grid/mod.rs b/vendor/papergrid/src/grid/mod.rs
new file mode 100644
index 000000000..ba3db33bd
--- /dev/null
+++ b/vendor/papergrid/src/grid/mod.rs
@@ -0,0 +1,9 @@
+//! Module contains a list of backends for pretty print tables.
+
+pub mod compact;
+
+#[cfg(feature = "std")]
+pub mod iterable;
+
+#[cfg(feature = "std")]
+pub mod peekable;
diff --git a/vendor/papergrid/src/grid/peekable.rs b/vendor/papergrid/src/grid/peekable.rs
new file mode 100644
index 000000000..ee7877293
--- /dev/null
+++ b/vendor/papergrid/src/grid/peekable.rs
@@ -0,0 +1,976 @@
+//! The module contains a [`PeekableGrid`] structure.
+
+use core::borrow::Borrow;
+use std::{
+ borrow::Cow,
+ cmp,
+ fmt::{self, Write},
+};
+
+use crate::{
+ color::{AnsiColor, Color},
+ colors::Colors,
+ config::spanned::{Formatting, Offset, SpannedConfig},
+ config::{AlignmentHorizontal, AlignmentVertical, Indent, Position, Sides},
+ dimension::Dimension,
+ records::{ExactRecords, PeekableRecords, Records},
+ util::string::string_width,
+};
+
+/// Grid provides a set of methods for building a text-based table.
+#[derive(Debug, Clone)]
+pub struct PeekableGrid<R, G, D, C> {
+ records: R,
+ config: G,
+ dimension: D,
+ colors: C,
+}
+
+impl<R, G, D, C> PeekableGrid<R, G, D, C> {
+ /// The new method creates a grid instance with default styles.
+ pub fn new(records: R, config: G, dimension: D, colors: C) -> Self {
+ PeekableGrid {
+ records,
+ config,
+ dimension,
+ colors,
+ }
+ }
+}
+
+impl<R, G, D, C> PeekableGrid<R, G, D, C> {
+ /// Builds a table.
+ pub fn build<F>(self, mut f: F) -> fmt::Result
+ where
+ R: Records + PeekableRecords + ExactRecords,
+ D: Dimension,
+ C: Colors,
+ G: Borrow<SpannedConfig>,
+ F: Write,
+ {
+ if self.records.count_columns() == 0 || self.records.hint_count_rows() == Some(0) {
+ return Ok(());
+ }
+
+ let config = self.config.borrow();
+ print_grid(&mut f, self.records, config, &self.dimension, &self.colors)
+ }
+
+ /// Builds a table into string.
+ ///
+ /// Notice that it consumes self.
+ #[allow(clippy::inherent_to_string)]
+ pub fn to_string(self) -> String
+ where
+ R: Records + PeekableRecords + ExactRecords,
+ D: Dimension,
+ G: Borrow<SpannedConfig>,
+ C: Colors,
+ {
+ let mut buf = String::new();
+ self.build(&mut buf).expect("It's guaranteed to never happen otherwise it's considered an stdlib error or impl error");
+ buf
+ }
+}
+
+fn print_grid<F: Write, R: Records + PeekableRecords + ExactRecords, D: Dimension, C: Colors>(
+ f: &mut F,
+ records: R,
+ cfg: &SpannedConfig,
+ dimension: &D,
+ colors: &C,
+) -> fmt::Result {
+ if cfg.has_column_spans() || cfg.has_row_spans() {
+ build_grid_spanned(f, &records, cfg, dimension, colors)
+ } else {
+ build_grid(f, &records, cfg, dimension, colors)
+ }
+}
+
+fn build_grid<F: Write, R: Records + PeekableRecords + ExactRecords, D: Dimension, C: Colors>(
+ f: &mut F,
+ records: &R,
+ cfg: &SpannedConfig,
+ dimension: &D,
+ colors: &C,
+) -> fmt::Result {
+ let shape = (records.count_rows(), records.count_columns());
+
+ let total_width = total_width(cfg, dimension, shape.1);
+ let total_width_with_margin =
+ total_width + cfg.get_margin().left.size + cfg.get_margin().right.size;
+
+ let total_height = total_height(cfg, dimension, shape.0);
+
+ if cfg.get_margin().top.size > 0 {
+ print_margin_top(f, cfg, total_width_with_margin)?;
+ f.write_char('\n')?;
+ }
+
+ let mut table_line = 0;
+ let mut prev_empty_horizontal = false;
+ for row in 0..shape.0 {
+ let height = dimension.get_height(row);
+
+ if cfg.has_horizontal(row, shape.0) {
+ if prev_empty_horizontal {
+ f.write_char('\n')?;
+ }
+
+ print_margin_left(f, cfg, table_line, total_height)?;
+ print_split_line(f, cfg, dimension, row, shape)?;
+ print_margin_right(f, cfg, table_line, total_height)?;
+
+ if height > 0 {
+ f.write_char('\n')?;
+ prev_empty_horizontal = false;
+ } else {
+ prev_empty_horizontal = true;
+ }
+
+ table_line += 1;
+ } else if height > 0 && prev_empty_horizontal {
+ f.write_char('\n')?;
+ prev_empty_horizontal = false;
+ }
+
+ for i in 0..height {
+ print_margin_left(f, cfg, table_line, total_height)?;
+
+ for col in 0..records.count_columns() {
+ print_vertical_char(f, cfg, (row, col), i, height, shape.1)?;
+
+ let width = dimension.get_width(col);
+ print_cell_line(f, records, cfg, colors, width, height, (row, col), i)?;
+
+ let is_last_column = col + 1 == records.count_columns();
+ if is_last_column {
+ print_vertical_char(f, cfg, (row, col + 1), i, height, shape.1)?;
+ }
+ }
+
+ print_margin_right(f, cfg, table_line, total_height)?;
+
+ let is_last_line = i + 1 == height;
+ let is_last_row = row + 1 == records.count_rows();
+ if !(is_last_line && is_last_row) {
+ f.write_char('\n')?;
+ }
+
+ table_line += 1;
+ }
+ }
+
+ if cfg.has_horizontal(shape.0, shape.0) {
+ f.write_char('\n')?;
+ print_margin_left(f, cfg, table_line, total_height)?;
+ print_split_line(f, cfg, dimension, records.count_rows(), shape)?;
+ print_margin_right(f, cfg, table_line, total_height)?;
+ }
+
+ if cfg.get_margin().bottom.size > 0 {
+ f.write_char('\n')?;
+ print_margin_bottom(f, cfg, total_width_with_margin)?;
+ }
+
+ Ok(())
+}
+
+fn print_split_line<F: Write, D: Dimension>(
+ f: &mut F,
+ cfg: &SpannedConfig,
+ dimension: &D,
+ row: usize,
+ shape: (usize, usize),
+) -> fmt::Result {
+ let mut used_color = None;
+ print_vertical_intersection(f, cfg, (row, 0), shape, &mut used_color)?;
+
+ for col in 0..shape.1 {
+ let width = dimension.get_width(col);
+
+ // general case
+ if width > 0 {
+ let pos = (row, col);
+ let main = cfg.get_horizontal(pos, shape.0);
+ match main {
+ Some(c) => {
+ let clr = cfg.get_horizontal_color(pos, shape.0);
+ prepare_coloring(f, clr, &mut used_color)?;
+ print_horizontal_border(f, cfg, pos, width, c, &used_color)?;
+ }
+ None => repeat_char(f, ' ', width)?,
+ }
+ }
+
+ print_vertical_intersection(f, cfg, (row, col + 1), shape, &mut used_color)?;
+ }
+
+ if let Some(clr) = used_color.take() {
+ clr.fmt_suffix(f)?;
+ }
+
+ Ok(())
+}
+
+fn print_vertical_intersection<'a, F: fmt::Write>(
+ f: &mut F,
+ cfg: &'a SpannedConfig,
+ pos: Position,
+ shape: (usize, usize),
+ used_color: &mut Option<&'a AnsiColor<'static>>,
+) -> fmt::Result {
+ match cfg.get_intersection(pos, shape) {
+ Some(c) => {
+ let clr = cfg.get_intersection_color(pos, shape);
+ prepare_coloring(f, clr, used_color)?;
+ f.write_char(c)
+ }
+ None => Ok(()),
+ }
+}
+
+fn prepare_coloring<'a, 'b, F: Write>(
+ f: &mut F,
+ clr: Option<&'a AnsiColor<'b>>,
+ used_color: &mut Option<&'a AnsiColor<'b>>,
+) -> fmt::Result {
+ match clr {
+ Some(clr) => match used_color.as_mut() {
+ Some(used_clr) => {
+ if **used_clr != *clr {
+ used_clr.fmt_suffix(f)?;
+ clr.fmt_prefix(f)?;
+ *used_clr = clr;
+ }
+ }
+ None => {
+ clr.fmt_prefix(f)?;
+ *used_color = Some(clr);
+ }
+ },
+ None => {
+ if let Some(clr) = used_color.take() {
+ clr.fmt_suffix(f)?
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn print_vertical_char<F: Write>(
+ f: &mut F,
+ cfg: &SpannedConfig,
+ pos: Position,
+ line: usize,
+ count_lines: usize,
+ count_columns: usize,
+) -> fmt::Result {
+ let symbol = match cfg.get_vertical(pos, count_columns) {
+ Some(c) => c,
+ None => return Ok(()),
+ };
+
+ let symbol = cfg
+ .is_overridden_vertical(pos)
+ .then(|| cfg.lookup_vertical_char(pos, line, count_lines))
+ .flatten()
+ .unwrap_or(symbol);
+
+ match cfg.get_vertical_color(pos, count_columns) {
+ Some(clr) => {
+ clr.fmt_prefix(f)?;
+ f.write_char(symbol)?;
+ clr.fmt_suffix(f)?;
+ }
+ None => f.write_char(symbol)?,
+ }
+
+ Ok(())
+}
+
+fn build_grid_spanned<
+ F: Write,
+ R: Records + PeekableRecords + ExactRecords,
+ D: Dimension,
+ C: Colors,
+>(
+ f: &mut F,
+ records: &R,
+ cfg: &SpannedConfig,
+ dims: &D,
+ colors: &C,
+) -> fmt::Result {
+ let shape = (records.count_rows(), records.count_columns());
+
+ let total_width = total_width(cfg, dims, shape.1);
+ let total_width_with_margin =
+ total_width + cfg.get_margin().left.size + cfg.get_margin().right.size;
+
+ let total_height = total_height(cfg, dims, shape.0);
+
+ if cfg.get_margin().top.size > 0 {
+ print_margin_top(f, cfg, total_width_with_margin)?;
+ f.write_char('\n')?;
+ }
+
+ let mut table_line = 0;
+ let mut prev_empty_horizontal = false;
+ for row in 0..records.count_rows() {
+ let count_lines = dims.get_height(row);
+
+ if cfg.has_horizontal(row, shape.0) {
+ if prev_empty_horizontal {
+ f.write_char('\n')?;
+ }
+
+ print_margin_left(f, cfg, table_line, total_height)?;
+ print_split_line_spanned(f, records, cfg, dims, colors, row, shape)?;
+ print_margin_right(f, cfg, table_line, total_height)?;
+
+ if count_lines > 0 {
+ f.write_char('\n')?;
+ prev_empty_horizontal = false;
+ } else {
+ prev_empty_horizontal = true;
+ }
+
+ table_line += 1;
+ } else if count_lines > 0 && prev_empty_horizontal {
+ f.write_char('\n')?;
+ prev_empty_horizontal = false;
+ }
+
+ for i in 0..count_lines {
+ print_margin_left(f, cfg, table_line, total_height)?;
+
+ for col in 0..records.count_columns() {
+ if cfg.is_cell_covered_by_both_spans((row, col)) {
+ continue;
+ }
+
+ if cfg.is_cell_covered_by_column_span((row, col)) {
+ let is_last_column = col + 1 == records.count_columns();
+ if is_last_column {
+ print_vertical_char(f, cfg, (row, col + 1), i, count_lines, shape.1)?;
+ }
+
+ continue;
+ }
+
+ print_vertical_char(f, cfg, (row, col), i, count_lines, shape.1)?;
+
+ if cfg.is_cell_covered_by_row_span((row, col)) {
+ // means it's part of other a spanned cell
+ // so. we just need to use line from other cell.
+ let original_row = closest_visible_row(cfg, (row, col)).unwrap();
+
+ // considering that the content will be printed instead horizontal lines so we can skip some lines.
+ let mut skip_lines = (original_row..row)
+ .map(|i| dims.get_height(i))
+ .sum::<usize>();
+
+ skip_lines += (original_row + 1..=row)
+ .map(|row| cfg.has_horizontal(row, shape.0) as usize)
+ .sum::<usize>();
+
+ let line = i + skip_lines;
+ let pos = (original_row, col);
+
+ let width = get_cell_width(cfg, dims, pos, shape.1);
+ let height = get_cell_height(cfg, dims, pos, shape.0);
+
+ print_cell_line(f, records, cfg, colors, width, height, pos, line)?;
+ } else {
+ let width = get_cell_width(cfg, dims, (row, col), shape.1);
+ let height = get_cell_height(cfg, dims, (row, col), shape.0);
+ print_cell_line(f, records, cfg, colors, width, height, (row, col), i)?;
+ }
+
+ let is_last_column = col + 1 == records.count_columns();
+ if is_last_column {
+ print_vertical_char(f, cfg, (row, col + 1), i, count_lines, shape.1)?;
+ }
+ }
+
+ print_margin_right(f, cfg, table_line, total_height)?;
+
+ let is_last_line = i + 1 == count_lines;
+ let is_last_row = row + 1 == records.count_rows();
+ if !(is_last_line && is_last_row) {
+ f.write_char('\n')?;
+ }
+
+ table_line += 1;
+ }
+ }
+
+ if cfg.has_horizontal(shape.0, shape.0) {
+ f.write_char('\n')?;
+ print_margin_left(f, cfg, table_line, total_height)?;
+ print_split_line(f, cfg, dims, records.count_rows(), shape)?;
+ print_margin_right(f, cfg, table_line, total_height)?;
+ }
+
+ if cfg.get_margin().bottom.size > 0 {
+ f.write_char('\n')?;
+ print_margin_bottom(f, cfg, total_width_with_margin)?;
+ }
+
+ Ok(())
+}
+
+fn print_split_line_spanned<
+ F: Write,
+ R: Records + ExactRecords + PeekableRecords,
+ D: Dimension,
+ C: Colors,
+>(
+ f: &mut F,
+ records: &R,
+ cfg: &SpannedConfig,
+ dims: &D,
+ colors: &C,
+ row: usize,
+ shape: (usize, usize),
+) -> fmt::Result {
+ let mut used_color = None;
+ print_vertical_intersection(f, cfg, (row, 0), shape, &mut used_color)?;
+
+ for col in 0..shape.1 {
+ let pos = (row, col);
+ if cfg.is_cell_covered_by_both_spans(pos) {
+ continue;
+ }
+
+ if cfg.is_cell_covered_by_row_span(pos) {
+ // means it's part of other a spanned cell
+ // so. we just need to use line from other cell.
+
+ let original_row = closest_visible_row(cfg, (row, col)).unwrap();
+
+ // considering that the content will be printed instead horizontal lines so we can skip some lines.
+ let mut skip_lines = (original_row..row)
+ .map(|i| dims.get_height(i))
+ .sum::<usize>();
+
+ // skip horizontal lines
+ if row > 0 {
+ skip_lines += (original_row..row - 1)
+ .map(|row| cfg.has_horizontal(row + 1, shape.0) as usize)
+ .sum::<usize>();
+ }
+
+ let pos = (original_row, col);
+ let height = get_cell_height(cfg, dims, pos, shape.0);
+ let width = get_cell_width(cfg, dims, pos, shape.1);
+ let line = skip_lines;
+
+ print_cell_line(f, records, cfg, colors, width, height, pos, line)?;
+
+ // We need to use a correct right split char.
+ let mut col = col;
+ if let Some(span) = cfg.get_column_span(pos) {
+ col += span - 1;
+ }
+
+ print_vertical_intersection(f, cfg, (row, col + 1), shape, &mut used_color)?;
+
+ continue;
+ }
+
+ let width = dims.get_width(col);
+ if width > 0 {
+ // general case
+ let main = cfg.get_horizontal(pos, shape.0);
+ match main {
+ Some(c) => {
+ let clr = cfg.get_horizontal_color(pos, shape.0);
+ prepare_coloring(f, clr, &mut used_color)?;
+ print_horizontal_border(f, cfg, pos, width, c, &used_color)?;
+ }
+ None => repeat_char(f, ' ', width)?,
+ }
+ }
+
+ print_vertical_intersection(f, cfg, (row, col + 1), shape, &mut used_color)?;
+ }
+
+ if let Some(clr) = used_color {
+ clr.fmt_suffix(f)?;
+ }
+
+ Ok(())
+}
+
+fn print_horizontal_border<F: Write>(
+ f: &mut F,
+ cfg: &SpannedConfig,
+ pos: Position,
+ width: usize,
+ c: char,
+ used_color: &Option<&AnsiColor<'static>>,
+) -> fmt::Result {
+ if !cfg.is_overridden_horizontal(pos) {
+ return repeat_char(f, c, width);
+ }
+
+ for i in 0..width {
+ let c = cfg.lookup_horizontal_char(pos, i, width).unwrap_or(c);
+ match cfg.lookup_horizontal_color(pos, i, width) {
+ Some(color) => match used_color {
+ Some(clr) => {
+ clr.fmt_suffix(f)?;
+ color.fmt_prefix(f)?;
+ f.write_char(c)?;
+ color.fmt_suffix(f)?;
+ clr.fmt_prefix(f)?;
+ }
+ None => {
+ color.fmt_prefix(f)?;
+ f.write_char(c)?;
+ color.fmt_suffix(f)?;
+ }
+ },
+ _ => f.write_char(c)?,
+ }
+ }
+
+ Ok(())
+}
+
+#[allow(clippy::too_many_arguments)]
+fn print_cell_line<F: Write, R: Records + PeekableRecords + ExactRecords, C: Colors>(
+ f: &mut F,
+ records: &R,
+ cfg: &SpannedConfig,
+ colors: &C,
+ width: usize,
+ height: usize,
+ pos: Position,
+ line: usize,
+) -> fmt::Result {
+ let entity = pos.into();
+
+ let mut cell_height = records.count_lines(pos);
+ let formatting = *cfg.get_formatting(entity);
+ if formatting.vertical_trim {
+ cell_height -=
+ count_empty_lines_at_start(records, pos) + count_empty_lines_at_end(records, pos);
+ }
+
+ if cell_height > height {
+ // it may happen if the height estimation decide so
+ cell_height = height;
+ }
+
+ let pad = cfg.get_padding(entity);
+ let pad_color = cfg.get_padding_color(entity);
+ let alignment = cfg.get_alignment_vertical(entity);
+ let indent = top_indent(&pad, *alignment, cell_height, height);
+ if indent > line {
+ return print_indent(f, pad.top.fill, width, pad_color.top.as_ref());
+ }
+
+ let mut index = line - indent;
+ let cell_has_this_line = cell_height > index;
+ if !cell_has_this_line {
+ // happens when other cells have bigger height
+ return print_indent(f, pad.bottom.fill, width, pad_color.bottom.as_ref());
+ }
+
+ if formatting.vertical_trim {
+ let empty_lines = count_empty_lines_at_start(records, pos);
+ index += empty_lines;
+
+ if index > records.count_lines(pos) {
+ return print_indent(f, pad.top.fill, width, pad_color.top.as_ref());
+ }
+ }
+
+ print_indent(f, pad.left.fill, pad.left.size, pad_color.left.as_ref())?;
+
+ let width = width - pad.left.size - pad.right.size;
+ let alignment = *cfg.get_alignment_horizontal(entity);
+ let justification = (
+ cfg.get_justification(entity),
+ cfg.get_justification_color(entity),
+ );
+ let color = colors.get_color(pos);
+ print_line(
+ f,
+ records,
+ pos,
+ index,
+ alignment,
+ formatting,
+ color,
+ justification,
+ width,
+ )?;
+
+ print_indent(f, pad.right.fill, pad.right.size, pad_color.right.as_ref())?;
+
+ Ok(())
+}
+
+#[allow(clippy::too_many_arguments)]
+fn print_line<F: Write, R: Records + PeekableRecords, C: Color>(
+ f: &mut F,
+ records: &R,
+ pos: Position,
+ index: usize,
+ alignment: AlignmentHorizontal,
+ formatting: Formatting,
+ color: Option<C>,
+ justification: (char, Option<&AnsiColor<'_>>),
+ available: usize,
+) -> fmt::Result {
+ let line = records.get_line(pos, index);
+ let (line, line_width) = if formatting.horizontal_trim {
+ let line = string_trim(line);
+ let width = string_width(&line);
+ (line, width)
+ } else {
+ let width = records.get_line_width(pos, index);
+ (Cow::Borrowed(line), width)
+ };
+
+ if formatting.allow_lines_alignment {
+ let (left, right) = calculate_indent(alignment, line_width, available);
+ return print_text_with_pad(f, &line, color, justification, left, right);
+ }
+
+ let cell_width = if formatting.horizontal_trim {
+ (0..records.count_lines(pos))
+ .map(|i| records.get_line(pos, i))
+ .map(|line| string_width(line.trim()))
+ .max()
+ .unwrap_or_default()
+ } else {
+ records.get_width(pos)
+ };
+
+ let (left, right) = calculate_indent(alignment, cell_width, available);
+ print_text_with_pad(f, &line, color, justification, left, right)?;
+
+ // todo: remove me
+ let rest_width = cell_width - line_width;
+ repeat_char(f, ' ', rest_width)?;
+
+ Ok(())
+}
+
+fn print_text_with_pad<F: Write, C: Color>(
+ f: &mut F,
+ text: &str,
+ color: Option<C>,
+ justification: (char, Option<&AnsiColor<'_>>),
+ left: usize,
+ right: usize,
+) -> fmt::Result {
+ print_indent(f, justification.0, left, justification.1)?;
+ print_text(f, text, color)?;
+ print_indent(f, justification.0, right, justification.1)?;
+ Ok(())
+}
+
+fn print_text<F: Write, C: Color>(f: &mut F, text: &str, clr: Option<C>) -> fmt::Result {
+ match clr {
+ Some(color) => {
+ color.fmt_prefix(f)?;
+ f.write_str(text)?;
+ color.fmt_suffix(f)
+ }
+ None => f.write_str(text),
+ }
+}
+
+fn top_indent(
+ pad: &Sides<Indent>,
+ alignment: AlignmentVertical,
+ cell_height: usize,
+ available: usize,
+) -> usize {
+ let height = available - pad.top.size;
+ let indent = indent_from_top(alignment, height, cell_height);
+
+ indent + pad.top.size
+}
+
+fn indent_from_top(alignment: AlignmentVertical, available: usize, real: usize) -> usize {
+ match alignment {
+ AlignmentVertical::Top => 0,
+ AlignmentVertical::Bottom => available - real,
+ AlignmentVertical::Center => (available - real) / 2,
+ }
+}
+
+fn calculate_indent(
+ alignment: AlignmentHorizontal,
+ text_width: usize,
+ available: usize,
+) -> (usize, usize) {
+ let diff = available - text_width;
+ match alignment {
+ AlignmentHorizontal::Left => (0, diff),
+ AlignmentHorizontal::Right => (diff, 0),
+ AlignmentHorizontal::Center => {
+ let left = diff / 2;
+ let rest = diff - left;
+ (left, rest)
+ }
+ }
+}
+
+fn repeat_char<F: Write>(f: &mut F, c: char, n: usize) -> fmt::Result {
+ for _ in 0..n {
+ f.write_char(c)?;
+ }
+
+ Ok(())
+}
+
+fn count_empty_lines_at_end<R>(records: &R, pos: Position) -> usize
+where
+ R: Records + PeekableRecords,
+{
+ (0..records.count_lines(pos))
+ .map(|i| records.get_line(pos, i))
+ .rev()
+ .take_while(|l| l.trim().is_empty())
+ .count()
+}
+
+fn count_empty_lines_at_start<R>(records: &R, pos: Position) -> usize
+where
+ R: Records + PeekableRecords,
+{
+ (0..records.count_lines(pos))
+ .map(|i| records.get_line(pos, i))
+ .take_while(|s| s.trim().is_empty())
+ .count()
+}
+
+fn total_width<D: Dimension>(cfg: &SpannedConfig, dimension: &D, count_columns: usize) -> usize {
+ (0..count_columns)
+ .map(|i| dimension.get_width(i))
+ .sum::<usize>()
+ + cfg.count_vertical(count_columns)
+}
+
+fn total_height<D: Dimension>(cfg: &SpannedConfig, dimension: &D, count_rows: usize) -> usize {
+ (0..count_rows)
+ .map(|i| dimension.get_height(i))
+ .sum::<usize>()
+ + cfg.count_horizontal(count_rows)
+}
+
+fn print_margin_top<F: Write>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result {
+ let indent = cfg.get_margin().top;
+ let offset = cfg.get_margin_offset().top;
+ let color = cfg.get_margin_color();
+ let color = color.top.as_ref();
+ print_indent_lines(f, &indent, &offset, color, width)
+}
+
+fn print_margin_bottom<F: Write>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result {
+ let indent = cfg.get_margin().bottom;
+ let offset = cfg.get_margin_offset().bottom;
+ let color = cfg.get_margin_color();
+ let color = color.bottom.as_ref();
+ print_indent_lines(f, &indent, &offset, color, width)
+}
+
+fn print_margin_left<F: Write>(
+ f: &mut F,
+ cfg: &SpannedConfig,
+ line: usize,
+ height: usize,
+) -> fmt::Result {
+ let indent = cfg.get_margin().left;
+ let offset = cfg.get_margin_offset().left;
+ let color = cfg.get_margin_color();
+ let color = color.left.as_ref();
+ print_margin_vertical(f, indent, offset, color, line, height)
+}
+
+fn print_margin_right<F: Write>(
+ f: &mut F,
+ cfg: &SpannedConfig,
+ line: usize,
+ height: usize,
+) -> fmt::Result {
+ let indent = cfg.get_margin().right;
+ let offset = cfg.get_margin_offset().right;
+ let color = cfg.get_margin_color();
+ let color = color.right.as_ref();
+ print_margin_vertical(f, indent, offset, color, line, height)
+}
+
+fn print_margin_vertical<F: Write>(
+ f: &mut F,
+ indent: Indent,
+ offset: Offset,
+ color: Option<&AnsiColor<'_>>,
+ line: usize,
+ height: usize,
+) -> fmt::Result {
+ if indent.size == 0 {
+ return Ok(());
+ }
+
+ match offset {
+ Offset::Begin(offset) => {
+ let offset = cmp::min(offset, height);
+ if line >= offset {
+ print_indent(f, indent.fill, indent.size, color)?;
+ } else {
+ repeat_char(f, ' ', indent.size)?;
+ }
+ }
+ Offset::End(offset) => {
+ let offset = cmp::min(offset, height);
+ let pos = height - offset;
+
+ if line >= pos {
+ repeat_char(f, ' ', indent.size)?;
+ } else {
+ print_indent(f, indent.fill, indent.size, color)?;
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn print_indent_lines<F: Write>(
+ f: &mut F,
+ indent: &Indent,
+ offset: &Offset,
+ color: Option<&AnsiColor<'_>>,
+ width: usize,
+) -> fmt::Result {
+ if indent.size == 0 {
+ return Ok(());
+ }
+
+ let (start_offset, end_offset) = match offset {
+ Offset::Begin(start) => (*start, 0),
+ Offset::End(end) => (0, *end),
+ };
+
+ let start_offset = std::cmp::min(start_offset, width);
+ let end_offset = std::cmp::min(end_offset, width);
+ let indent_size = width - start_offset - end_offset;
+
+ for i in 0..indent.size {
+ if start_offset > 0 {
+ repeat_char(f, ' ', start_offset)?;
+ }
+
+ if indent_size > 0 {
+ print_indent(f, indent.fill, indent_size, color)?;
+ }
+
+ if end_offset > 0 {
+ repeat_char(f, ' ', end_offset)?;
+ }
+
+ if i + 1 != indent.size {
+ f.write_char('\n')?;
+ }
+ }
+
+ Ok(())
+}
+
+fn print_indent<F: Write, C: Color>(f: &mut F, c: char, n: usize, color: Option<C>) -> fmt::Result {
+ if n == 0 {
+ return Ok(());
+ }
+
+ match color {
+ Some(color) => {
+ color.fmt_prefix(f)?;
+ repeat_char(f, c, n)?;
+ color.fmt_suffix(f)
+ }
+ None => repeat_char(f, c, n),
+ }
+}
+
+fn get_cell_width<D: Dimension>(cfg: &SpannedConfig, dims: &D, pos: Position, max: usize) -> usize {
+ match cfg.get_column_span(pos) {
+ Some(span) => {
+ let start = pos.1;
+ let end = pos.1 + span;
+ range_width(dims, start, end) + count_verticals_range(cfg, start, end, max)
+ }
+ None => dims.get_width(pos.1),
+ }
+}
+
+fn range_width<D: Dimension>(dims: &D, start: usize, end: usize) -> usize {
+ (start..end).map(|col| dims.get_width(col)).sum::<usize>()
+}
+
+fn count_verticals_range(cfg: &SpannedConfig, start: usize, end: usize, max: usize) -> usize {
+ (start + 1..end)
+ .map(|i| cfg.has_vertical(i, max) as usize)
+ .sum()
+}
+
+fn get_cell_height<D: Dimension>(
+ cfg: &SpannedConfig,
+ dims: &D,
+ pos: Position,
+ max: usize,
+) -> usize {
+ match cfg.get_row_span(pos) {
+ Some(span) => {
+ let start = pos.0;
+ let end = pos.0 + span;
+ range_height(dims, start, end) + count_horizontals_range(cfg, start, end, max)
+ }
+ None => dims.get_height(pos.0),
+ }
+}
+
+fn range_height<D: Dimension>(dims: &D, start: usize, end: usize) -> usize {
+ (start..end).map(|col| dims.get_height(col)).sum::<usize>()
+}
+
+fn count_horizontals_range(cfg: &SpannedConfig, start: usize, end: usize, max: usize) -> usize {
+ (start + 1..end)
+ .map(|i| cfg.has_horizontal(i, max) as usize)
+ .sum()
+}
+
+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 {
+ return None;
+ }
+
+ pos.0 -= 1;
+ }
+}
+
+/// Trims a string.
+fn string_trim(text: &str) -> Cow<'_, str> {
+ #[cfg(feature = "color")]
+ {
+ ansi_str::AnsiStr::ansi_trim(text)
+ }
+
+ #[cfg(not(feature = "color"))]
+ {
+ text.trim().into()
+ }
+}
diff --git a/vendor/papergrid/src/lib.rs b/vendor/papergrid/src/lib.rs
new file mode 100644
index 000000000..8da4f14ba
--- /dev/null
+++ b/vendor/papergrid/src/lib.rs
@@ -0,0 +1,87 @@
+#![cfg_attr(not(any(feature = "std", test)), no_std)]
+#![warn(
+ rust_2018_idioms,
+ rust_2018_compatibility,
+ rust_2021_compatibility,
+ missing_debug_implementations,
+ unreachable_pub,
+ missing_docs
+)]
+#![allow(clippy::uninlined_format_args)]
+#![deny(unused_must_use)]
+#![doc(
+ html_logo_url = "https://raw.githubusercontent.com/zhiburt/tabled/86ac146e532ce9f7626608d7fd05072123603a2e/assets/tabled-gear.svg"
+)]
+
+//! Papergrid is a library for generating text-based tables.
+//!
+//! It has relatively low level API.
+//! If you're interested in a more friendly one take a look at [`tabled`](https://github.com/zhiburt/tabled).
+//!
+//! # Example
+//!
+#![cfg_attr(feature = "std", doc = "```")]
+#![cfg_attr(not(feature = "std"), doc = "```ignore")]
+//! use papergrid::{
+//! records::IterRecords,
+//! dimension::{Estimate},
+//! config::Borders,
+//! colors::NoColors,
+//! grid::iterable::Grid,
+//! config::spanned::SpannedConfig,
+//! dimension::spanned::SpannedGridDimension,
+//! };
+//!
+//! // Creating a borders structure of a grid.
+//! let borders = Borders {
+//! top: Some('-'),
+//! top_left: Some('+'),
+//! top_right: Some('+'),
+//! top_intersection: Some('+'),
+//! bottom: Some('-'),
+//! bottom_left: Some('+'),
+//! bottom_right: Some('+'),
+//! bottom_intersection: Some('+'),
+//! horizontal: Some('-'),
+//! vertical: Some('|'),
+//! left: Some('|'),
+//! right: Some('|'),
+//! intersection: Some('+'),
+//! left_intersection: Some('+'),
+//! right_intersection: Some('+'),
+//! };
+//!
+//! // Creating a grid config.
+//! let mut cfg = SpannedConfig::default();
+//! cfg.set_borders(borders);
+//!
+//! // Creating an actual data for grid.
+//! let records = vec![vec!["Hello", "World"], vec!["Hi", "World"]];
+//! let records = IterRecords::new(records, 2, None);
+//!
+//! // Estimate grid dimension.
+//! let mut dimension = SpannedGridDimension::default();
+//! dimension.estimate(&records, &cfg);
+//!
+//! // Creating a grid.
+//! let grid = Grid::new(&records, &dimension, &cfg, NoColors).to_string();
+//!
+//! assert_eq!(
+//! grid,
+//! concat!(
+//! "+-----+-----+\n",
+//! "|Hello|World|\n",
+//! "+-----+-----+\n",
+//! "|Hi |World|\n",
+//! "+-----+-----+",
+//! ),
+//! );
+//! ```
+
+pub mod color;
+pub mod colors;
+pub mod config;
+pub mod dimension;
+pub mod grid;
+pub mod records;
+pub mod util;
diff --git a/vendor/papergrid/src/records/exact_records.rs b/vendor/papergrid/src/records/exact_records.rs
new file mode 100644
index 000000000..9a564bc82
--- /dev/null
+++ b/vendor/papergrid/src/records/exact_records.rs
@@ -0,0 +1,31 @@
+/// [`Records`] extension which guarantees the amount of rows.
+///
+/// [`Records`]: crate::records::Records
+pub trait ExactRecords {
+ /// Returns an exact amount of rows in records.
+ ///
+ /// It must be guaranteed that an iterator will yield this amount.
+ fn count_rows(&self) -> usize;
+}
+
+impl<T> ExactRecords for &T
+where
+ T: ExactRecords,
+{
+ fn count_rows(&self) -> usize {
+ T::count_rows(self)
+ }
+}
+
+#[cfg(feature = "std")]
+impl<T> ExactRecords for Vec<T> {
+ fn count_rows(&self) -> usize {
+ self.len()
+ }
+}
+
+impl<T> ExactRecords for [T] {
+ fn count_rows(&self) -> usize {
+ self.len()
+ }
+}
diff --git a/vendor/papergrid/src/records/into_records.rs b/vendor/papergrid/src/records/into_records.rs
new file mode 100644
index 000000000..c1de73d9c
--- /dev/null
+++ b/vendor/papergrid/src/records/into_records.rs
@@ -0,0 +1,33 @@
+/// The representation of data, rows and columns of a [`Grid`].
+///
+/// [`Grid`]: crate::grid::iterable::Grid
+pub trait IntoRecords {
+ /// A string representation of a [`Grid`] cell.
+ ///
+ /// [`Grid`]: crate::grid::iterable::Grid
+ type Cell: AsRef<str>;
+
+ /// Cell iterator inside a row.
+ type IterColumns: IntoIterator<Item = Self::Cell>;
+
+ /// Rows iterator.
+ type IterRows: IntoIterator<Item = Self::IterColumns>;
+
+ /// Returns an iterator over rows.
+ fn iter_rows(self) -> Self::IterRows;
+}
+
+impl<T> IntoRecords for T
+where
+ T: IntoIterator,
+ <T as IntoIterator>::Item: IntoIterator,
+ <<T as IntoIterator>::Item as IntoIterator>::Item: AsRef<str>,
+{
+ type Cell = <<T as IntoIterator>::Item as IntoIterator>::Item;
+ type IterColumns = <T as IntoIterator>::Item;
+ type IterRows = <T as IntoIterator>::IntoIter;
+
+ fn iter_rows(self) -> Self::IterRows {
+ self.into_iter()
+ }
+}
diff --git a/vendor/papergrid/src/records/iter_records.rs b/vendor/papergrid/src/records/iter_records.rs
new file mode 100644
index 000000000..3d4f50754
--- /dev/null
+++ b/vendor/papergrid/src/records/iter_records.rs
@@ -0,0 +1,87 @@
+use super::{IntoRecords, Records};
+
+/// A [Records] implementation for any [IntoIterator].
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct IterRecords<I> {
+ iter: I,
+ count_columns: usize,
+ count_rows: Option<usize>,
+}
+
+impl<I> IterRecords<I> {
+ /// Returns a new [IterRecords] object.
+ pub const fn new(iter: I, count_columns: usize, count_rows: Option<usize>) -> Self {
+ Self {
+ iter,
+ count_columns,
+ count_rows,
+ }
+ }
+}
+
+impl<I> IntoRecords for IterRecords<I>
+where
+ I: IntoRecords,
+{
+ type Cell = I::Cell;
+ type IterColumns = I::IterColumns;
+ type IterRows = I::IterRows;
+
+ fn iter_rows(self) -> Self::IterRows {
+ self.iter.iter_rows()
+ }
+}
+
+// why this does not work?
+
+// impl<'a, I> IntoRecords for &'a IterRecords<I>
+// where
+// &'a I: IntoRecords,
+// {
+// type Cell = <&'a I as IntoRecords>::Cell;
+// type IterColumns = <&'a I as IntoRecords>::IterColumns;
+// type IterRows = <&'a I as IntoRecords>::IterRows;
+
+// fn iter_rows(self) -> Self::IterRows {
+// // (&self.iter).iter_rows()
+// todo!()
+// }
+// }
+
+impl<I> Records for IterRecords<I>
+where
+ I: IntoRecords,
+{
+ type Iter = I;
+
+ fn iter_rows(self) -> <Self::Iter as IntoRecords>::IterRows {
+ self.iter.iter_rows()
+ }
+
+ fn count_columns(&self) -> usize {
+ self.count_columns
+ }
+
+ fn hint_count_rows(&self) -> Option<usize> {
+ self.count_rows
+ }
+}
+
+impl<'a, I> Records for &'a IterRecords<I>
+where
+ &'a I: IntoRecords,
+{
+ type Iter = &'a I;
+
+ fn iter_rows(self) -> <Self::Iter as IntoRecords>::IterRows {
+ (&self.iter).iter_rows()
+ }
+
+ fn count_columns(&self) -> usize {
+ self.count_columns
+ }
+
+ fn hint_count_rows(&self) -> Option<usize> {
+ self.count_rows
+ }
+}
diff --git a/vendor/papergrid/src/records/mod.rs b/vendor/papergrid/src/records/mod.rs
new file mode 100644
index 000000000..149b6f55f
--- /dev/null
+++ b/vendor/papergrid/src/records/mod.rs
@@ -0,0 +1,31 @@
+//! The module contains a [Records] abstraction of a [`Grid`] trait and its implementers.
+//!
+//! [`Grid`]: crate::grid::iterable::Grid
+
+mod exact_records;
+mod into_records;
+mod iter_records;
+mod peekable_records;
+
+pub use exact_records::ExactRecords;
+pub use into_records::IntoRecords;
+pub use iter_records::IterRecords;
+pub use peekable_records::PeekableRecords;
+
+#[cfg(feature = "std")]
+pub mod vec_records;
+
+/// Records represents table data.
+pub trait Records {
+ /// Iterator which goes over rows.
+ type Iter: IntoRecords;
+
+ /// Returns a iterator over rows.
+ fn iter_rows(self) -> <Self::Iter as IntoRecords>::IterRows;
+
+ /// Returns count of columns in the records.
+ fn count_columns(&self) -> usize;
+
+ /// Hint amount of rows in the records.
+ fn hint_count_rows(&self) -> Option<usize>;
+}
diff --git a/vendor/papergrid/src/records/peekable_records.rs b/vendor/papergrid/src/records/peekable_records.rs
new file mode 100644
index 000000000..528ba5f0c
--- /dev/null
+++ b/vendor/papergrid/src/records/peekable_records.rs
@@ -0,0 +1,52 @@
+use crate::config::Position;
+
+/// The representation of data, rows and columns of a grid.
+pub trait PeekableRecords {
+ /// Returns a text of a cell by an index.
+ fn get_text(&self, pos: Position) -> &str;
+
+ /// Returns a line of a text of a cell by an index.
+ fn get_line(&self, pos: Position, line: usize) -> &str {
+ self.get_text(pos).lines().nth(line).unwrap()
+ }
+
+ /// Returns an amount of lines of a text of a cell by an index.
+ fn count_lines(&self, pos: Position) -> usize {
+ self.get_text(pos).lines().count()
+ }
+
+ /// Returns a width of a text of a cell by an index.
+ fn get_width(&self, pos: Position) -> usize {
+ crate::util::string::string_width_multiline(self.get_text(pos))
+ }
+
+ /// Returns a width of line of a text of a cell by an index.
+ fn get_line_width(&self, pos: Position, line: usize) -> usize {
+ crate::util::string::string_width(self.get_line(pos, line))
+ }
+}
+
+impl<R> PeekableRecords for &R
+where
+ R: PeekableRecords,
+{
+ fn get_text(&self, pos: Position) -> &str {
+ R::get_text(self, pos)
+ }
+
+ fn get_line(&self, pos: Position, line: usize) -> &str {
+ R::get_line(self, pos, line)
+ }
+
+ fn count_lines(&self, pos: Position) -> usize {
+ R::count_lines(self, pos)
+ }
+
+ fn get_width(&self, pos: Position) -> usize {
+ R::get_width(self, pos)
+ }
+
+ fn get_line_width(&self, pos: Position, line: usize) -> usize {
+ R::get_line_width(self, pos, line)
+ }
+}
diff --git a/vendor/papergrid/src/records/vec_records/cell.rs b/vendor/papergrid/src/records/vec_records/cell.rs
new file mode 100644
index 000000000..38a36b6e3
--- /dev/null
+++ b/vendor/papergrid/src/records/vec_records/cell.rs
@@ -0,0 +1,19 @@
+/// Cell implementation which can be used with [`VecRecords`].
+///
+/// [`VecRecords`]: crate::records::vec_records::VecRecords
+pub trait Cell {
+ /// Gets a text.
+ fn text(&self) -> &str;
+
+ /// Gets a line by index.
+ fn line(&self, line: usize) -> &str;
+
+ /// Returns a number of lines cell has.
+ fn count_lines(&self) -> usize;
+
+ /// Returns a width of cell.
+ fn width(&self) -> usize;
+
+ /// Returns a width of cell line.
+ fn line_width(&self, line: usize) -> usize;
+}
diff --git a/vendor/papergrid/src/records/vec_records/cell_info.rs b/vendor/papergrid/src/records/vec_records/cell_info.rs
new file mode 100644
index 000000000..d9b0ed71e
--- /dev/null
+++ b/vendor/papergrid/src/records/vec_records/cell_info.rs
@@ -0,0 +1,170 @@
+use std::{borrow::Cow, cmp::max};
+
+use crate::{
+ records::vec_records::Cell,
+ util::string::{self, count_lines, get_lines, string_width},
+};
+
+/// The struct is a [Cell] implementation which keeps width information pre allocated.
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CellInfo<S> {
+ text: S,
+ width: usize,
+ lines: Vec<StrWithWidth<'static>>,
+}
+
+impl<S> CellInfo<S> {
+ /// Creates a new instance of the structure.
+ pub fn new(text: S) -> Self
+ where
+ S: AsRef<str>,
+ {
+ create_cell_info(text)
+ }
+
+ /// Creates a new instance of the structure with a single line.
+ pub fn exact(text: S, width: usize, lines: Vec<StrWithWidth<'static>>) -> Self {
+ Self { text, width, lines }
+ }
+
+ /// Return a original text value.
+ pub fn into_inner(self) -> S {
+ self.text
+ }
+}
+
+impl<S> AsRef<str> for CellInfo<S>
+where
+ S: AsRef<str>,
+{
+ fn as_ref(&self) -> &str {
+ self.text()
+ }
+}
+
+impl<S> Cell for CellInfo<S>
+where
+ S: AsRef<str>,
+{
+ fn text(&self) -> &str {
+ self.text.as_ref()
+ }
+
+ fn line(&self, i: usize) -> &str {
+ if i == 0 && self.lines.is_empty() {
+ return self.text.as_ref();
+ }
+
+ &self.lines[i].text
+ }
+
+ fn count_lines(&self) -> usize {
+ std::cmp::max(1, self.lines.len())
+ }
+
+ fn width(&self) -> usize {
+ self.width
+ }
+
+ fn line_width(&self, i: usize) -> usize {
+ if i == 0 && self.lines.is_empty() {
+ return self.width;
+ }
+
+ self.lines[i].width
+ }
+}
+
+impl<S> Clone for CellInfo<S>
+where
+ S: Clone + AsRef<str>,
+{
+ fn clone(&self) -> Self {
+ let mut cell = Self {
+ text: self.text.clone(),
+ width: self.width,
+ lines: vec![StrWithWidth::default(); self.lines.len()],
+ };
+
+ for (i, line) in self.lines.iter().enumerate() {
+ // We need to redirect pointers to the original string.
+ //
+ // # Safety
+ //
+ // It must be safe because the referenced string and the references are dropped at the same time.
+ // And the referenced String is guaranteed to not be changed.
+ let text = unsafe {
+ let text_ptr = self.text.as_ref().as_ptr();
+ let line_ptr = line.text.as_ptr();
+ let text_shift = line_ptr as isize - text_ptr as isize;
+
+ let new_text_shifted_ptr = cell.text.as_ref().as_ptr().offset(text_shift);
+
+ std::str::from_utf8_unchecked(std::slice::from_raw_parts(
+ new_text_shifted_ptr,
+ line.text.len(),
+ ))
+ };
+
+ cell.lines[i].width = line.width;
+ cell.lines[i].text = Cow::Borrowed(text);
+ }
+
+ cell
+ }
+}
+
+/// StrWithWidth is a structure is responsible for a string and it's width.
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)]
+pub struct StrWithWidth<'a> {
+ text: Cow<'a, str>,
+ width: usize,
+}
+
+impl<'a> StrWithWidth<'a> {
+ /// Creates a new object.
+ pub fn new(text: Cow<'a, str>, width: usize) -> Self {
+ Self { text, width }
+ }
+}
+
+fn create_cell_info<S: AsRef<str>>(text: S) -> CellInfo<S> {
+ let mut info = CellInfo {
+ text,
+ lines: vec![],
+ width: 0,
+ };
+
+ // Here we do a small optimization.
+ // We check if there's only 1 line in which case we don't allocate lines Vec
+ let count_lines = count_lines(info.text.as_ref());
+ if count_lines < 2 {
+ info.width = string::string_width_multiline(info.text.as_ref());
+ return info;
+ }
+
+ // In case `Cow::Borrowed` we want to not allocate a String.
+ // It's currerently not possible due to a lifetime issues. (It's known as self-referential struct)
+ //
+ // Here we change the lifetime of text.
+ //
+ // # Safety
+ //
+ // It must be safe because the referenced string and the references are dropped at the same time.
+ // And the referenced String is guaranteed to not be changed.
+ let text = unsafe {
+ std::str::from_utf8_unchecked(std::slice::from_raw_parts(
+ info.text.as_ref().as_ptr(),
+ info.text.as_ref().len(),
+ ))
+ };
+
+ info.lines = vec![StrWithWidth::new(Cow::Borrowed(""), 0); count_lines];
+ for (line, i) in get_lines(text).zip(info.lines.iter_mut()) {
+ i.width = string_width(&line);
+ i.text = line;
+ info.width = max(info.width, i.width);
+ }
+
+ info
+}
diff --git a/vendor/papergrid/src/records/vec_records/mod.rs b/vendor/papergrid/src/records/vec_records/mod.rs
new file mode 100644
index 000000000..279eb3a7b
--- /dev/null
+++ b/vendor/papergrid/src/records/vec_records/mod.rs
@@ -0,0 +1,124 @@
+//! Module contains [`VecRecords`].
+
+mod cell;
+mod cell_info;
+
+use crate::{
+ config::Position,
+ records::{ExactRecords, IntoRecords, Records},
+};
+use std::ops::{Deref, DerefMut};
+
+use super::PeekableRecords;
+
+pub use cell::Cell;
+pub use cell_info::{CellInfo, StrWithWidth};
+
+/// A [Records] implementation based on allocated buffers.
+#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub struct VecRecords<T> {
+ data: Vec<Vec<T>>,
+ shape: (usize, usize),
+}
+
+impl<T> VecRecords<T> {
+ /// Creates new [`VecRecords`] structure.
+ ///
+ /// It assumes that data vector has all rows has the same length().
+ pub fn new(data: Vec<Vec<T>>) -> Self {
+ let count_columns = data.get(0).map_or(0, |row| row.len());
+ let count_rows = data.len();
+ let shape = (count_rows, count_columns);
+
+ Self { data, shape }
+ }
+}
+
+impl<T> Records for VecRecords<T>
+where
+ T: AsRef<str>,
+{
+ type Iter = Vec<Vec<T>>;
+
+ fn iter_rows(self) -> <Self::Iter as IntoRecords>::IterRows {
+ self.data.iter_rows()
+ }
+
+ fn count_columns(&self) -> usize {
+ self.shape.1
+ }
+
+ fn hint_count_rows(&self) -> Option<usize> {
+ Some(self.shape.0)
+ }
+}
+
+impl<'a, T> Records for &'a VecRecords<T>
+where
+ T: AsRef<str>,
+{
+ type Iter = &'a [Vec<T>];
+
+ fn iter_rows(self) -> <Self::Iter as IntoRecords>::IterRows {
+ (&self.data).iter_rows()
+ }
+
+ fn count_columns(&self) -> usize {
+ self.shape.1
+ }
+
+ fn hint_count_rows(&self) -> Option<usize> {
+ Some(self.shape.0)
+ }
+}
+
+impl<T> ExactRecords for VecRecords<T> {
+ fn count_rows(&self) -> usize {
+ self.shape.0
+ }
+}
+
+impl<T> PeekableRecords for VecRecords<T>
+where
+ T: Cell,
+{
+ fn get_text(&self, (row, col): Position) -> &str {
+ self[row][col].text()
+ }
+
+ fn count_lines(&self, (row, col): Position) -> usize {
+ self[row][col].count_lines()
+ }
+
+ fn get_line(&self, (row, col): Position, line: usize) -> &str {
+ self[row][col].line(line)
+ }
+
+ fn get_line_width(&self, (row, col): Position, line: usize) -> usize {
+ self[row][col].line_width(line)
+ }
+
+ fn get_width(&self, (row, col): Position) -> usize {
+ self[row][col].width()
+ }
+}
+
+impl<T> Deref for VecRecords<T> {
+ type Target = Vec<Vec<T>>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
+impl<T> DerefMut for VecRecords<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.data
+ }
+}
+
+impl<T> From<VecRecords<T>> for Vec<Vec<T>> {
+ fn from(records: VecRecords<T>) -> Self {
+ records.data
+ }
+}
diff --git a/vendor/papergrid/src/util/mod.rs b/vendor/papergrid/src/util/mod.rs
new file mode 100644
index 000000000..8480ff9be
--- /dev/null
+++ b/vendor/papergrid/src/util/mod.rs
@@ -0,0 +1,3 @@
+//! A module contains utility functions which grid relay on.
+
+pub mod string;
diff --git a/vendor/papergrid/src/util/string.rs b/vendor/papergrid/src/util/string.rs
new file mode 100644
index 000000000..0017e6a91
--- /dev/null
+++ b/vendor/papergrid/src/util/string.rs
@@ -0,0 +1,270 @@
+//! This module contains a different functions which are used by the [`Grid`].
+//!
+//! You should use it if you want to comply with how [`Grid`].
+//!
+//! [`Grid`]: crate::grid::iterable::Grid
+
+/// Returns string width and count lines of a string. It's a combination of [`string_width_multiline`] and [`count_lines`].
+#[cfg(feature = "std")]
+pub fn string_dimension(text: &str) -> (usize, usize) {
+ #[cfg(not(feature = "color"))]
+ {
+ let (lines, acc, max) = text.chars().fold((1, 0, 0), |(lines, acc, max), c| {
+ if c == '\n' {
+ (lines + 1, 0, acc.max(max))
+ } else {
+ let w = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
+ (lines, acc + w, max)
+ }
+ });
+
+ (lines, acc.max(max))
+ }
+
+ #[cfg(feature = "color")]
+ {
+ get_lines(text)
+ .map(|line| string_width(&line))
+ .fold((0, 0), |(i, acc), width| (i + 1, acc.max(width)))
+ }
+}
+
+/// Returns a string width.
+pub fn string_width(text: &str) -> usize {
+ #[cfg(not(feature = "color"))]
+ {
+ unicode_width::UnicodeWidthStr::width(text)
+ }
+
+ #[cfg(feature = "color")]
+ {
+ // we need to strip ansi because of terminal links
+ // and they're can't be stripped by ansi_str.
+
+ ansitok::parse_ansi(text)
+ .filter(|e| e.kind() == ansitok::ElementKind::Text)
+ .map(|e| &text[e.start()..e.end()])
+ .map(unicode_width::UnicodeWidthStr::width)
+ .sum()
+ }
+}
+
+/// Returns a max string width of a line.
+pub fn string_width_multiline(text: &str) -> usize {
+ #[cfg(not(feature = "color"))]
+ {
+ text.lines()
+ .map(unicode_width::UnicodeWidthStr::width)
+ .max()
+ .unwrap_or(0)
+ }
+
+ #[cfg(feature = "color")]
+ {
+ text.lines().map(string_width).max().unwrap_or(0)
+ }
+}
+
+/// Calculates a number of lines.
+pub fn count_lines(s: &str) -> usize {
+ if s.is_empty() {
+ return 1;
+ }
+
+ bytecount::count(s.as_bytes(), b'\n') + 1
+}
+
+/// Returns a list of tabs (`\t`) in a string..
+pub fn count_tabs(s: &str) -> usize {
+ bytecount::count(s.as_bytes(), b'\t')
+}
+
+/// Splits the string by lines.
+#[cfg(feature = "std")]
+pub fn get_lines(text: &str) -> Lines<'_> {
+ #[cfg(not(feature = "color"))]
+ {
+ // we call `split()` but not `lines()` in order to match colored implementation
+ // specifically how we treat a trailing '\n' character.
+ Lines {
+ inner: text.split('\n'),
+ }
+ }
+
+ #[cfg(feature = "color")]
+ {
+ Lines {
+ inner: ansi_str::AnsiStr::ansi_split(text, "\n"),
+ }
+ }
+}
+
+/// Iterator over lines.
+///
+/// In comparison to `std::str::Lines`, it treats trailing '\n' as a new line.
+#[allow(missing_debug_implementations)]
+#[cfg(feature = "std")]
+pub struct Lines<'a> {
+ #[cfg(not(feature = "color"))]
+ inner: std::str::Split<'a, char>,
+ #[cfg(feature = "color")]
+ inner: ansi_str::AnsiSplit<'a>,
+}
+#[cfg(feature = "std")]
+impl<'a> Iterator for Lines<'a> {
+ type Item = std::borrow::Cow<'a, str>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ #[cfg(not(feature = "color"))]
+ {
+ self.inner.next().map(std::borrow::Cow::Borrowed)
+ }
+
+ #[cfg(feature = "color")]
+ {
+ self.inner.next()
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+/// Replaces tabs in a string with a given width of spaces.
+pub fn replace_tab(text: &str, n: usize) -> std::borrow::Cow<'_, str> {
+ if !text.contains('\t') {
+ return std::borrow::Cow::Borrowed(text);
+ }
+
+ // it's a general case which probably must be faster?
+ let replaced = if n == 4 {
+ text.replace('\t', " ")
+ } else {
+ let mut text = text.to_owned();
+ replace_tab_range(&mut text, n);
+ text
+ };
+
+ std::borrow::Cow::Owned(replaced)
+}
+
+#[cfg(feature = "std")]
+fn replace_tab_range(cell: &mut String, n: usize) -> &str {
+ let mut skip = 0;
+ while let &Some(pos) = &cell[skip..].find('\t') {
+ let pos = skip + pos;
+
+ let is_escaped = pos > 0 && cell.get(pos - 1..pos) == Some("\\");
+ if is_escaped {
+ skip = pos + 1;
+ } else if n == 0 {
+ cell.remove(pos);
+ skip = pos;
+ } else {
+ // I'am not sure which version is faster a loop of 'replace'
+ // or allacation of a string for replacement;
+ cell.replace_range(pos..=pos, &" ".repeat(n));
+ skip = pos + 1;
+ }
+
+ if cell.is_empty() || skip >= cell.len() {
+ break;
+ }
+ }
+
+ cell
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn string_width_emojie_test() {
+ // ...emojis such as “joy”, which normally take up two columns when printed in a terminal
+ // https://github.com/mgeisler/textwrap/pull/276
+ assert_eq!(string_width("🎩"), 2);
+ assert_eq!(string_width("Rust 💕"), 7);
+ assert_eq!(string_width_multiline("Go 👍\nC 😎"), 5);
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn colored_string_width_test() {
+ use owo_colors::OwoColorize;
+ assert_eq!(string_width(&"hello world".red().to_string()), 11);
+ assert_eq!(
+ string_width_multiline(&"hello\nworld".blue().to_string()),
+ 5
+ );
+ assert_eq!(string_width("\u{1b}[34m0\u{1b}[0m"), 1);
+ assert_eq!(string_width(&"0".red().to_string()), 1);
+ }
+
+ #[test]
+ fn count_lines_test() {
+ assert_eq!(
+ count_lines("\u{1b}[37mnow is the time for all good men\n\u{1b}[0m"),
+ 2
+ );
+ assert_eq!(count_lines("now is the time for all good men\n"), 2);
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn string_width_multinline_for_link() {
+ assert_eq!(
+ string_width_multiline(
+ "\u{1b}]8;;file:///home/nushell/asd.zip\u{1b}\\asd.zip\u{1b}]8;;\u{1b}\\"
+ ),
+ 7
+ );
+ }
+
+ #[cfg(feature = "color")]
+ #[test]
+ fn string_width_for_link() {
+ assert_eq!(
+ string_width("\u{1b}]8;;file:///home/nushell/asd.zip\u{1b}\\asd.zip\u{1b}]8;;\u{1b}\\"),
+ 7
+ );
+ }
+
+ #[cfg(feature = "std")]
+ #[test]
+ fn string_dimension_test() {
+ assert_eq!(
+ string_dimension("\u{1b}[37mnow is the time for all good men\n\u{1b}[0m"),
+ {
+ #[cfg(feature = "color")]
+ {
+ (2, 32)
+ }
+ #[cfg(not(feature = "color"))]
+ {
+ (2, 36)
+ }
+ }
+ );
+ assert_eq!(
+ string_dimension("now is the time for all good men\n"),
+ (2, 32)
+ );
+ assert_eq!(string_dimension("asd"), (1, 3));
+ assert_eq!(string_dimension(""), (1, 0));
+ }
+
+ #[cfg(feature = "std")]
+ #[test]
+ fn replace_tab_test() {
+ assert_eq!(replace_tab("123\t\tabc\t", 3), "123 abc ");
+
+ assert_eq!(replace_tab("\t", 0), "");
+ assert_eq!(replace_tab("\t", 3), " ");
+ assert_eq!(replace_tab("123\tabc", 3), "123 abc");
+ assert_eq!(replace_tab("123\tabc\tzxc", 0), "123abczxc");
+
+ assert_eq!(replace_tab("\\t", 0), "\\t");
+ assert_eq!(replace_tab("\\t", 4), "\\t");
+ assert_eq!(replace_tab("123\\tabc", 0), "123\\tabc");
+ assert_eq!(replace_tab("123\\tabc", 4), "123\\tabc");
+ }
+}