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, D, Cfg> 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: usize = records.count_rows();
160 let count_columns: usize = records.count_columns();
161
162 let mut rows: (usize, usize) = bounds_to_usize(self.rows.start_bound(), self.rows.end_bound(), count_elements:count_rows);
163 let mut cols: (usize, usize) = bounds_to_usize(
164 self.columns.start_bound(),
165 self.columns.end_bound(),
166 count_elements: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(v1:rows.0, v2:count_rows);
173 cols.0 = min(v1:cols.0, v2: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: usize, count_cols: usize): (usize, usize),
209 (start_row: usize, end_row: usize): (usize, usize),
210 (start_col: usize, end_col: usize): (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: &usize), Bound::Included(y: &usize)) => (*x, y + 1),
246 (Bound::Included(x: &usize), Bound::Excluded(y: &usize)) => (*x, *y),
247 (Bound::Included(x: &usize), Bound::Unbounded) => (*x, count_elements),
248 (Bound::Unbounded, Bound::Unbounded) => (0, count_elements),
249 (Bound::Unbounded, Bound::Included(y: &usize)) => (0, y + 1),
250 (Bound::Unbounded, Bound::Excluded(y: &usize)) => (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}
258