tabled/features/
extract.rs

1//! This module contains an [`Extract`] structure which is used to
2//! obtain an ordinary segment from the [`Table`].
3//!
4//! There's a similar structure [`Highlight`] which does a highlighting a of segments.
5//!
6//! [`Table`]: crate::Table
7//! [`Highlight`]: crate::Highlight
8
9use std::ops::{RangeBounds, RangeFull};
10
11use papergrid::records::{Records, Resizable};
12
13use crate::{object::bounds_to_usize, Table, TableOption};
14
15/// Returns a new [`Table`] that reflects a segment of the referenced [`Table`]
16///
17/// # Example
18///
19/// ```
20/// use tabled::{Table, format::Format, object::Rows, Modify, Extract};
21///
22/// let data = vec![
23///     (0, "Grodno", true),
24///     (1, "Minsk", true),
25///     (2, "Hamburg", false),
26///     (3, "Brest", true),
27/// ];
28///
29/// let table = Table::new(&data)
30///                .with(Modify::new(Rows::new(1..)).with(Format::new(|s| format!(": {} :", s))))
31///                .with(Extract::segment(1..=2, 1..))
32///                .to_string();
33///
34/// assert_eq!(table, "+------------+----------+\n\
35///                    | : Grodno : | : true : |\n\
36///                    +------------+----------+\n\
37///                    | : Minsk :  | : true : |\n\
38///                    +------------+----------+");
39/// ```
40///
41/// [`Table`]: crate::Table
42#[derive(Debug)]
43pub struct Extract<R, C> {
44    rows: R,
45    columns: C,
46}
47
48impl<R, C> Extract<R, C>
49where
50    R: RangeBounds<usize>,
51    C: RangeBounds<usize>,
52{
53    /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`]
54    ///
55    /// ```rust,no_run
56    /// # use tabled::Extract;
57    /// let rows = 1..3;
58    /// let columns = 1..;
59    /// Extract::segment(rows, columns);
60    /// ```
61    ///
62    /// # Range
63    ///
64    /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`]
65    ///
66    /// If a [`RangeBounds`] argument is malformed or too large the thread will panic
67    ///
68    /// ```text
69    /// // Empty                         Full                      Out of bounds
70    ///    Extract::segment(0..0, 0..0)  Extract::segment(.., ..)  Extract::segment(0..1, ..4)
71    ///    [].   .   .                   [O   O   O                [O   O   O  X] //ERROR            
72    ///      .   .   .                    O   O   O                 .   .   .             
73    ///      .   .   .                    O   O   O]                .   .   .          
74    /// ```
75    ///
76    /// [`Table`]: crate::Table
77    pub fn segment(rows: R, columns: C) -> Self {
78        Extract { rows, columns }
79    }
80}
81
82impl<R> Extract<R, RangeFull>
83where
84    R: RangeBounds<usize>,
85{
86    /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`]
87    ///
88    /// The segment is defined by [`RangeBounds`<usize>] for Rows
89    ///
90    /// ```rust,no_run
91    /// # use tabled::Extract;
92    /// Extract::rows(1..3);
93    /// ```
94    ///
95    /// # Range
96    ///
97    /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`]
98    ///
99    /// If a [`RangeBounds`] argument is malformed or too large the thread will panic
100    ///
101    /// ```text
102    /// // Empty                Full               Out of bounds
103    ///    Extract::rows(0..0)  Extract::rows(..)  Extract::rows(0..4)
104    ///    [].   .   .          [O   O   O         [O   O   O             
105    ///      .   .   .           O   O   O          O   O   O              
106    ///      .   .   .           O   O   O]         O   O   O
107    ///                                             X   X   X] // ERROR          
108    /// ```
109    ///
110    /// [`Table`]: crate::Table
111    pub fn rows(rows: R) -> Self {
112        Extract { rows, columns: .. }
113    }
114}
115
116impl<C> Extract<RangeFull, C>
117where
118    C: RangeBounds<usize>,
119{
120    /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`]
121    ///
122    /// The segment is defined by [`RangeBounds`<usize>] for Columns
123    ///
124    /// ```rust,no_run
125    /// # use tabled::Extract;
126    /// Extract::columns(1..3);
127    /// ```
128    ///
129    /// # Range
130    ///
131    /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`]
132    ///
133    /// If a [`RangeBounds`] argument is malformed or too large the thread will panic
134    ///
135    /// ```text
136    /// // Empty                   Full                  Out of bounds
137    ///    Extract::columns(0..0)  Extract::columns(..)  Extract::columns(0..4)
138    ///    [].   .   .             [O   O   O            [O   O   O   X          
139    ///      .   .   .              O   O   O             O   O   O   X          
140    ///      .   .   .              O   O   O]            O   O   O   X] // ERROR
141    /// ```
142    ///
143    /// [`Table`]: crate::Table
144    pub fn columns(columns: C) -> Self {
145        Extract { rows: .., columns }
146    }
147}
148
149impl<R, C, RR> TableOption<RR> for Extract<R, C>
150where
151    R: RangeBounds<usize> + Clone,
152    C: RangeBounds<usize> + Clone,
153    RR: Records + Resizable,
154{
155    fn change(&mut self, table: &mut Table<RR>) {
156        let shape = table.shape();
157        let mut rows = bounds_to_usize(self.rows.start_bound(), self.rows.end_bound(), shape.0);
158        let mut cols = bounds_to_usize(
159            self.columns.start_bound(),
160            self.columns.end_bound(),
161            shape.1,
162        );
163
164        // Cleanup table in case if boundries are exeeded.
165        //
166        // todo: can be optimized by adding a clear() method to Resizable
167        rows.0 = std::cmp::min(rows.0, shape.0);
168        cols.0 = std::cmp::min(cols.0, shape.1);
169
170        extract(table.get_records_mut(), shape, rows, cols);
171    }
172}
173
174/// Returns a new [`Grid`] that reflects a segment of the referenced [`Grid`].
175///
176/// # Example
177///
178/// ```text
179/// grid
180/// +---+---+---+
181/// |0-0|0-1|0-2|
182/// +---+---+---+
183/// |1-0|1-1|1-2|
184/// +---+---+---+
185/// |2-0|2-1|2-2|
186/// +---+---+---+
187///
188/// let rows = ..;
189/// let columns = ..1;
190/// grid.extract(rows, columns)
191///
192/// grid
193/// +---+
194/// |0-0|
195/// +---+
196/// |1-0|
197/// +---+
198/// |2-0|
199/// +---+
200/// ```
201fn extract<R>(
202    records: &mut R,
203    (count_rows, count_cols): (usize, usize),
204    (start_row, end_row): (usize, usize),
205    (start_col, end_col): (usize, usize),
206) where
207    R: Resizable,
208{
209    for (i, row) in (0..start_row).enumerate() {
210        let row = row - i;
211        records.remove_row(row);
212    }
213
214    let count_rows = count_rows - start_row;
215    let end_row = end_row - start_row;
216    for (i, row) in (end_row..count_rows).enumerate() {
217        let row = row - i;
218        records.remove_row(row);
219    }
220
221    for (i, col) in (0..start_col).enumerate() {
222        let col = col - i;
223        records.remove_column(col);
224    }
225
226    let count_cols = count_cols - start_col;
227    let end_col = end_col - start_col;
228    for (i, col) in (end_col..count_cols).enumerate() {
229        let col = col - i;
230        records.remove_column(col);
231    }
232}