Repository: Nukesor/comfy-table
Pull request #15 (2020-11-08 02:47:42)

Add a constraint to hide columns
Implements #14

codecov-io: # [Codecov](https://codecov.io/gh/Nukesor/comfy-table/pull/15?src=pr&el=h1) Report
> Merging [#15](https://codecov.io/gh/Nukesor/comfy-table/pull/15?src=pr&el=desc) (393dc13) into [master](https://codecov.io/gh/Nukesor/comfy-table/commit/e2ba49bd55624d602d783ecc048dd43b9994820c?el=desc) (e2ba49b) will **increase** coverage by `0.24%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/Nukesor/comfy-table/pull/15/graphs/tree.svg?width=650&height=150&src=pr&token=K5T0p7ekRG)](https://codecov.io/gh/Nukesor/comfy-table/pull/15?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #15      +/-   ##
==========================================
+ Coverage   92.34%   92.58%   +0.24%     
==========================================
  Files           9        9              
  Lines         666      688      +22     
==========================================
+ Hits          615      637      +22     
  Misses         51       51              
```


| [Impacted Files](https://codecov.io/gh/Nukesor/comfy-table/pull/15?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/utils/arrangement.rs](https://codecov.io/gh/Nukesor/comfy-table/pull/15/diff?src=pr&el=tree#diff-c3JjL3V0aWxzL2FycmFuZ2VtZW50LnJz) | `99.35% <100.00%> (+0.05%)` | :arrow_up: |
| [src/utils/borders.rs](https://codecov.io/gh/Nukesor/comfy-table/pull/15/diff?src=pr&el=tree#diff-c3JjL3V0aWxzL2JvcmRlcnMucnM=) | `95.00% <100.00%> (+0.22%)` | :arrow_up: |
| [src/utils/format.rs](https://codecov.io/gh/Nukesor/comfy-table/pull/15/diff?src=pr&el=tree#diff-c3JjL3V0aWxzL2Zvcm1hdC5ycw==) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/Nukesor/comfy-table/pull/15?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/Nukesor/comfy-table/pull/15?src=pr&el=footer). Last update [e2ba49b...a96b67b](https://codecov.io/gh/Nukesor/comfy-table/pull/15?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/style/column.rs b/src/style/column.rs
index 729e760..f375217 100644
--- a/src/style/column.rs
+++ b/src/style/column.rs
@@ -2,6 +2,8 @@
 /// They allow some control over the dynamic content arrangement process.
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum ColumnConstraint {
+    /// This will completely hide a column as if it doesn't exist.
+    Hidden,
     /// Force the column to be as long as it's content.
     /// Use with caution! This can easily break your table, if the column's content is overly long.
     ContentWidth,
diff --git a/src/utils/arrangement.rs b/src/utils/arrangement.rs
index 43a2919..a9994b4 100644
--- a/src/utils/arrangement.rs
+++ b/src/utils/arrangement.rs
@@ -65,6 +65,14 @@ impl ColumnDisplayInfo {
         self.content_width + self.padding.0 + self.padding.1
     }
 
+    pub fn is_hidden(&self) -> bool {
+        if let Some(constraint) = self.constraint {
+            return constraint == ColumnConstraint::Hidden;
+        }
+
+        false
+    }
+
     /// Return the remaining value after subtracting the padding width.
     fn without_padding(&self, width: u16) -> u16 {
         let padding = self.padding_width();
@@ -158,6 +166,9 @@ fn evaluate_constraint(
                 info.constraint = Some(MaxWidth(max_width));
             }
         }
+        Hidden => {
+            info.constraint = Some(ColumnConstraint::Hidden);
+        }
     }
 }
 
@@ -198,6 +209,7 @@ fn disabled_arrangement(infos: &mut Vec<ColumnDisplayInfo>) {
 fn dynamic_arrangement(table: &Table, infos: &mut Vec<ColumnDisplayInfo>, table_width: u16) {
     // Convert to i32 to handle negative values in case we work with a very small terminal
     let mut remaining_width = table_width as i32;
+    let column_count = count_visible_columns(infos);
 
     // Remove space occupied by borders from remaining_width
     if should_draw_left_border(table) {
@@ -207,7 +219,7 @@ fn dynamic_arrangement(table: &Table, infos: &mut Vec<ColumnDisplayInfo>, table_
         remaining_width -= 1;
     }
     if should_draw_vertical_lines(table) {
-        remaining_width -= infos.len() as i32 - 1;
+        remaining_width -= column_count as i32 - 1;
     }
 
     // All columns that have have been checked.
@@ -227,7 +239,7 @@ fn dynamic_arrangement(table: &Table, infos: &mut Vec<ColumnDisplayInfo>, table_
     let mut found_smaller = true;
     while found_smaller {
         found_smaller = false;
-        let remaining_columns = infos.len() - checked.len();
+        let remaining_columns = column_count - checked.len();
 
         // There are no columns left to check. Proceed to the next step
         if remaining_columns == 0 {
@@ -241,6 +253,11 @@ fn dynamic_arrangement(table: &Table, infos: &mut Vec<ColumnDisplayInfo>, table_
         }
 
         for (id, info) in infos.iter_mut().enumerate() {
+            // Ignore hidden columns
+            if info.is_hidden() {
+                continue;
+            }
+
             // We already checked this column, skip it
             if checked.contains(&id) {
                 continue;
@@ -276,7 +293,7 @@ fn dynamic_arrangement(table: &Table, infos: &mut Vec<ColumnDisplayInfo>, table_
     }
 
     // Step 5. Equally distribute the remaining_width to all remaining columns
-    let remaining_columns = infos.len() - checked.len();
+    let remaining_columns = column_count - checked.len();
     // We already managed to fix all.
     if remaining_columns == 0 {
         return;
@@ -296,10 +313,16 @@ fn dynamic_arrangement(table: &Table, infos: &mut Vec<ColumnDisplayInfo>, table_
     let mut excess = remaining_width - (average_space * remaining_columns as u16);
 
     for (id, info) in infos.iter_mut().enumerate() {
+        // Ignore hidden columns
+        if info.is_hidden() {
+            continue;
+        }
+
         // We already checked this column, skip it
         if checked.contains(&id) {
             continue;
         }
+
         // Distribute the excess until nothing is left
         let mut width = if excess > 0 {
             excess -= 1;
@@ -315,6 +338,16 @@ fn dynamic_arrangement(table: &Table, infos: &mut Vec<ColumnDisplayInfo>, table_
     }
 }
 
+fn count_visible_columns(infos: &[ColumnDisplayInfo]) -> usize {
+    let mut count = 0;
+    for info in infos {
+        if !info.is_hidden() {
+            count += 1;
+        }
+    }
+    count
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/src/utils/borders.rs b/src/utils/borders.rs
index 526b002..577d286 100644
--- a/src/utils/borders.rs
+++ b/src/utils/borders.rs
@@ -24,7 +24,7 @@ pub(crate) fn draw_borders(
 fn draw_top_border(table: &Table, display_info: &[ColumnDisplayInfo]) -> String {
     let left_corner = table.style_or_default(TableComponent::TopLeftCorner);
     let top_border = table.style_or_default(TableComponent::TopBorder);
-    let border_intersection = table.style_or_default(TableComponent::TopBorderIntersections);
+    let intersection = table.style_or_default(TableComponent::TopBorderIntersections);
     let right_corner = table.style_or_default(TableComponent::TopRightCorner);
 
     let mut line = String::new();
@@ -33,13 +33,17 @@ fn draw_top_border(table: &Table, display_info: &[ColumnDisplayInfo]) -> String
         line += &left_corner;
     }
 
-    // Add the top border lines depending on column width
-    // Also add the border intersections, if we haven't arrived at the last element yet
-    let mut iter = display_info.iter().peekable();
-    while let Some(info) = iter.next() {
-        line += &top_border.repeat(info.width() as usize);
-        if iter.peek().is_some() {
-            line += &border_intersection;
+    // Build the top border line depending on the columns' width.
+    // Also add the border intersections.
+    let mut first = true;
+    for info in display_info.iter() {
+        // Only add something, if the column isn't hidden
+        if !info.is_hidden() {
+            if !first {
+                line += &intersection;
+            }
+            line += &top_border.repeat(info.width() as usize);
+            first = false;
         }
     }
 
@@ -112,6 +116,7 @@ fn draw_horizontal_lines(
     display_info: &[ColumnDisplayInfo],
     header: bool,
 ) -> String {
+    // Styling depends on whether we're currently on the header line or not.
     let (left_intersection, horizontal_lines, middle_intersection, right_intersection) = if header {
         (
             table.style_or_default(TableComponent::LeftHeaderIntersection),
@@ -134,13 +139,17 @@ fn draw_horizontal_lines(
         line += &left_intersection;
     }
 
-    // Add the bottom border lines depending on column width
-    // Also add the border intersections, if we haven't arrived at the last element yet
-    let mut iter = display_info.iter().peekable();
-    while let Some(info) = iter.next() {
-        line += &horizontal_lines.repeat(info.width() as usize);
-        if iter.peek().is_some() {
-            line += &middle_intersection;
+    // Append the middle lines depending on the columns' widths.
+    // Also add the middle intersections.
+    let mut first = true;
+    for info in display_info.iter() {
+        // Only add something, if the column isn't hidden
+        if !info.is_hidden() {
+            if !first {
+                line += &middle_intersection;
+            }
+            line += &horizontal_lines.repeat(info.width() as usize);
+            first = false;
         }
     }
 
@@ -165,12 +174,16 @@ fn draw_bottom_border(table: &Table, display_info: &[ColumnDisplayInfo]) -> Stri
     }
 
     // Add the bottom border lines depending on column width
-    // Also add the border intersections, if we haven't arrived at the last element yet
-    let mut iter = display_info.iter().peekable();
-    while let Some(info) = iter.next() {
-        line += &bottom_border.repeat(info.width() as usize);
-        if iter.peek().is_some() {
-            line += &middle_intersection;
+    // Also add the border intersections.
+    let mut first = true;
+    for info in display_info.iter() {
+        // Only add something, if the column isn't hidden
+        if !info.is_hidden() {
+            if !first {
+                line += &middle_intersection;
+            }
+            line += &bottom_border.repeat(info.width() as usize);
+            first = false;
         }
     }
 
diff --git a/src/utils/format.rs b/src/utils/format.rs
index 0bffe3b..bb6335e 100644
--- a/src/utils/format.rs
+++ b/src/utils/format.rs
@@ -40,7 +40,7 @@ pub fn format_content(table: &Table, display_info: &[ColumnDisplayInfo]) -> Vec<
 
 pub fn format_row(
     row: &Row,
-    display_info: &[ColumnDisplayInfo],
+    display_infos: &[ColumnDisplayInfo],
     table: &Table,
 ) -> Vec<Vec<String>> {
     // The content of this specific row
@@ -48,7 +48,11 @@ pub fn format_row(
 
     let mut cell_iter = row.cells.iter();
     // Now iterate over all cells and handle them according to their alignment
-    for info in display_info.iter() {
+    for info in display_infos.iter() {
+        if info.is_hidden() {
+            cell_iter.next();
+            continue;
+        }
         // Each cell is devided into several lines devided by newline
         // Every line that's too long will be split into two/several lines
         let mut cell_lines = Vec::new();
@@ -133,7 +137,10 @@ pub fn format_row(
     for index in 0..max_lines {
         let mut line = Vec::new();
         let mut cell_iter = temp_row_content.iter();
-        for info in display_info.iter() {
+        for info in display_infos.iter() {
+            if info.is_hidden() {
+                continue;
+            }
             let cell = cell_iter.next().unwrap();
             match cell.get(index) {
                 // The current cell has content for this line. Append it


Test patch:
diff --git a/tests/constraints_test.rs b/tests/constraints_test.rs
index a74d415..f2b17de 100644
--- a/tests/constraints_test.rs
+++ b/tests/constraints_test.rs
@@ -1,7 +1,7 @@
 use comfy_table::*;
 use pretty_assertions::assert_eq;
 
-fn get_max_min_table() -> Table {
+fn get_constraint_table() -> Table {
     let mut table = Table::new();
     table
         .set_header(&vec!["smol", "Header2", "Header3"])
@@ -22,7 +22,7 @@ fn get_max_min_table() -> Table {
 #[test]
 /// Ensure max-, min- and fixed-width constraints are respected
 fn fixed_max_min_constraints() {
-    let mut table = get_max_min_table();
+    let mut table = get_constraint_table();
 
     table.set_constraints(vec![
         ColumnConstraint::MinWidth(10),
@@ -99,7 +99,7 @@ fn fixed_max_min_constraints() {
 /// Max and Min constraints won't be considered, if they are unnecessary
 /// This is true for normal and dynamic arrangement tables.
 fn unnecessary_max_min_constraints() {
-    let mut table = get_max_min_table();
+    let mut table = get_constraint_table();
 
     table.set_constraints(vec![
         ColumnConstraint::MinWidth(1),
@@ -140,7 +140,7 @@ fn unnecessary_max_min_constraints() {
 /// This is allowed, but results in a wider table than acutally aimed for.
 /// Anyway we still try to fit everything as good as possible, which of course breaks stuff.
 fn constraints_bigger_than_table_width() {
-    let mut table = get_max_min_table();
+    let mut table = get_constraint_table();
 
     table
         .set_content_arrangement(ContentArrangement::Dynamic)
@@ -176,7 +176,7 @@ fn constraints_bigger_than_table_width() {
 /// Test correct usage of the Percentage constraint.
 /// Percentage allows to set a fixed width.
 fn percentage() {
-    let mut table = get_max_min_table();
+    let mut table = get_constraint_table();
 
     // Set a percentage of 20% for the first column
     // Hide the second
diff --git a/tests/hidden_test.rs b/tests/hidden_test.rs
new file mode 100644
index 0000000..085d72d
--- /dev/null
+++ b/tests/hidden_test.rs
@@ -0,0 +1,125 @@
+use comfy_table::*;
+use pretty_assertions::assert_eq;
+
+fn get_table() -> Table {
+    let mut table = Table::new();
+    table
+        .load_preset(presets::UTF8_FULL)
+        .set_header(&vec![
+            "hidden_header",
+            "smol",
+            "hidden_header",
+            "Two_hidden_headers_in_a_row",
+            "Header2",
+            "Header3",
+            "hidden_header",
+        ])
+        .add_row(&vec![
+            "start_hidden",
+            "smol",
+            "middle_hidden",
+            "two_hidden_headers_in_a_row",
+            "This is another text",
+            "This is the third text",
+            "end_hidden",
+        ])
+        .add_row(&vec![
+            "asdf",
+            "smol",
+            "asdf",
+            "asdf",
+            "Now\nadd some\nmulti line stuff",
+            "This is awesome",
+            "asdf",
+        ]);
+
+    // Hide the first, third and 6th column
+    table
+        .get_column_mut(0)
+        .unwrap()
+        .set_constraint(ColumnConstraint::Hidden);
+    table
+        .get_column_mut(2)
+        .unwrap()
+        .set_constraint(ColumnConstraint::Hidden);
+    table
+        .get_column_mut(3)
+        .unwrap()
+        .set_constraint(ColumnConstraint::Hidden);
+
+    table
+        .get_column_mut(6)
+        .unwrap()
+        .set_constraint(ColumnConstraint::Hidden);
+
+    table
+}
+
+/// Make sure hidden columns won't be displayed
+#[test]
+fn hidden_columns() {
+    let table = get_table();
+    println!("{}", table.to_string());
+    let expected = "
+┌──────┬──────────────────────┬────────────────────────┐
+│ smol ┆ Header2              ┆ Header3                │
+╞══════╪══════════════════════╪════════════════════════╡
+│ smol ┆ This is another text ┆ This is the third text │
+├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+│ smol ┆ Now                  ┆ This is awesome        │
+│      ┆ add some             ┆                        │
+│      ┆ multi line stuff     ┆                        │
+└──────┴──────────────────────┴────────────────────────┘";
+    assert_eq!("\n".to_string() + &table.to_string(), expected);
+}
+
+/// Make sure dynamic adjustment still works with hidden columns
+#[test]
+fn hidden_columns_with_dynamic_adjustment() {
+    let mut table = get_table();
+    table.set_table_width(25);
+    table.set_content_arrangement(ContentArrangement::Dynamic);
+
+    println!("{}", table.to_string());
+    let expected = "
+┌──────┬────────┬───────┐
+│ smol ┆ Header ┆ Heade │
+│      ┆ 2      ┆ r3    │
+╞══════╪════════╪═══════╡
+│ smol ┆ This   ┆ This  │
+│      ┆ is ano ┆ is    │
+│      ┆ ther   ┆ the   │
+│      ┆ text   ┆ third │
+│      ┆        ┆ text  │
+├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┤
+│ smol ┆ Now    ┆ This  │
+│      ┆ add    ┆ is    │
+│      ┆ some   ┆ aweso │
+│      ┆ multi  ┆ me    │
+│      ┆ line   ┆       │
+│      ┆ stuff  ┆       │
+└──────┴────────┴───────┘";
+    assert_eq!("\n".to_string() + &table.to_string(), expected);
+}
+
+/// Nothing breaks, if all columns are hidden
+#[test]
+fn only_hidden_columns() {
+    let mut table = get_table();
+    table.set_constraints(vec![
+        ColumnConstraint::Hidden,
+        ColumnConstraint::Hidden,
+        ColumnConstraint::Hidden,
+        ColumnConstraint::Hidden,
+        ColumnConstraint::Hidden,
+        ColumnConstraint::Hidden,
+    ]);
+
+    println!("{}", table.to_string());
+    let expected = "
+┌┐
+╞╡
+├┤
+└┘";
+    assert_eq!("\n".to_string() + &table.to_string(), expected);
+}
diff --git a/tests/property_test.rs b/tests/property_test.rs
index bc2fdde..8e6468b 100644
--- a/tests/property_test.rs
+++ b/tests/property_test.rs
@@ -23,6 +23,7 @@ fn column_constraint() -> impl Strategy<Value = Option<ColumnConstraint>> {
     prop_oneof![
         Just(None),
         Just(Some(ColumnConstraint::ContentWidth)),
+        Just(Some(ColumnConstraint::Hidden)),
         any::<u16>().prop_map(|width| { Some(ColumnConstraint::Width(width)) }),
         any::<u16>().prop_map(|width| { Some(ColumnConstraint::MinWidth(width)) }),
         any::<u16>().prop_map(|width| { Some(ColumnConstraint::MaxWidth(width)) }),