Repository: zhiburt/tabled
Pull request #40 (2021-12-08 14:04:35)

Add join method
It relates #31 . 

Note
- What options do I need?
- Is there a way to write the `pub mod` that doesn't require it to be specified?

zhiburt: Looks good :100: 

I have a couple of notes though.

1. I think we need to handle a case where `data1.len() != data2.len()`.
Currently as I see we get panicked at 'index out of bounds: the len is 5 but the index is 5`.
    
    By default we could keep the such cells empty.
    But it should be optional. 
    
    Here's a tests that can be used
    
    ```rust
    #[test]
    fn table_join_horizontal_with_different_length() {
        #[derive(Tabled)]
        enum Domain {
            Security,
            Embeded,
            Frontend,
            Unknown,
        }
    
        #[derive(Tabled)]
        struct Developer(#[header("name")] &'static str);
    
        let data1 = vec![
            (Developer("Terri Kshlerin"), Domain::Embeded),
            (Developer("Catalina Dicki"), Domain::Security),
            (Developer("Jennie Schmeler"), Domain::Frontend),
            (Developer("Maxim Zhiburt"), Domain::Unknown),
        ];
    
        let table1 = Table::new(data1).with(Style::psql());
    
        let data2 = vec![
            (Developer("Taro Yamada"), Domain::Embeded),
            (Developer("Hanako Sato"), Domain::Security),
            (Developer("Ken Nakamura"), Domain::Unknown),
            (Developer("Hiro Takahashi"), Domain::Frontend),
            (Developer("Hiro Takahashi"), Domain::Frontend),
        ];
    
        let table2 = Table::new(data2).with(Style::psql());
        let table3 = table1.with(join::Join::HorizontalRight(table2));
    
        assert_eq!(
            table3.to_string(),
            concat!(
                "      name       | Security | Embeded | Frontend | Unknown       name      | Security | Embeded | Frontend | Unknown \n",
                "-----------------+----------+---------+----------+-------------------------+----------+---------+----------+---------\n",
                " Terri Kshlerin  |          |    +    |          |           Taro Yamada   |          |    +    |          |         \n",
                " Catalina Dicki  |    +     |         |          |           Hanako Sato   |    +     |         |          |         \n",
                " Jennie Schmeler |          |         |    +     |           Ken Nakamura  |          |         |          |    +    \n",
                "  Maxim Zhiburt  |          |         |          |    +     Hiro Takahashi |          |         |    +     |         \n",
                "                 |          |         |          |          Hiro Takahashi |          |         |    +     |         \n",
            )
        );
    }
    ```
    
    ```rust
    #[test]
    fn table_join_vertical_with_different_length() {
        #[derive(Tabled)]
        enum Domain {
            Security,
            Embeded,
            Frontend,
            Unknown,
        }
    
        #[derive(Tabled)]
        struct Developer(#[header("name")] &'static str);
    
        let data1 = vec![
            (Developer("Terri Kshlerin"), Domain::Embeded),
            (Developer("Catalina Dicki"), Domain::Security),
            (Developer("Jennie Schmeler"), Domain::Frontend),
            (Developer("Maxim Zhiburt"), Domain::Unknown),
        ];
    
        let table1 = Table::new(data1).with(Style::psql());
    
        let data2 = vec![
            (Developer("Taro Yamada"), Domain::Embeded),
            (Developer("Hanako Sato"), Domain::Security),
            (Developer("Ken Nakamura"), Domain::Unknown),
            (Developer("Hiro Takahashi"), Domain::Frontend),
            (Developer("Hiro Takahashi"), Domain::Frontend),
        ];
    
        let table2 = Table::new(data2).with(Style::psql());
        let table3 = table1.with(join::Join::VerticalTop(table2));
    
        assert_eq!(
            table3.to_string(),
            concat!(
                "      name       | Security | Embeded | Frontend | Unknown \n",
                "-----------------+----------+---------+----------+---------\n",
                "   Taro Yamada   |          |    +    |          |         \n",
                "   Hanako Sato   |    +     |         |          |         \n",
                "  Ken Nakamura   |          |         |          |    +    \n",
                " Hiro Takahashi  |          |         |    +     |         \n",
                " Hiro Takahashi  |          |         |    +     |         \n",
                "      name       | Security | Embeded | Frontend | Unknown \n",
                "-----------------+----------+---------+----------+---------\n",
                " Terri Kshlerin  |          |    +    |          |         \n",
                " Catalina Dicki  |    +     |         |          |         \n",
                " Jennie Schmeler |          |         |    +     |         \n",
                "  Maxim Zhiburt  |          |         |          |    +    \n",
            )
        );
    }
    ```

