Repository: zhiburt/tabled
Pull request #80 (2022-03-12 14:26:41)

Add a `Span` support tabled


Patch:
diff --git a/examples/span.rs b/examples/span.rs
new file mode 100644
index 0000000..8d01b02
--- /dev/null
+++ b/examples/span.rs
@@ -0,0 +1,51 @@
+//! The example can be run by this command
+//! `cargo run --example span`
+//!
+//! The table from the example originaly inspired https://github.com/vdmeer/asciitable#column-span
+
+use tabled::{Cell, Modify, Span, Style, TableIteratorExt};
+
+fn main() {
+    let data = [["just 1 column"; 5]; 5];
+
+    let table = data
+        .table()
+        .with(Style::modern())
+        .with(
+            Modify::new(Cell(0, 0))
+                .with(Span::column(5))
+                .with(|_: &str| "span all 5 columns".to_string()),
+        )
+        .with(
+            Modify::new(Cell(1, 0))
+                .with(Span::column(4))
+                .with(|_: &str| "span 4 columns".to_string()),
+        )
+        .with(
+            Modify::new(Cell(2, 0))
+                .with(Span::column(3))
+                .with(|_: &str| "span 3 columns".to_string()),
+        )
+        .with(
+            Modify::new(Cell(2, 3))
+                .with(Span::column(2))
+                .with(|_: &str| "span 2 columns".to_string()),
+        )
+        .with(
+            Modify::new(Cell(3, 0))
+                .with(Span::column(2))
+                .with(|_: &str| "span 3 columns".to_string()),
+        )
+        .with(
+            Modify::new(Cell(3, 2))
+                .with(Span::column(3))
+                .with(|_: &str| "span 3 columns".to_string()),
+        )
+        .with(
+            Modify::new(Cell(4, 1))
+                .with(Span::column(4))
+                .with(|_: &str| "span 4 columns".to_string()),
+        );
+
+    println!("{}", table);
+}
diff --git a/papergrid/src/lib.rs b/papergrid/src/lib.rs
index eeefc3a..951d5fe 100644
--- a/papergrid/src/lib.rs
+++ b/papergrid/src/lib.rs
@@ -1153,7 +1153,7 @@ fn columns_width(
             count_rows,
             count_columns,
             span,
-        )
+        );
     });
 
     widths
