Repository: zhiburt/tabled
Pull request #87 (2022-03-15 18:12:56)

Extract
#33 
Hello, this is my first public pull request. Any feedback would be greatly appreciated 🙂

This change would add a new Table api feature `Extract`. This feature would allow user to create a new Table from a segment of the original to focus on a reduced number of rows and columns.

Todo list for adding new api:
- [X] code
- [X] tests
- [X] comments
- [X] example
- [X] readme

Please note that I had to make a decision regarding the `grid.extract` method tests. Changing this function to accept references broke all of the `papergrid>tests>extract` tests which I was not happy with. However, I do think that letting the `Grid` extract api be more obtuse to allow the `Table` extract api to be more fluent is a worthy trade.


Again any feedback would mean a lot. Cheers!


Patch:
diff --git a/README.md b/README.md
index dc101a7..c0f7e9c 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,10 @@ An easy to use library for pretty printing tables of Rust `struct`s and `enum`s.
     * [Max width](#Max-width)
     * [Rotate](#Rotate)
     * [Disable](#Disable)
+    * [Extract](#Extract)
+        * [Interface](#Interface)
+        * [Range](#Range)
+        * [Refinish](#Refinishing)
     * [Header and Footer](#Header-and-Footer)
     * [Concat](#Concat)
 * [Derive](#Derive)
@@ -344,6 +348,85 @@ Table::new(&data)
     .with(Disable::Column(3..4));
 ```
 
+### Extract
+
+You can `Extract` segments of a table to focus on a reduced number of rows and columns.
+
+```rust
+let rows = 1..3;
+let columns = 1..;
+Table::new(&data)
+    .with(Extract::segment(rows, columns));
+```
+
+```text
++-------+-------------+-----------+
+|  i32  |    &str     |   bool    |
++-------+-------------+-----------+         +-------------+-----------+
+| : 0 : | : Grodno :  | : true :  |         | : Grodno :  | : true :  |
++-------+-------------+-----------+    =    +-------------+-----------+
+| : 1 : |  : Minsk :  | : true :  |         |  : Minsk :  | : true :  |
++-------+-------------+-----------+         +-------------+-----------+
+| : 2 : | : Hamburg : | : false : |
++-------+-------------+-----------+
+| : 3 : |  : Brest :  | : true :  |
++-------+-------------+-----------+
+```
+
+#### Interface
+
+```rust
+// rows from 0 to max
+pub fn columns(columns: C) -> Extract<RangeFull, C>;
+// columns from 0 to max
+pub fn rows(rows: R) -> Extract<R, RangeFull>;
+
+pub fn segment(rows: R, columns: C) -> Extract<, C>;
+```
+
+#### Range
+
+A `RangeBounds` argument can be less than or equal to the shape of a `Table`
+
+If a `RangeBounds` argument is malformed or too large the thread will panic
+
+```rust
+// Empty                         Full                      Out of bounds
+   Extract::segment(0..0, 0..0)  Extract::segment(.., ..)  Extract::segment(0..1, ..4)
+   [].   .   .                   [O   O   O                [O   O   O  X] //ERROR      
+     .   .   .                    O   O   O                 .   .   .             
+     .   .   .                    O   O   O]                .   .   .          
+```
+
+#### Refinishing
+
+For styles with unique corner and edge textures it is possible to reapply a table style once a `Table` extract has been created.
+
+```rust
+let rows = 1..3;
+let columns = 1..;
+Table::new(&data)
+    .with(Extract::segment(rows, columns))
+    .with(Style::modern());
+```
+
+```text
+Raw extract
+┼───────────────────────────┼──────────────────┼──────────────┤
+│ The Dark Side of the Moon │ 01 March 1973    │ Unparalleled │
+┼───────────────────────────┼──────────────────┼──────────────┤
+│ Rumours                   │ 04 February 1977 │ Outstanding  │
+┼───────────────────────────┼──────────────────┼──────────────┤
+
+Refinished extract
+┌───────────────────────────┬──────────────────┬───────────────┐
+│ The Dark Side of the Moon │ 01 March 1973    │ Unparalleled  │
+├───────────────────────────┼──────────────────┼───────────────┤
+│ Rumours                   │ 04 February 1977 │  Outstanding  │
+└───────────────────────────┴──────────────────┴───────────────┘
+```
+
+
 ### Header and Footer
 
 You can add a `Header` and `Footer` to display some information.
diff --git a/examples/extract.rs b/examples/extract.rs
new file mode 100644
index 0000000..bb31eb0
--- /dev/null
+++ b/examples/extract.rs
@@ -0,0 +1,79 @@
+//! The example can be run by this command
+//! `cargo run --example extract`
+
+use std::fmt::{Display, Formatter};
+
+use tabled::{
+    Alignment, AlignmentHorizontal, Column, Extract, Format, Head, Modify, Row, Style, Table,
+    Tabled,
+};
+
+#[derive(Tabled)]
+struct Album {
+    artist: &'static str,
+    name: &'static str,
+    released: &'static str,
+    level_of_greatness: LevelOfGreatness,
+}
+
+enum LevelOfGreatness {
+    Supreme,
+    Outstanding,
+    Unparalleled,
+}
+
+impl Display for LevelOfGreatness {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
+        match self {
+            LevelOfGreatness::Supreme => write!(f, "Supreme"),
+            LevelOfGreatness::Outstanding => write!(f, "Outstanding"),
+            LevelOfGreatness::Unparalleled => write!(f, "Unparalleled"),
+        }
+    }
+}
+
+fn main() {
+    let data = [
+        Album {
+            artist: "Pink Floyd",
+            name: "The Dark Side of the Moon",
+            released: "01 March 1973",
+            level_of_greatness: LevelOfGreatness::Unparalleled,
+        },
+        Album {
+            artist: "Fleetwood Mac",
+            name: "Rumours",
+            released: "04 February 1977",
+            level_of_greatness: LevelOfGreatness::Outstanding,
+        },
+        Album {
+            artist: "Led Zeppelin",
+            name: "Led Zeppelin IV",
+            released: "08 November 1971",
+            level_of_greatness: LevelOfGreatness::Supreme,
+        },
+    ];
+
+    println!("Full");
+    let table = Table::new(&data)
+        .with(Style::modern())
+        .with(Modify::new(Head).with(Alignment::Horizontal(AlignmentHorizontal::Center)))
+        .with(Modify::new(Row(1..)).with(Alignment::Horizontal(AlignmentHorizontal::Left)));
+    println!("{}", table);
+
+    println!("Segment   row: (1..=2)   column: (1..)");
+    let table = table.with(Extract::segment(1..=2, 1..));
+    println!("{}", table);
+
+    println!("Refinished segment");
+    let table = table
+        .with(Style::modern())
+        .with(Modify::new(Column(1..)).with(Format(|s| {
+            if s == "Outstanding" {
+                format!("+{}+", s)
+            } else {
+                s.to_string()
+            }
+        })));
+    println!("{}", table);
+}
diff --git a/papergrid/src/lib.rs b/papergrid/src/lib.rs
index badd977..f6512c4 100644
--- a/papergrid/src/lib.rs
+++ b/papergrid/src/lib.rs
@@ -398,6 +398,32 @@ impl Grid {
         }
     }
 
+    /// Returns a new [Grid] that reflects a segment of the referenced [Grid]
+    ///
+    /// The segment is defined by [RangeBounds<usize>] for Rows and Columns
+    ///
+    /// # Example
+    ///
+    /// ```rust,no_run
+    /// grid
+    /// +---+---+---+
+    /// |0-0|0-1|0-2|
+    /// +---+---+---+
+    /// |1-0|1-1|1-2|
+    /// +---+---+---+
+    /// |2-0|2-1|2-2|
+    /// +---+---+---+
+    /// let rows = ..;
+    /// let columns = ..1;
+    /// grid.extract(rows, columns)
+    /// +---+
+    /// |0-0|
+    /// +---+
+    /// |1-0|
+    /// +---+
+    /// |2-0|
+    /// +---+
+    /// ```
     pub fn extract<R, C>(&self, rows: R, columns: C) -> Self
     where
         R: RangeBounds<usize>,
diff --git a/src/extract.rs b/src/extract.rs
new file mode 100644
index 0000000..85e49a6
--- /dev/null
+++ b/src/extract.rs
@@ -0,0 +1,178 @@
+use std::ops::{RangeBounds, RangeFull};
+
+use crate::TableOption;
+
+/// Returns a new [Table] that reflects a segment of the referenced [Table]
+///
+/// The segment is defined by [RangeBounds<usize>] for Rows and Columns
+///
+/// # API
+///
+/// ### [`Extract::segment()`](tabled::extract::Extract::segment)
+///
+/// ### [`Extract::rows()`](tabled::extract::Extract::rows)
+///
+/// ### [`Extract::columns()`](tabled::extract::Extract::columns)
+///
+/// # Range
+///
+/// A [RangeBounds] argument can be less than or equal to the shape of a [Table]
+///
+/// If a [RangeBounds] argument is malformed or too large the thread will panic
+///
+///
+/// ```
+/// // Empty                         Full                      Out of bounds
+///    Extract::segment(0..0, 0..0)  Extract::segment(.., ..)  Extract::segment(0..1, ..4)
+///    [].   .   .                   [O   O   O                [O   O   O  X] //ERROR            
+///      .   .   .                    O   O   O                 .   .   .             
+///      .   .   .                    O   O   O]                .   .   .          
+/// ```
+///
+/// # Example
+///
+/// ```
+/// use tabled::{Table, Format, Row, Modify};
+///
+/// let data = vec![
+///     (0, "Grodno", true),
+///     (1, "Minsk", true),
+///     (2, "Hamburg", false),
+///     (3, "Brest", true),
+/// ];
+///
+/// let table = Table::new(&data)
+///                .with(Modify::new(Row(1..)).with(Format(|s| format!(": {} :", s))))
+///                .with(Extract::segment(1..=2, 1..))
+///                .to_string();
+///
+/// assert_eq!(table, "+-------------+-----------+\n\
+///                    | : Grodno :  | : true :  |\n\
+///                    +-------------+-----------+\n\
+///                    |  : Minsk :  | : true :  |\n\
+///                    +-------------+-----------+\n");
+///
+/// assert_ne!(table, "+-------+-------------+-----------+\n\
+///                    |  i32  |    &str     |   bool    |\n\
+///                    +-------+-------------+-----------+\n\
+///                    | : 0 : | : Grodno :  | : true :  |\n\
+///                    +-------+-------------+-----------+\n\
+///                    | : 1 : |  : Minsk :  | : true :  |\n\
+///                    +-------+-------------+-----------+\n\
+///                    | : 2 : | : Hamburg : | : false : |\n\
+///                    +-------+-------------+-----------+\n\
+///                    | : 3 : |  : Brest :  | : true :  |\n\
+///                    +-------+-------------+-----------+\n");
+/// ```
+///
+pub struct Extract<R, C> {
+    rows: R,
+    columns: C,
+}
+
+impl<R, C> Extract<R, C>
+where
+    R: RangeBounds<usize>,
+    C: RangeBounds<usize>,
+{
+    /// Returns a new [Table] that reflects a segment of the referenced [Table]
+    ///
+    /// The segment is defined by [RangeBounds<usize>] for Rows and Columns
+    ///
+    /// ```rust,no_run
+    /// let rows = 1..3;
+    /// let columns = 1..;
+    /// Extract::segment(rows, columns);
+    /// ```
+    ///
+    /// # Range
+    ///
+    /// A [RangeBounds] argument can be less than or equal to the shape of a [Table]
+    ///
+    /// If a [RangeBounds] argument is malformed or too large the thread will panic
+    ///
+    /// ```
+    /// // Empty                         Full                      Out of bounds
+    ///    Extract::segment(0..0, 0..0)  Extract::segment(.., ..)  Extract::segment(0..1, ..4)
+    ///    [].   .   .                   [O   O   O                [O   O   O  X] //ERROR            
+    ///      .   .   .                    O   O   O                 .   .   .             
+    ///      .   .   .                    O   O   O]                .   .   .          
+    /// ```
+    ///
+    pub fn segment(rows: R, columns: C) -> Self {
+        Extract { rows, columns }
+    }
+}
+
+impl<R> Extract<R, RangeFull>
+where
+    R: RangeBounds<usize>,
+{
+    /// Returns a new [Table] that reflects a segment of the referenced [Table]
+    ///
+    /// The segment is defined by [RangeBounds<usize>] for Rows
+    ///
+    /// ```rust,no_run
+    /// Extract::rows(1..3);
+    /// ```
+    ///
+    /// # Range
+    ///
+    /// A [RangeBounds] argument can be less than or equal to the shape of a [Table]
+    ///
+    /// If a [RangeBounds] argument is malformed or too large the thread will panic
+    ///
+    /// ```
+    /// // Empty                Full               Out of bounds
+    ///    Extract::rows(0..0)  Extract::rows(..)  Extract::rows(0..4)
+    ///    [].   .   .          [O   O   O         [O   O   O             
+    ///      .   .   .           O   O   O          O   O   O              
+    ///      .   .   .           O   O   O]         O   O   O
+    ///                                             X   X   X] // ERROR          
+    /// ```
+    ///
+    pub fn rows(rows: R) -> Self {
+        Extract { rows, columns: .. }
+    }
+}
+
+impl<C> Extract<RangeFull, C>
+where
+    C: RangeBounds<usize>,
+{
+    /// Returns a new [Table] that reflects a segment of the referenced [Table]
+    ///
+    /// The segment is defined by [RangeBounds<usize>] for Columns
+    ///
+    /// ```rust,no_run
+    /// Extract::columns(1..3);
+    /// ```
+    ///
+    /// # Range
+    ///
+    /// A [RangeBounds] argument can be less than or equal to the shape of a [Table]
+    ///
+    /// If a [RangeBounds] argument is malformed or too large the thread will panic
+    ///
+    /// ```
+    /// // Empty                   Full                  Out of bounds
+    ///    Extract::columns(0..0)  Extract::columns(..)  Extract::columns(0..4)
+    ///    [].   .   .             [O   O   O            [O   O   O   X          
+    ///      .   .   .              O   O   O             O   O   O   X          
+    ///      .   .   .              O   O   O]            O   O   O   X] // ERROR
+    /// ```
+    ///
+    pub fn columns(columns: C) -> Self {
+        Extract { rows: .., columns }
+    }
+}
+
+impl<R, C> TableOption for Extract<R, C>
+where
+    R: RangeBounds<usize> + Clone,
+    C: RangeBounds<usize> + Clone,
+{
+    fn change(&mut self, grid: &mut papergrid::Grid) {
+        *grid = grid.extract(self.rows.clone(), self.columns.clone());
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 7f4542e..0b968a1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -156,6 +156,7 @@ use std::fmt;
 mod alignment;
 mod concat;
 mod disable;
+mod extract;
 mod formating;
 mod highlight;
 mod object;
@@ -171,8 +172,8 @@ pub mod display;
 pub mod style;
 
 pub use crate::{
-    alignment::*, concat::*, disable::*, formating::*, highlight::*, object::*, padding::*,
-    panel::*, rotate::*, span::*, style::Style, table::*, width::*,
+    alignment::*, concat::*, disable::*, extract::*, formating::*, highlight::*, object::*,
+    padding::*, panel::*, rotate::*, span::*, style::Style, table::*, width::*,
 };
 
 pub use tabled_derive::Tabled;


Test patch:
diff --git a/papergrid/tests/extract.rs b/papergrid/tests/extract.rs
index 7d87074..64fc41d 100644
--- a/papergrid/tests/extract.rs
+++ b/papergrid/tests/extract.rs
@@ -68,7 +68,7 @@ fn extract_top_test() {
 fn extract_bottom_test() {
     let grid = util::new_grid::<3, 3>();
 
-    let grid = grid.extract(2.., ..);
+    let grid = grid.extract(.., ..);
 
     assert_eq!(
         grid.to_string(),
diff --git a/tests/extract_test.rs b/tests/extract_test.rs
new file mode 100644
index 0000000..a9de1a7
--- /dev/null
+++ b/tests/extract_test.rs
@@ -0,0 +1,256 @@
+use crate::util::create_vector;
+use tabled::{Alignment, Extract, Format, Full, Modify, Padding, Table};
+
+mod util;
+
+#[test]
+fn extract_segment_full_test() {
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Full).with(Padding::new(3, 1, 0, 0)))
+        .with(Modify::new(Full).with(Format(|s| format!("[{}]", s))))
+        .with(Extract::segment(.., ..))
+        .to_string();
+
+    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",
+    );
+
+    assert_eq!(table, expected);
+}
+
+#[test]
+fn extract_segment_skip_top_row_test() {
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Full).with(Padding::new(3, 1, 0, 0)))
+        .with(Modify::new(Full).with(Format(|s| format!("[{}]", s))))
+        .with(Extract::segment(1.., ..))
+        .to_string();
+
+    let expected = concat!(
+        "+-------+---------+---------+---------+\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!(table, expected);
+}
+
+#[test]
+fn extract_segment_skip_left_col_test() {
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Full).with(Padding::new(3, 1, 0, 0)))
+        .with(Modify::new(Full).with(Format(|s| format!("[{}]", s))))
+        .with(Extract::segment(.., 1..))
+        .to_string();
+
+    let expected = concat!(
+        "+--------------+--------------+--------------+\n",
+        "|   [column 0] |   [column 1] |   [column 2] |\n",
+        "+--------------+--------------+--------------+\n",
+        "|   [0-0]      |   [0-1]      |   [0-2]      |\n",
+        "+--------------+--------------+--------------+\n",
+        "|   [1-0]      |   [1-1]      |   [1-2]      |\n",
+        "+--------------+--------------+--------------+\n",
+        "|   [2-0]      |   [2-1]      |   [2-2]      |\n",
+        "+--------------+--------------+--------------+\n",
+    );
+
+    assert_eq!(table, expected);
+}
+
+#[test]
+fn extract_segment_bottom_right_square_test() {
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Full).with(Padding::new(3, 1, 0, 0)))
+        .with(Modify::new(Full).with(Format(|s| format!("[{}]", s))))
+        .with(Extract::segment(2.., 2..))
+        .to_string();
+
+    let expected = concat!(
+        "+---------+---------+\n",
+        "|   [1-1] |   [1-2] |\n",
+        "+---------+---------+\n",
+        "|   [2-1] |   [2-2] |\n",
+        "+---------+---------+\n",
+    );
+
+    assert_eq!(table, expected);
+}
+
+#[test]
+fn extract_segment_middle_section_test() {
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Full).with(Padding::new(3, 1, 0, 0)))
+        .with(Modify::new(Full).with(Format(|s| format!("[{}]", s))))
+        .with(Extract::segment(1..3, 1..))
+        .to_string();
+
+    let expected = concat!(
+        "+---------+---------+---------+\n",
+        "|   [0-0] |   [0-1] |   [0-2] |\n",
+        "+---------+---------+---------+\n",
+        "|   [1-0] |   [1-1] |   [1-2] |\n",
+        "+---------+---------+---------+\n",
+    );
+
+    assert_eq!(table, expected);
+}
+
+#[test]
+fn extract_segment_empty_test() {
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Full).with(Padding::new(3, 1, 0, 0)))
+        .with(Modify::new(Full).with(Format(|s| format!("[{}]", s))))
+        .with(Extract::segment(1..1, 1..1))
+        .to_string();
+
+    assert_eq!(table, "");
+}
+
+#[test]
+fn extract_rows_full_test() {
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Full).with(Padding::new(3, 1, 0, 0)))
+        .with(Modify::new(Full).with(Format(|s| format!("[{}]", s))))
+        .with(Extract::rows(..))
+        .to_string();
+
+    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",
+    );
+
+    assert_eq!(table, expected);
+}
+
+#[test]
+fn extract_rows_empty_test() {
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Full).with(Padding::new(3, 1, 0, 0)))
+        .with(Modify::new(Full).with(Format(|s| format!("[{}]", s))))
+        .with(Extract::rows(0..0))
+        .to_string();
+
+    assert_eq!(table, "");
+}
+
+#[test]
+fn extract_rows_partial_view_test() {
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Full).with(Padding::new(3, 1, 0, 0)))
+        .with(Modify::new(Full).with(Format(|s| format!("[{}]", s))))
+        .with(Extract::rows(0..=2))
+        .to_string();
+
+    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",
+    );
+
+    assert_eq!(table, expected);
+}
+
+#[test]
+fn extract_columns_full_test() {
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Full).with(Padding::new(3, 1, 0, 0)))
+        .with(Modify::new(Full).with(Format(|s| format!("[{}]", s))))
+        .with(Extract::columns(..))
+        .to_string();
+
+    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",
+    );
+
+    assert_eq!(table, expected);
+}
+
+#[test]
+fn extract_columns_empty_test() {
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Full).with(Padding::new(3, 1, 0, 0)))
+        .with(Modify::new(Full).with(Format(|s| format!("[{}]", s))))
+        .with(Extract::columns(0..0))
+        .to_string();
+
+    assert_eq!(table, "");
+}
+
+#[test]
+fn extract_columns_partial_view_test() {
+    let data = create_vector::<3, 3>();
+    let table = Table::new(&data)
+        .with(Modify::new(Full).with(Alignment::left()))
+        .with(Modify::new(Full).with(Padding::new(3, 1, 0, 0)))
+        .with(Modify::new(Full).with(Format(|s| format!("[{}]", s))))
+        .with(Extract::columns(0..2))
+        .to_string();
+
+    let expected = concat!(
+        "+-------+--------------+\n",
+        "|   [N] |   [column 0] |\n",
+        "+-------+--------------+\n",
+        "|   [0] |   [0-0]      |\n",
+        "+-------+--------------+\n",
+        "|   [1] |   [1-0]      |\n",
+        "+-------+--------------+\n",
+        "|   [2] |   [2-0]      |\n",
+        "+-------+--------------+\n",
+    );
+
+    assert_eq!(table, expected);
+}