2. What is the point to have a Top/Bottom combination?
    An order of arguments could be changed to reach the same affect.
    I mean call ` table1.with(join::Join::Vertical(table2));` for Bottom style of vertical join or cal `table2.with(join::Join::VerticalTop(table1));` for top one.
   *Left/right is a bit different. Look at #31 I describe a new idea there.
zhiburt: Overall I think you shouldn't be worry with this comment https://github.com/zhiburt/tabled/issues/31#issuecomment-989773210

We can merge your PR with the functionality you've already implemented and then iterate on it.

senk8: @zhiburt 

> I think we need to handle a case where data1.len() != data2.len().
> Currently as I see we get panicked at 'index out of bounds: the len is 5 but the index is 5`.
> 
> In case of horizontal join we could keep the such cells empty.
> But it should be a configurable option.

How should it be designed?  Here are two ideas I have.

1. We add variant of `Join::StrictHorizontal(Table)`. The behavior is determined by whether variant is `StrictHorizontal` or `Horizontal`.

2. We modify the enum  to be  `Join::Horizontal(Table,bool)`. The behavior is determined by whether bool is `true` or `false`.

Please feel free to give me any ideas.



zhiburt: Aaaa having it's as a optional behavior might be a good idea.

I would rather go with something like this.

```rust
pub struct Join {
  mode: JoinMode,
  strict_size: bool,
}

pub enum JoinMode {
    Vertical,
    Horizontal,
}
```

senk8: https://github.com/zhiburt/tabled/pull/40#issuecomment-991946855
This is very cool 👍 . I am working on the design.
senk8: @zhiburt 
Sorry for the late reply 😔.  I have implemented all the features except https://github.com/zhiburt/tabled/issues/31#issuecomment-989773210 . We didn't discuss the case of different column sizes in vertical join, what do you think?  I think that we can use empty cell like a horizontal join.

