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