@@ -1221,7 +1221,6 @@ fn adjust_range_width(
     // increase the widths
     (0..count_rows)
         .filter(|&row| row != max_row)
-        .filter(|&row| !is_row_bigger_than_span(&styles[row], span))
         .filter(|&row| !is_there_out_of_scope_cell(&styles[row], start_column, end_column)) // ignore the cell we do handle this case later on
         .for_each(|row| {
             let row_width = row_width(
@@ -1250,7 +1249,6 @@ fn adjust_range_width(
     // a width of cells with the same span and on the same column.
     (0..count_rows)
         .filter(|&row| row != max_row)
-        .filter(|&row| !is_row_bigger_than_span(&styles[row], span))
         .filter(|&row| is_there_out_of_scope_cell(&styles[row], start_column, end_column))
         .for_each(|row| {
             (start_column..end_column)
diff --git a/src/alignment.rs b/src/alignment.rs
index 83be2e7..e884ef4 100644
--- a/src/alignment.rs
+++ b/src/alignment.rs
@@ -6,7 +6,7 @@ pub use papergrid::{AlignmentHorizontal, AlignmentVertical};
 /// Alignment represent a horizontal and vertical alignemt setting for any cell on a [crate::Table].
 ///
 /// ```rust,no_run
-///   # use tabled::{Style, Alignment, Modify, Row, Table};
+///   # use tabled::{Alignment, Modify, Row, Table};
 ///   # let data: Vec<&'static str> = Vec::new();
 ///     let table = Table::new(&data).with(Modify::new(Row(..1)).with(Alignment::center_horizontal()));
 /// ```
diff --git a/src/lib.rs b/src/lib.rs
index 35d80e4..1c51f76 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -162,6 +162,7 @@ mod indent;
 mod object;
 mod panel;
 mod rotate;
+mod span;
 mod table;
 mod width;
 
@@ -171,7 +172,7 @@ pub mod style;
 
 pub use crate::{
     alignment::*, concat::*, disable::*, formating::*, highlight::*, indent::*, object::*,
-    panel::*, rotate::*, style::Style, table::*, width::*,
+    panel::*, rotate::*, span::*, style::Style, table::*, width::*,
 };
 
 pub use tabled_derive::Tabled;
diff --git a/src/span.rs b/src/span.rs
new file mode 100644
index 0000000..e2f91c5
--- /dev/null
+++ b/src/span.rs
@@ -0,0 +1,30 @@
+use crate::CellOption;
+use papergrid::{Entity, Grid, Settings};
+
+pub use papergrid::{AlignmentHorizontal, AlignmentVertical};
+
+/// Span represent a horizontal/column span setting for any cell on a [crate::Table].
+///
+/// ```rust,no_run
+///   # use tabled::{Style, Span, Modify, Column, Table};
+///   # let data: Vec<&'static str> = Vec::new();
+///     let table = Table::new(&data)
+///         .with(Modify::new(Column(..1)).with(Span::column(2)));
+/// ```
+#[derive(Debug)]
+pub struct Span {
+    size: usize,
+}
+
+impl Span {
+    /// New constructs a horizontal/column [Span].
+    pub fn column(size: usize) -> Self {
+        Self { size }
+    }
+}
+
+impl CellOption for Span {
+    fn change_cell(&mut self, grid: &mut Grid, row: usize, column: usize) {
+        grid.set(&Entity::Cell(row, column), Settings::new().span(self.size));
+    }
+}


Test patch:
diff --git a/papergrid/tests/render.rs b/papergrid/tests/render.rs
index 8b7f0b8..d178a14 100644
--- a/papergrid/tests/render.rs
+++ b/papergrid/tests/render.rs
@@ -505,6 +505,39 @@ fn render_2_colided_row_span_3x3() {
     assert_eq!(grid, expected);
 }
 
+#[test]
+fn render_spaned_column_in_first_cell_3x3() {
+    let mut grid = Grid::new(3, 3);
+    grid.set_cell_borders(DEFAULT_CELL_STYLE.clone());
+
+    grid.set(
+        &Entity::Cell(0, 0),
+        Settings::new().text("0-0xxxxxxx").span(2),
+    );
+    grid.set(&Entity::Cell(0, 1), Settings::new().text("0-1"));
+    grid.set(&Entity::Cell(0, 2), Settings::new().text("0-2"));
+    grid.set(&Entity::Cell(1, 0), Settings::new().text("1-0"));
+    grid.set(&Entity::Cell(1, 1), Settings::new().text("1-1"));
+    grid.set(&Entity::Cell(1, 2), Settings::new().text("1-2"));
+    grid.set(&Entity::Cell(2, 0), Settings::new().text("2-0"));
+    grid.set(&Entity::Cell(2, 1), Settings::new().text("2-1"));
+    grid.set(&Entity::Cell(2, 2), Settings::new().text("2-2"));
+
+    let grid = grid.to_string();
+
+    let expected = concat!(
+        "+-----+----+---+\n",
+        "|0-0xxxxxxx|0-2|\n",
+        "+-----+----+---+\n",
+        "|1-0  |1-1 |1-2|\n",
+        "+-----+----+---+\n",
+        "|2-0  |2-1 |2-2|\n",
+        "+-----+----+---+\n",
+    );
+
+    assert_eq!(grid, expected);
+}
+
 #[test]
 fn render_row_span_with_different_length() {
     let mut grid = Grid::new(3, 2);
diff --git a/tests/span_test.rs b/tests/span_test.rs
new file mode 100644
index 0000000..3c2c4cb
--- /dev/null
+++ b/tests/span_test.rs
@@ -0,0 +1,433 @@
+use crate::util::create_vector;
+use tabled::{Alignment, Cell, Column, Full, Indent, Modify, Span, Style, Table};
+
+mod util;
+
+#[test]
+fn span_column_test() {
+    let data = create_vector::<3, 3>();
+    {
+        let table = Table::new(&data)
+            .with(Style::psql())
+            .with(Modify::new(Full).with(Alignment::left()))
+            .with(Modify::new(Column(..1)).with(Span::column(2)))
+            .to_string();
+
+        let expected = concat!(
+            " N | column 1 | column 2 \n",
+            "-+-+----------+----------\n",
+            " 0 | 0-1      | 0-2      \n",
+            " 1 | 1-1      | 1-2      \n",
+            " 2 | 2-1      | 2-2      \n",
+        );
+
+        assert_eq!(table, expected);
+    }
+    {
+        let table = Table::new(&data)
+            .with(Style::psql())
+            .with(Modify::new(Full).with(Alignment::left()))
+            .with(Modify::new(Column(1..2)).with(Span::column(2)))
+            .to_string();
+
+        let expected = concat!(
+            " N | column 0 | column 2 \n",
+            "---+-----+----+----------\n",
+            " 0 | 0-0      | 0-2      \n",
+            " 1 | 1-0      | 1-2      \n",
+            " 2 | 2-0      | 2-2      \n",
+        );
+
+        assert_eq!(table, expected);
+    }
+    {
+        let table = Table::new(&data)
+            .with(Style::psql())
+            .with(Modify::new(Full).with(Alignment::left()))
+            .with(Modify::new(Column(..1)).with(Span::column(data.len() + 1)))
+            .to_string();
+
+        let expected = concat!(" N \n", "+++\n", " 0 \n", " 1 \n", " 2 \n");
+
+        assert_eq!(table, expected);
+    }
+}
+
+#[test]
+fn cell_span_test() {
+    let data = create_vector::<3, 3>();
+    {
+        // first column span=2
+
+        {
+            let table = Table::new(&data)
+                .with(Style::psql())
+                .with(Modify::new(Full).with(Alignment::left()))
+                .with(Modify::new(Cell(0, 0)).with(Span::column(2)))
+                .to_string();
+
+            let expected = concat!(
+                " N       | column 1 | column 2 \n",
+                "---+-----+----------+----------\n",
+                " 0 | 0-0 | 0-1      | 0-2      \n",
+                " 1 | 1-0 | 1-1      | 1-2      \n",
+                " 2 | 2-0 | 2-1      | 2-2      \n",
+            );
+
+            assert_eq!(table, expected);
+        }
+        {
+            let table = Table::new(&data)
+                .with(Style::psql())
+                .with(Modify::new(Full).with(Alignment::left()))
+                .with(Modify::new(Cell(1, 0)).with(Span::column(2)))
+                .to_string();
+
+            let expected = concat!(
+                " N | column 0 | column 1 | column 2 \n",
+                "---+----------+----------+----------\n",
+                " 0            | 0-1      | 0-2      \n",
+                " 1 | 1-0      | 1-1      | 1-2      \n",
+                " 2 | 2-0      | 2-1      | 2-2      \n",
+            );
+
+            assert_eq!(table, expected);
+        }
+        {
+            let table = Table::new(&data)
+                .with(Style::psql())
+                .with(Modify::new(Cell(2, 0)).with(Span::column(2)))
+                .to_string();
+
+            let expected = concat!(
+                " N | column 0 | column 1 | column 2 \n",
+                "---+----------+----------+----------\n",
+                " 0 |   0-0    |   0-1    |   0-2    \n",
+                "      1       |   1-1    |   1-2    \n",
+                " 2 |   2-0    |   2-1    |   2-2    \n",
+            );
+
+            assert_eq!(table, expected);
+        }
+        {
+            let table = Table::new(&data)
+                .with(Style::psql())
+                .with(Modify::new(Full).with(Alignment::left()))
+                .with(Modify::new(Cell(3, 0)).with(Span::column(2)))
+                .to_string();
+
+            let expected = concat!(
+                " N | column 0 | column 1 | column 2 \n",
+                "---+----------+----------+----------\n",
+                " 0 | 0-0      | 0-1      | 0-2      \n",
+                " 1 | 1-0      | 1-1      | 1-2      \n",
+                " 2            | 2-1      | 2-2      \n",
+            );
+
+            assert_eq!(table, expected);
+        }
+    }
+
+    {
+        // second column span=2
+        {
+            let table = Table::new(&data)
+                .with(Style::psql())
+                .with(Modify::new(Full).with(Alignment::left()))
+                .with(Modify::new(Cell(0, 1)).with(Span::column(2)))
+                .to_string();
+
+            let expected = concat!(
+                " N | column 0  | column 2 \n",
+                "---+-----+-----+----------\n",
+                " 0 | 0-0 | 0-1 | 0-2      \n",
+                " 1 | 1-0 | 1-1 | 1-2      \n",
+                " 2 | 2-0 | 2-1 | 2-2      \n",
+            );
+
+            assert_eq!(table, expected);
+        }
+        {
+            let table = Table::new(&data)
+                .with(Style::psql())
+                .with(Modify::new(Full).with(Alignment::left()))
+                .with(Modify::new(Cell(1, 1)).with(Span::column(2)))
+                .to_string();
+
+            let expected = concat!(
+                " N | column 0 | column 1 | column 2 \n",
+                "---+----------+----------+----------\n",
+                " 0 | 0-0                 | 0-2      \n",
+                " 1 | 1-0      | 1-1      | 1-2      \n",
+                " 2 | 2-0      | 2-1      | 2-2      \n",
+            );
+
+            assert_eq!(table, expected);
+        }
+        {
+            let table = Table::new(&data)
+                .with(Style::psql())
+                .with(Modify::new(Full).with(Alignment::left()))
+                .with(Modify::new(Cell(2, 1)).with(Span::column(2)))
+                .to_string();
+
+            let expected = concat!(
+                " N | column 0 | column 1 | column 2 \n",
+                "---+----------+----------+----------\n",
+                " 0 | 0-0      | 0-1      | 0-2      \n",
+                " 1 | 1-0                 | 1-2      \n",
+                " 2 | 2-0      | 2-1      | 2-2      \n",
+            );
+
+            assert_eq!(table, expected);
+        }
+        {
+            let table = Table::new(&data)
+                .with(Style::psql())
+                .with(Modify::new(Full).with(Alignment::left()))
+                .with(Modify::new(Cell(3, 1)).with(Span::column(2)))
+                .to_string();
+
+            let expected = concat!(
+                " N | column 0 | column 1 | column 2 \n",
+                "---+----------+----------+----------\n",
+                " 0 | 0-0      | 0-1      | 0-2      \n",
+                " 1 | 1-0      | 1-1      | 1-2      \n",
+                " 2 | 2-0                 | 2-2      \n",
+            );
+
+            assert_eq!(table, expected);
+        }
+    }
+
+    {
+        // 3rd column span = 1
+
+        {
+            let table = Table::new(&data)
+                .with(Style::psql())
+                .with(Modify::new(Cell(0, 2)).with(Span::column(2)))
+                .to_string();
+
+            let expected = concat!(
+                " N | column 0 | column 1  \n",
+                "---+----------+-----+-----\n",
+                " 0 |   0-0    | 0-1 | 0-2 \n",
+                " 1 |   1-0    | 1-1 | 1-2 \n",
+                " 2 |   2-0    | 2-1 | 2-2 \n",
+            );
+
+            assert_eq!(table, expected);
+        }
+        {
+            let table = Table::new(&data)
+                .with(Style::psql())
+                .with(Modify::new(Cell(1, 2)).with(Span::column(2)))
+                .to_string();
+
+            let expected = concat!(
+                " N | column 0 | column 1 | column 2 \n",
+                "---+----------+----------+----------\n",
+                " 0 |   0-0    |         0-1         \n",
+                " 1 |   1-0    |   1-1    |   1-2    \n",
+                " 2 |   2-0    |   2-1    |   2-2    \n",
+            );
+
+            assert_eq!(table, expected);
+        }
+        {
+            let table = Table::new(&data)
+                .with(Style::psql())
+                .with(Modify::new(Cell(2, 2)).with(Span::column(2)))
+                .to_string();
+
+            let expected = concat!(
+                " N | column 0 | column 1 | column 2 \n",
+                "---+----------+----------+----------\n",
+                " 0 |   0-0    |   0-1    |   0-2    \n",
+                " 1 |   1-0    |         1-1         \n",
+                " 2 |   2-0    |   2-1    |   2-2    \n",
+            );
+
+            assert_eq!(table, expected);
+        }
+        {
+            let table = Table::new(&data)
+                .with(Style::psql())
+                .with(Modify::new(Cell(3, 2)).with(Span::column(2)))
+                .to_string();
+
+            let expected = concat!(
+                " N | column 0 | column 1 | column 2 \n",
+                "---+----------+----------+----------\n",
+                " 0 |   0-0    |   0-1    |   0-2    \n",
+                " 1 |   1-0    |   1-1    |   1-2    \n",
+                " 2 |   2-0    |         2-1         \n",
+            );
+
+            assert_eq!(table, expected);
+        }
+    }
+}
+
+#[test]
+#[should_panic]
+fn span_column_exceeds_boundries_test() {
+    // todo: determine if it's the right behaiviour
+
+    let data = create_vector::<3, 3>();
+    Table::new(&data)
+        .with(Modify::new(Column(..1)).with(Span::column(100)))
+        .to_string();
+}
+
+#[test]
+fn span_cell_exceeds_boundries_test() {
+    // these tests shows that exiding boundries causes invalid behaiviour
+    //
+    // todo: determine if it's the right behaiviour
+
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Style::psql())
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Cell(0, 0)).with(Span::column(20)))
+        .to_string();
+
+    let expected = " N \n---+-----+-----+-----\n 0 | 0-0 | 0-1 | 0-2 \n 1 | 1-0 | 1-1 | 1-2 \n 2 | 2-0 | 2-1 | 2-2 \n";
+
+    assert_eq!(table, expected);
+
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Style::psql())
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Cell(1, 1)).with(Span::column(20)))
+        .to_string();
+
+    let expected = " N | column 0 | column 1 | column 2 \n---+----------+----------+----------\n 0 | 0-0 \n 1 | 1-0      | 1-1      | 1-2      \n 2 | 2-0      | 2-1      | 2-2      \n";
+
+    assert_eq!(table, expected);
+
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Style::psql())
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Cell(1, 0)).with(Span::column(20)))
+        .to_string();
+
+    let expected = " N | column 0 | column 1 | column 2 \n---+----------+----------+----------\n 0 \n 1 | 1-0      | 1-1      | 1-2      \n 2 | 2-0      | 2-1      | 2-2      \n";
+
+    assert_eq!(table, expected);
+}
+
+#[test]
+fn span_multiline() {
+    let mut data = create_vector::<3, 3>();
+    data[2][2] = String::from("https://\nwww\n.\nredhat\n.com\n/en");
+
+    let table = Table::new(&data)
+        .with(Style::psql())
+        .with(Modify::new(Cell(3, 2)).with(Span::column(2)))
+        .to_string();
+
+    let expected = concat!(
+        " N | column 0 | column 1 | column 2 \n",
+        "---+----------+----------+----------\n",
+        " 0 |   0-0    |   0-1    |   0-2    \n",
+        " 1 |   1-0    |   1-1    |   1-2    \n",
+        " 2 |   2-0    |      https://       \n",
+        "   |          |         www         \n",
+        "   |          |          .          \n",
+        "   |          |       redhat        \n",
+        "   |          |        .com         \n",
+        "   |          |         /en         \n",
+    );
+
+    assert_eq!(table, expected);
+}
+
+#[test]
+fn indent_works_in_spaned_columns() {
+    let data = create_vector::<3, 3>();
+
+    let table = Table::new(&data)
+        .with(Style::psql())
+        .with(Modify::new(Full).with(Indent::new(3, 0, 0, 0)))
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Cell(1, 1)).with(Span::column(3)))
+        .with(Modify::new(Cell(3, 1)).with(Span::column(3)))
+        .to_string();
+
+    let expected = concat!(
+        "   N|   column 0|   column 1|   column 2\n",
+        "----+-----------+-----------+-----------\n",
+        "   0|   0-0                             \n",
+        "   1|   1-0     |   1-1     |   1-2     \n",
+        "   2|   2-0                             \n",
+    );
+
+    assert_eq!(table, expected);
+}
+
+#[test]
+fn spaned_columns_with_colision() {
+    let data = [["just 1 column"; 5]; 5];
+
+    let table = Table::new(data)
+        .with(Style::modern())
+        .with(
+            Modify::new(Cell(0, 0))
+                .with(Span::column(5))
+                .with(|_: &str| "span all 5 columns".to_string()),
+        )
+        .with(
+            Modify::new(Cell(1, 0))
+                .with(Span::column(4))
+                .with(|_: &str| "span 4 columns".to_string()),
+        )
+        .with(
+            Modify::new(Cell(2, 0))
+                .with(Span::column(3))
+                .with(|_: &str| "span 3 columns".to_string()),
+        )
+        .with(
+            Modify::new(Cell(2, 3))
+                .with(Span::column(2))
+                .with(|_: &str| "span 2 columns".to_string()),
+        )
+        .with(
+            Modify::new(Cell(3, 0))
+                .with(Span::column(2))
+                .with(|_: &str| "span 3 columns".to_string()),
+        )
+        .with(
+            Modify::new(Cell(3, 2))
+                .with(Span::column(3))
+                .with(|_: &str| "span 3 columns".to_string()),
+        )
+        .with(
+            Modify::new(Cell(4, 1))
+                .with(Span::column(4))
+                .with(|_: &str| "span 4 columns".to_string()),
+        )
+        .to_string();
+
+    let expected = concat!(
+        "┌───────────────┬───────────────┬───────────────┬───────────────┬───────────────┐\n",
+        "│                              span all 5 columns                               │\n",
+        "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤\n",
+        "│                        span 4 columns                         │ just 1 column │\n",
+        "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤\n",
+        "│                span 3 columns                 │        span 2 columns         │\n",
+        "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤\n",
+        "│        span 3 columns         │                span 3 columns                 │\n",
+        "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤\n",
+        "│ just 1 column │                        span 4 columns                         │\n",
+        "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤\n",
+        "│ just 1 column │ just 1 column │ just 1 column │ just 1 column │ just 1 column │\n",
+        "└───────────────┴───────────────┴───────────────┴───────────────┴───────────────┘\n",
+    );
+
+    assert_eq!(table, expected);
+}