Repository: zhiburt/tabled
Pull request #346 (2023-06-25 23:44:37)

tabled/ Add Justification settings


Patch:
diff --git a/papergrid/src/config/spanned/mod.rs b/papergrid/src/config/spanned/mod.rs
index ceb1a90..632d03f 100644
--- a/papergrid/src/config/spanned/mod.rs
+++ b/papergrid/src/config/spanned/mod.rs
@@ -37,6 +37,8 @@ pub struct SpannedConfig {
     horizontal_colors: HashMap<Position, HashMap<Offset, AnsiColor<'static>>>,
     vertical_chars: HashMap<Position, HashMap<Offset, char>>,
     vertical_colors: HashMap<Position, HashMap<Offset, AnsiColor<'static>>>,
+    justification_char: char,
+    justification_color: Option<AnsiColor<'static>>,
 }
 
 impl Default for SpannedConfig {
@@ -56,6 +58,8 @@ impl Default for SpannedConfig {
             horizontal_colors: HashMap::default(),
             vertical_chars: HashMap::default(),
             vertical_colors: HashMap::default(),
+            justification_char: ' ',
+            justification_color: None,
         }
     }
 }
@@ -516,6 +520,30 @@ impl SpannedConfig {
         self.borders_colors.remove_border(pos, shape);
     }
 