Patch:
diff --git a/README.md b/README.md
index 63d8c6c..e048907 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 [![Build Status](https://github.com/zhiburt/tabled/actions/workflows/ci.yml/badge.svg?style=for-the-badge)](https://github.com/zhiburt/tabled/actions)
 [![Coverage Status](https://img.shields.io/coveralls/github/zhiburt/tabled/master)](https://coveralls.io/github/zhiburt/tabled)
 [![Crate](https://img.shields.io/crates/v/tabled)](https://crates.io/crates/tabled)
-[![docs.rs](https://img.shields.io/docsrs/tabled?color=blue)](https://docs.rs/tabled/0.1.1/tabled/)
+[![docs.rs](https://img.shields.io/docsrs/tabled?color=blue)](https://docs.rs/tabled)
 [![license](https://img.shields.io/crates/l/tabled)](./LICENSE.txt)
 [![dependency status](https://deps.rs/repo/github/zhiburt/tabled/status.svg)](https://deps.rs/repo/github/zhiburt/tabled)
 
diff --git a/papergrid/src/lib.rs b/papergrid/src/lib.rs
index 22c29f1..a3210a8 100644
--- a/papergrid/src/lib.rs
+++ b/papergrid/src/lib.rs
@@ -338,7 +338,7 @@ impl Grid {
     }
 
     /// get_cell_content returns content without any style changes
-    pub fn get_cell_content(&mut self, row: usize, column: usize) -> &str {
+    pub fn get_cell_content(&self, row: usize, column: usize) -> &str {
         self.cells[row][column].as_str()
     }
 
@@ -1352,18 +1352,12 @@ impl Borders {
         }
 
         if line.len() != self.count_columns {
-            return Err(BorderError::NotEnoughLineSymbols {
-                expected: self.count_columns,
-                got: line.len(),
-            });
+            return Err(BorderError::NotEnoughLineSymbols);
         }
 
         let need_intersections = self.need_horizontal_intersections();
         if intersections.len() != need_intersections {
-            return Err(BorderError::NotEnoughIntersections {
-                expected: need_intersections,
-                got: intersections.len(),
-            });
+            return Err(BorderError::NotEnoughIntersections);
         }
 
         self.horizontal.insert(row, line);
@@ -1409,18 +1403,12 @@ impl Borders {
         }
 
         if line.len() != self.count_rows {
-            return Err(BorderError::NotEnoughLineSymbols {
-                expected: self.count_rows,
-                got: line.len(),
-            });
+            return Err(BorderError::NotEnoughLineSymbols);
         }
 
         let need_intersections = self.need_vertical_intersections();
         if intersections.len() != need_intersections {
-            return Err(BorderError::NotEnoughIntersections {
-                expected: need_intersections,
-                got: intersections.len(),
-            });
+            return Err(BorderError::NotEnoughIntersections);
         }
 
         self.vertical.insert(column, line);
@@ -1497,8 +1485,8 @@ enum BorderError {
     WrongIntersectionIndex,
     WrongRowIndex,
     WrongColumnIndex,
-    NotEnoughLineSymbols { expected: usize, got: usize },
-    NotEnoughIntersections { expected: usize, got: usize },
+    NotEnoughLineSymbols,
+    NotEnoughIntersections,
 }
 
 fn entity_frame(entity: &Entity, count_rows: usize, count_columns: usize) -> EntityFrame {
diff --git a/src/display/expanded_display.rs b/src/display/expanded_display.rs
index 4c74ce0..6efb1f2 100644
--- a/src/display/expanded_display.rs
+++ b/src/display/expanded_display.rs
@@ -112,7 +112,7 @@ impl std::fmt::Display for ExpandedDisplay {
             .map(|record| {
                 record
                     .iter()
-                    .map(|v| v.lines().map(|l| papergrid::string_width(l)).max())
+                    .map(|v| v.lines().map(papergrid::string_width).max())
                     .max()
             })
             .max()
diff --git a/src/formating.rs b/src/formating.rs
index ca41edc..20e82c7 100644
--- a/src/formating.rs
+++ b/src/formating.rs
@@ -63,8 +63,20 @@ where
 ///     .with(Modify::new(Full).with(Format(multiline(|s| format!("{}", s)))))
 ///     .to_string();
 /// ```
-pub fn multiline<F: 'static + Fn(&str) -> String>(f: F) -> Box<dyn Fn(&str) -> String> {
-    Box::new(move |s: &str| s.lines().map(|s| f(s)).collect::<Vec<_>>().join("\n"))
+pub fn multiline<F>(f: F) -> Box<dyn Fn(&str) -> String>
+where
+    F: Fn(&str) -> String + 'static,
+{
+    let closure = move |s: &str| {
+        let mut v = Vec::new();
+        for line in s.lines() {
+            v.push(f(line));
+        }
+
+        v.join("\n")
+    };
+
+    Box::new(closure)
 }
 
 /// FormatFrom repeatedly uses first possible element
diff --git a/src/join.rs b/src/join.rs
new file mode 100644
index 0000000..1d9f8c9
--- /dev/null
+++ b/src/join.rs
@@ -0,0 +1,140 @@
+#[allow(unused)]
+use crate::Table;
+use crate::TableOption;
+use papergrid::{Entity, Grid};
+use std::cmp;
+
+pub struct Join {
+    mode: JoinMode,
+    strict_size: bool,
+}
+enum JoinMode {
+    Vertical(Table),
+    Horizontal(Table),
+}
+
+impl Join {
+    pub fn vertical(table: Table) -> Self {
+        Self {
+            mode: JoinMode::Vertical(table),
+            strict_size: false,
+        }
+    }
+
+    pub fn horizontal(table: Table) -> Self {
+        Self {
+            mode: JoinMode::Horizontal(table),
+            strict_size: false,
+        }
+    }
+
+    pub fn strict_size(mut self) -> Self {
+        self.strict_size = true;
+        self
+    }
+}
+
+impl TableOption for Join {
+    fn change(&mut self, other: &mut Grid) {
+        match &mut self.mode {
+            JoinMode::Vertical(table) if self.strict_size => {
+                let new_row_size = table.grid.count_rows() + other.count_rows();
+                let new_column_size = table.grid.count_columns();
+                let mut new_grid = Grid::new(new_row_size, new_column_size);
+
+                for column in 0..new_grid.count_columns() {
+                    for row in 0..new_grid.count_rows() {
+                        let settings = if row < other.count_rows() {
+                            other.get_settings(row, column)
+                        } else {
+                            let row = row - other.count_rows();
+                            table.grid.get_settings(row, column)
+                        };
+                        new_grid.set(
+                            &Entity::Cell(row, column),
+                            settings.border_restriction(false),
+                        );
+                    }
+                }
+
+                *other = new_grid;
+            }
+            JoinMode::Horizontal(table) if self.strict_size => {
+                let new_row_size = table.grid.count_rows();
+                let new_column_size = table.grid.count_columns() + other.count_columns();
+                let mut new_grid = Grid::new(new_row_size, new_column_size);
+
+                for column in 0..new_grid.count_columns() {
+                    for row in 0..new_grid.count_rows() {
+                        let settings = if column < other.count_columns() {
+                            other.get_settings(row, column)
+                        } else {
+                            let column = column - other.count_columns();
+                            table.grid.get_settings(row, column)
+                        };
+                        new_grid.set(
+                            &Entity::Cell(row, column),
+                            settings.border_restriction(false),
+                        );
+                    }
+                }
+
+                *other = new_grid;
+            }
+            JoinMode::Vertical(table) => {
+                let new_row_size = table.grid.count_rows() + other.count_rows();
+                let new_column_size = cmp::max(table.grid.count_columns(), other.count_columns());
+                let mut new_grid = Grid::new(new_row_size, new_column_size);
+
+                for column in 0..other.count_columns() {
+                    for row in 0..other.count_rows() {
+                        let settings = other.get_settings(row, column);
+                        new_grid.set(
+                            &Entity::Cell(row, column),
+                            settings.border_restriction(false),
+                        );
+                    }
+                }
+
+                for column in 0..table.grid.count_columns() {
+                    for row in 0..table.grid.count_rows() {
+                        let settings = table.grid.get_settings(row, column);
+                        new_grid.set(
+                            &Entity::Cell(other.count_rows() + row, column),
+                            settings.border_restriction(false),
+                        );
+                    }
+                }
+
+                *other = new_grid;
+            }
+            JoinMode::Horizontal(table) => {
+                let new_row_size = cmp::max(table.grid.count_rows(), other.count_rows());
+                let new_column_size = table.grid.count_columns() + other.count_columns();
+                let mut new_grid = Grid::new(new_row_size, new_column_size);
+
+                for column in 0..other.count_columns() {
+                    for row in 0..other.count_rows() {
+                        let settings = other.get_settings(row, column);
+                        new_grid.set(
+                            &Entity::Cell(row, column),
+                            settings.border_restriction(false),
+                        );
+                    }
+                }
+
+                for column in 0..table.grid.count_columns() {
+                    for row in 0..table.grid.count_rows() {
+                        let settings = table.grid.get_settings(row, column);
+                        new_grid.set(
+                            &Entity::Cell(row, other.count_columns() + column),
+                            settings.border_restriction(false),
+                        );
+                    }
+                }
+
+                *other = new_grid;
+            }
+        }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 03f0824..dd1a023 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -150,6 +150,7 @@ pub mod display;
 mod formating;
 mod highlight;
 mod indent;
+pub mod join;
 mod object;
 mod panel;
 mod rotate;
@@ -264,6 +265,27 @@ impl Table {
     }
 }
 
+/// A trait for [IntoIterator] whose Item type is bound to [Tabled].
+/// Any type implements [IntoIterator] can call this function directly
+///
+/// ```text
+/// let people: &[Person] = ...
+/// let table = people.table().with(Style::PSQL);
+/// ```
+pub trait TableIteratorExt {
+    fn table(self) -> Table;
+}
+
+impl<T, U> TableIteratorExt for U
+where
+    T: Tabled,
+    U: IntoIterator<Item = T>,
+{
+    fn table(self) -> Table {
+        Table::new(self)
+    }
+}
+
 impl fmt::Display for Table {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(f, "{}", self.grid)


Test patch:
diff --git a/tests/join_test.rs b/tests/join_test.rs
new file mode 100644
index 0000000..e6a1be6
--- /dev/null
+++ b/tests/join_test.rs
@@ -0,0 +1,193 @@
+use tabled::join::Join;
+use tabled::{Alignment, Full, Modify, Style, Table};
+use util::create_vector;
+
+mod util;
+
+#[test]
+fn table_join_vertical_top() {
+    let data1 = create_vector::<3, 3>();
+    let data2 = create_vector::<3, 3>();
+
+    let table1 = Table::new(&data1)
+        .with(Style::PSQL)
+        .with(Modify::new(Full).with(Alignment::left()));
+
+    let table2 = Table::new(&data2)
+        .with(Style::ASCII)
+        .with(Modify::new(Full).with(Alignment::left()));
+
+    let table3 = table1.with(Join::vertical(table2).strict_size());
+
+    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      | 2-2       \n",
+        "+---+----------+----------+----------+\n",
+        "| N | column 0 | column 1 | column 2 |\n",
+        "+---+----------+----------+----------+\n",
+        "| 0 | 0-0      | 0-1      | 0-2      |\n",
+        "+---+----------+----------+----------+\n",
+        "| 1 | 1-0      | 1-1      | 1-2      |\n",
+        "+---+----------+----------+----------+\n",
+        "| 2 | 2-0      | 2-1      | 2-2      |\n",
+        "+---+----------+----------+----------+\n",
+    );
+
+    assert_eq!(table3.to_string(), expected);
+}
+
+#[test]
+fn table_join_vertical_bottom() {
+    let data1 = create_vector::<3, 3>();
+    let data2 = create_vector::<3, 3>();
+
+    let table1 = Table::new(&data1)
+        .with(Style::PSQL)
+        .with(Modify::new(Full).with(Alignment::left()));
+
+    let table2 = Table::new(&data2)
+        .with(Style::ASCII)
+        .with(Modify::new(Full).with(Alignment::left()));
+
+    let table3 = table2.with(Join::vertical(table1).strict_size());
+
+    let expected = concat!(
+        "+---+----------+----------+----------+\n",
+        "| N | column 0 | column 1 | column 2 |\n",
+        "+---+----------+----------+----------+\n",
+        "| 0 | 0-0      | 0-1      | 0-2      |\n",
+        "+---+----------+----------+----------+\n",
+        "| 1 | 1-0      | 1-1      | 1-2      |\n",
+        "+---+----------+----------+----------+\n",
+        "| 2 | 2-0      | 2-1      | 2-2      |\n",
+        "+---+----------+----------+----------+\n",
+        "  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      | 2-2       \n",
+    );
+
+    assert_eq!(table3.to_string(), expected);
+}
+
+#[test]
+fn table_join_horizontal_left() {
+    let data1 = create_vector::<3, 3>();
+    let data2 = create_vector::<3, 3>();
+
+    let table1 = Table::new(&data1)
+        .with(Style::PSQL)
+        .with(Modify::new(Full).with(Alignment::left()));
+
+    let table2 = Table::new(&data2)
+        .with(Style::ASCII)
+        .with(Modify::new(Full).with(Alignment::left()));
+
+    let table3 = table2.with(Join::horizontal(table1).strict_size());
+
+    let expected = concat!(
+        "+---+----------+----------+----------+                                    \n",
+        "| N | column 0 | column 1 | column 2 | N | column 0 | column 1 | column 2 \n",
+        "+---+----------+----------+----------+---+----------+----------+----------\n",
+        "| 0 | 0-0      | 0-1      | 0-2      | 0 | 0-0      | 0-1      | 0-2      \n",
+        "+---+----------+----------+----------+                                    \n",
+        "| 1 | 1-0      | 1-1      | 1-2      | 1 | 1-0      | 1-1      | 1-2      \n",
+        "+---+----------+----------+----------+                                    \n",
+        "| 2 | 2-0      | 2-1      | 2-2      | 2 | 2-0      | 2-1      | 2-2      \n",
+        "+---+----------+----------+----------+                                    \n",
+    );
+
+    assert_eq!(table3.to_string(), expected);
+}
+
+#[test]
+fn table_join_vertical_different_size() {
+    let data1 = create_vector::<3, 3>();
+    let data2 = create_vector::<3, 4>();
+
+    let table1 = Table::new(&data1)
+        .with(Style::PSQL)
+        .with(Modify::new(Full).with(Alignment::left()));
+
+    let table2 = Table::new(&data2)
+        .with(Style::PSQL)
+        .with(Modify::new(Full).with(Alignment::left()));
+
+    let table3 = table1.with(Join::vertical(table2));
+
+    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      | 2-2                 \n",
+        " N | column 0 | column 1 | column 2 | column 3 \n",
+        "---+----------+----------+----------+----------\n",
+        " 0 | 0-0      | 0-1      | 0-2      | 0-3      \n",
+        " 1 | 1-0      | 1-1      | 1-2      | 1-3      \n",
+        " 2 | 2-0      | 2-1      | 2-2      | 2-3      \n",
+    );
+
+    assert_eq!(table3.to_string(), expected);
+}
+
+#[test]
+fn table_join_horizontal_right() {
+    let data1 = create_vector::<3, 3>();
+    let data2 = create_vector::<3, 3>();
+
+    let table1 = Table::new(&data1)
+        .with(Style::PSQL)
+        .with(Modify::new(Full).with(Alignment::left()));
+
+    let table2 = Table::new(&data2)
+        .with(Style::ASCII)
+        .with(Modify::new(Full).with(Alignment::left()));
+
+    let table3 = table1.with(Join::horizontal(table2).strict_size());
+
+    let expected = concat!(
+        "                                    +---+----------+----------+----------+\n",
+        " N | column 0 | column 1 | column 2 | N | column 0 | column 1 | column 2 |\n",
+        "---+----------+----------+----------+---+----------+----------+----------+\n",
+        " 0 | 0-0      | 0-1      | 0-2      | 0 | 0-0      | 0-1      | 0-2      |\n",
+        "                                    +---+----------+----------+----------+\n",
+        " 1 | 1-0      | 1-1      | 1-2      | 1 | 1-0      | 1-1      | 1-2      |\n",
+        "                                    +---+----------+----------+----------+\n",
+        " 2 | 2-0      | 2-1      | 2-2      | 2 | 2-0      | 2-1      | 2-2      |\n",
+        "                                    +---+----------+----------+----------+\n",
+    );
+
+    assert_eq!(table3.to_string(), expected);
+}
+
+#[test]
+fn table_join_horizontal_different_size() {
+    let data1 = create_vector::<3, 3>();
+    let data2 = create_vector::<4, 3>();
+
+    let table1 = Table::new(&data1)
+        .with(Style::PSQL)
+        .with(Modify::new(Full).with(Alignment::left()));
+
+    let table2 = Table::new(&data2)
+        .with(Style::PSQL)
+        .with(Modify::new(Full).with(Alignment::left()));
+
+    let table3 = table1.with(Join::horizontal(table2));
+
+    let expected = concat!(
+        " N | column 0 | column 1 | column 2  N | column 0 | column 1 | column 2 \n",
+        "---+----------+----------+-------------+----------+----------+----------\n",
+        " 0 | 0-0      | 0-1      | 0-2       0 | 0-0      | 0-1      | 0-2      \n",
+        " 1 | 1-0      | 1-1      | 1-2       1 | 1-0      | 1-1      | 1-2      \n",
+        " 2 | 2-0      | 2-1      | 2-2       2 | 2-0      | 2-1      | 2-2      \n",
+        "                                     3 | 3-0      | 3-1      | 3-2      \n",
+    );
+
+    assert_eq!(table3.to_string(), expected);
+}
diff --git a/tests/table_test.rs b/tests/table_test.rs
index 459b687..81599c0 100644
--- a/tests/table_test.rs
+++ b/tests/table_test.rs
@@ -1,5 +1,5 @@
 use std::collections::{BTreeMap, BTreeSet};
-use tabled::{Style, Table, Tabled};
+use tabled::{Style, Table, TableIteratorExt, Tabled};
 
 mod default_types {
     use super::*;
@@ -735,3 +735,38 @@ fn tuple_combination() {
         )
     );
 }
+
+#[test]
+fn table_trait() {
+    #[derive(Tabled)]
+    enum Domain {
+        Security,
+        Embeded,
+        Frontend,
+        Unknown,
+    }
+
+    #[derive(Tabled)]
+    struct Developer(#[header("name")] &'static str);
+
+    let data = vec![
+        (Developer("Terri Kshlerin"), Domain::Embeded),
+        (Developer("Catalina Dicki"), Domain::Security),
+        (Developer("Jennie Schmeler"), Domain::Frontend),
+        (Developer("Maxim Zhiburt"), Domain::Unknown),
+    ];
+
+    let table = (&data).table().with(Style::PSQL).to_string();
+
+    assert_eq!(
+        table,
+        concat!(
+            "      name       | Security | Embeded | Frontend | Unknown \n",
+            "-----------------+----------+---------+----------+---------\n",
+            " Terri Kshlerin  |          |    +    |          |         \n",
+            " Catalina Dicki  |    +     |         |          |         \n",
+            " Jennie Schmeler |          |         |    +     |         \n",
+            "  Maxim Zhiburt  |          |         |          |    +    \n"
+        )
+    );
+}