+    /// Get a justification which will be used while expanding cells width/height.
+    pub fn get_justification(&self) -> char {
+        self.justification_char
+    }
+
+    /// Get a justification color which will be used while expanding cells width/height.
+    ///
+    /// `None` means no color.
+    pub fn get_justification_color(&self) -> Option<&AnsiColor<'static>> {
+        self.justification_color.as_ref()
+    }
+
+    /// Set a justification which will be used while expanding cells width/height.
+    pub fn set_justification(&mut self, c: char) {
+        self.justification_char = c;
+    }
+
+    /// Set a justification color which will be used while expanding cells width/height.
+    ///
+    /// `None` removes it.
+    pub fn set_justification_color(&mut self, color: Option<AnsiColor<'static>>) {
+        self.justification_color = 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()
@@ -575,9 +603,7 @@ impl SpannedConfig {
     pub fn has_row_spans(&self) -> bool {
         !self.span_rows.is_empty()
     }
-}
 
-impl SpannedConfig {
     /// Gets an intersection character which would be rendered on the grid.
     ///
     /// grid: crate::Grid
diff --git a/papergrid/src/grid/iterable.rs b/papergrid/src/grid/iterable.rs
index c134255..b87b2a9 100644
--- a/papergrid/src/grid/iterable.rs
+++ b/papergrid/src/grid/iterable.rs
@@ -286,6 +286,8 @@ fn print_single_line_column<F: Write, C: Color>(
     let pad = cfg.get_padding(pos);
     let pad_color = cfg.get_padding_color(pos);
     let fmt = cfg.get_formatting(pos);
+    let space_c = cfg.get_justification();
+    let space_color = cfg.get_justification_color();
 
     let (text, text_width) = if fmt.horizontal_trim && !text.is_empty() {
         let text = string_trim(text);
@@ -305,9 +307,9 @@ fn print_single_line_column<F: Write, C: Color>(
 
     print_padding(f, &pad.left, pad_color.left.as_ref())?;
 
-    repeat_char(f, ' ', left)?;
+    print_indent(f, space_c, left, space_color)?;
     print_text(f, &text, color)?;
-    repeat_char(f, ' ', right)?;
+    print_indent(f, space_c, right, space_color)?;
 
     print_padding(f, &pad.right, pad_color.right.as_ref())?;
 
@@ -744,6 +746,7 @@ struct Cell<T, C> {
     pad: Sides<Indent>,
     pad_color: Sides<Option<AnsiColor<'static>>>,
     color: Option<C>,
+    justification: (char, Option<AnsiColor<'static>>),
 }
 
 impl<T, C> Cell<T, C>
@@ -763,6 +766,10 @@ where
         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(),
+            cfg.get_justification_color().cloned(),
+        );
 
         let (count_lines, skip) = if fmt.vertical_trim {
             let (len, top, _) = count_empty_lines(text.as_ref());
@@ -795,6 +802,7 @@ where
             pad,
             pad_color,
             color,
+            justification,
         }
     }
 }
@@ -835,11 +843,14 @@ where
             (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())?;
 
-        repeat_char(f, ' ', left)?;
+        print_indent(f, justification, left, justification_color)?;
         print_text(f, &line, self.color.as_ref())?;
-        repeat_char(f, ' ', right)?;
+        print_indent(f, justification, right, justification_color)?;
 
         print_padding(f, &self.pad.right, self.pad_color.right.as_ref())?;
 
@@ -1145,6 +1156,10 @@ fn print_indent<F: Write>(
     n: usize,
     color: Option<&AnsiColor<'_>>,
 ) -> fmt::Result {
+    if n == 0 {
+        return Ok(());
+    }
+
     match color {
         Some(color) => {
             color.fmt_prefix(f)?;
diff --git a/papergrid/src/grid/peekable.rs b/papergrid/src/grid/peekable.rs
index 2feac39..aaa32a7 100644
--- a/papergrid/src/grid/peekable.rs
+++ b/papergrid/src/grid/peekable.rs
@@ -591,8 +591,19 @@ fn print_cell_line<F: Write, R: Records + PeekableRecords + ExactRecords, C: Col
 
     let width = width - pad.left.size - pad.right.size;
     let alignment = *cfg.get_alignment_horizontal(pos.into());
+    let justification = (cfg.get_justification(), cfg.get_justification_color());
     let color = colors.get_color(pos);
-    print_line(f, records, pos, index, alignment, formatting, color, width)?;
+    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())?;
 
@@ -608,6 +619,7 @@ fn print_line<F: Write, R: Records + PeekableRecords, C: Color>(
     alignment: AlignmentHorizontal,
     formatting: Formatting,
     color: Option<C>,
+    justification: (char, Option<&AnsiColor<'_>>),
     available: usize,
 ) -> fmt::Result {
     let line = records.get_line(pos, index);
@@ -622,7 +634,7 @@ fn print_line<F: Write, R: Records + PeekableRecords, C: Color>(
 
     if formatting.allow_lines_alignment {
         let (left, right) = calculate_indent(alignment, line_width, available);
-        return print_text_with_pad(f, &line, color, left, right);
+        return print_text_with_pad(f, &line, color, justification, left, right);
     }
 
     let cell_width = if formatting.horizontal_trim {
@@ -636,7 +648,7 @@ fn print_line<F: Write, R: Records + PeekableRecords, C: Color>(
     };
 
     let (left, right) = calculate_indent(alignment, cell_width, available);
-    print_text_with_pad(f, &line, color, left, right)?;
+    print_text_with_pad(f, &line, color, justification, left, right)?;
 
     // todo: remove me
     let rest_width = cell_width - line_width;
@@ -649,12 +661,14 @@ 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 {
-    repeat_char(f, ' ', left)?;
+    print_indent(f, justification.0, left, justification.1)?;
     print_text(f, text, color)?;
-    repeat_char(f, ' ', right)
+    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 {
@@ -867,12 +881,11 @@ fn print_indent_lines<F: Write>(
     Ok(())
 }
 
-fn print_indent<F: Write>(
-    f: &mut F,
-    c: char,
-    n: usize,
-    color: Option<&AnsiColor<'_>>,
-) -> fmt::Result {
+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)?;
diff --git a/tabled/Cargo.toml b/tabled/Cargo.toml
index 9d78692..595be94 100644
--- a/tabled/Cargo.toml
+++ b/tabled/Cargo.toml
@@ -262,7 +262,7 @@ color = ["papergrid/color", "ansi-str", "ansitok", "std", "testing_table/ansi"]
 macros = ["std"]
 
 [dependencies]
-papergrid = { version = "0.9", default-features = false }
+papergrid = { path = "../papergrid", default-features = false }
 tabled_derive = { version = "0.6", optional = true }
 ansi-str = { version = "0.8", optional = true }
 ansitok = { version = "0.2", optional = true }
diff --git a/tabled/src/settings/formatting/justification.rs b/tabled/src/settings/formatting/justification.rs
new file mode 100644
index 0000000..15cc36b
--- /dev/null
+++ b/tabled/src/settings/formatting/justification.rs
@@ -0,0 +1,89 @@
+use crate::{
+    grid::color::AnsiColor,
+    grid::config::ColoredConfig,
+    settings::{Color, TableOption},
+};
+
+/// Set a justification character and a color.
+///
+/// Default value is ' ' (<space>) with no color.
+///
+/// # Examples
+///
+/// Setting a justification character.
+///
+/// ```
+/// use tabled::{
+///     Table,
+///     settings::formatting::Justification,
+/// };
+///
+/// let mut table = Table::new(&[("Hello", ""), ("", "World")]);
+/// table.with(Justification::new('#'));
+///
+/// assert_eq!(
+///     table.to_string(),
+///     "+-------+-------+\n\
+///      | &str# | &str# |\n\
+///      +-------+-------+\n\
+///      | Hello | ##### |\n\
+///      +-------+-------+\n\
+///      | ##### | World |\n\
+///      +-------+-------+"
+/// );
+/// ```
+/// Setting a justification color.
+///
+/// ```
+/// use tabled::{
+///     Table,
+///     settings::{formatting::Justification, Color},
+/// };
+///
+/// let mut table = Table::new(&[("Hello", ""), ("", "World")]);
+/// table.with(Justification::default().color(Color::BG_BRIGHT_RED));
+///
+/// assert_eq!(
+///     table.to_string(),
+///     "+-------+-------+\n\
+///      | &str\u{1b}[101m \u{1b}[49m | &str\u{1b}[101m \u{1b}[49m |\n\
+///      +-------+-------+\n\
+///      | Hello | \u{1b}[101m     \u{1b}[49m |\n\
+///      +-------+-------+\n\
+///      | \u{1b}[101m     \u{1b}[49m | World |\n\
+///      +-------+-------+"
+/// );
+/// ```
+#[derive(Debug, Default, Clone)]
+pub struct Justification {
+    c: Option<char>,
+    color: Option<AnsiColor<'static>>,
+}
+
+impl Justification {
+    /// Creates new [`Justification`] object.
+    pub fn new(c: char) -> Self {
+        Self {
+            c: Some(c),
+            color: None,
+        }
+    }
+
+    /// Sets a color for a justification.
+    pub fn color(self, color: Color) -> Self {
+        Self {
+            c: self.c,
+            color: Some(color.into()),
+        }
+    }
+}
+
+impl<R, D> TableOption<R, D, ColoredConfig> for Justification {
+    fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
+        let justification_char = self.c.unwrap_or(' ');
+        let justification_color = self.color;
+
+        cfg.set_justification(justification_char);
+        cfg.set_justification_color(justification_color);
+    }
+}
diff --git a/tabled/src/settings/formatting/mod.rs b/tabled/src/settings/formatting/mod.rs
index 56e6c7c..edfcef2 100644
--- a/tabled/src/settings/formatting/mod.rs
+++ b/tabled/src/settings/formatting/mod.rs
@@ -10,10 +10,12 @@
 
 mod alignment_strategy;
 mod charset;
+mod justification;
 mod tab_size;
 mod trim_strategy;
 
 pub use alignment_strategy::AlignmentStrategy;
 pub use charset::{Charset, CleanCharset};
+pub use justification::Justification;
 pub use tab_size::TabSize;
 pub use trim_strategy::TrimStrategy;


Test patch:
diff --git a/papergrid/tests/grid/settings.rs b/papergrid/tests/grid/settings.rs
index f3844a8..e80229a 100644
--- a/papergrid/tests/grid/settings.rs
+++ b/papergrid/tests/grid/settings.rs
@@ -1,5 +1,6 @@
 #![cfg(feature = "std")]
 
+use papergrid::color::AnsiColor;
 use papergrid::config::{AlignmentHorizontal, Border, Borders, Entity, Indent, Sides};
 
 use crate::util::grid;
@@ -150,3 +151,98 @@ test_table!(
         .build(),
     "0-00-1\n$$$###\n$$$###\n$$$###\n1-01-1\n   ###\n   ###"
 );
+
+test_table!(
+    test_justification_char_left_alignment,
+    grid(2, 2)
+        .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]])
+        .config(|cfg| cfg.set_justification('$'))
+        .build(),
+    "+-----+-----------------------------+"
+    "|Hello|World$$$$$$$$$$$$$$$$$$$$$$$$|"
+    "+-----+-----------------------------+"
+    "|$$$$$|Hello Hello Hello Hello Hello|"
+    "+-----+-----------------------------+"
+);
+
+test_table!(
+    test_justification_char_right_alignment,
+    grid(2, 2)
+        .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]])
+        .config(|cfg| {
+            cfg.set_justification('$');
+            cfg.set_alignment_horizontal(Entity::Global, AlignmentHorizontal::Right);
+        })
+        .build(),
+    "+-----+-----------------------------+"
+    "|Hello|$$$$$$$$$$$$$$$$$$$$$$$$World|"
+    "+-----+-----------------------------+"
+    "|$$$$$|Hello Hello Hello Hello Hello|"
+    "+-----+-----------------------------+"
+);
+
+test_table!(
+    test_justification_char_center_alignment,
+    grid(2, 2)
+        .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]])
+        .config(|cfg| {
+            cfg.set_justification('$');
+            cfg.set_alignment_horizontal(Entity::Global, AlignmentHorizontal::Center);
+        })
+        .build(),
+    "+-----+-----------------------------+"
+    "|Hello|$$$$$$$$$$$$World$$$$$$$$$$$$|"
+    "+-----+-----------------------------+"
+    "|$$$$$|Hello Hello Hello Hello Hello|"
+    "+-----+-----------------------------+"
+);
+
+test_table!(
+    test_justification_color_left_alignment,
+    grid(2, 2)
+        .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]])
+        .config(|cfg| {
+            cfg.set_justification('$');
+            cfg.set_justification_color(Some(AnsiColor::new("\u{1b}[34m".into(), "\u{1b}[39m".into())));
+        })
+        .build(),
+        "+-----+-----------------------------+"
+        "|Hello|World\u{1b}[34m$$$$$$$$$$$$$$$$$$$$$$$$\u{1b}[39m|"
+        "+-----+-----------------------------+"
+        "|\u{1b}[34m$$$$$\u{1b}[39m|Hello Hello Hello Hello Hello|"
+        "+-----+-----------------------------+"
+);
+
+test_table!(
+    test_justification_color_right_alignment,
+    grid(2, 2)
+        .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]])
+        .config(|cfg| {
+            cfg.set_justification('$');
+            cfg.set_justification_color(Some(AnsiColor::new("\u{1b}[34m".into(), "\u{1b}[39m".into())));
+            cfg.set_alignment_horizontal(Entity::Global, AlignmentHorizontal::Right);
+        })
+        .build(),
+        "+-----+-----------------------------+"
+        "|Hello|\u{1b}[34m$$$$$$$$$$$$$$$$$$$$$$$$\u{1b}[39mWorld|"
+        "+-----+-----------------------------+"
+        "|\u{1b}[34m$$$$$\u{1b}[39m|Hello Hello Hello Hello Hello|"
+        "+-----+-----------------------------+"
+);
+
+test_table!(
+    test_justification_color_center_alignment,
+    grid(2, 2)
+        .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]])
+        .config(|cfg| {
+            cfg.set_justification('$');
+            cfg.set_justification_color(Some(AnsiColor::new("\u{1b}[34m".into(), "\u{1b}[39m".into())));
+            cfg.set_alignment_horizontal(Entity::Global, AlignmentHorizontal::Center);
+        })
+        .build(),
+    "+-----+-----------------------------+"
+    "|Hello|\u{1b}[34m$$$$$$$$$$$$\u{1b}[39mWorld\u{1b}[34m$$$$$$$$$$$$\u{1b}[39m|"
+    "+-----+-----------------------------+"
+    "|\u{1b}[34m$$\u{1b}[39m\u{1b}[34m$$$\u{1b}[39m|Hello Hello Hello Hello Hello|"
+    "+-----+-----------------------------+"
+);