|
1 | 1 | use crate::md_elem::elem::*; |
2 | | -use crate::util::vec_utils::ItemRetainer; |
3 | 2 |
|
4 | 3 | mod elem_ref { |
5 | 4 | use super::*; |
6 | | - use crate::util::vec_utils::IndexKeeper; |
7 | 5 |
|
8 | 6 | impl Table { |
9 | 7 | pub fn alignments(&self) -> &[Option<ColumnAlignment>] { |
@@ -36,259 +34,5 @@ mod elem_ref { |
36 | 34 | self.alignments.extend(nones); |
37 | 35 | } |
38 | 36 | } |
39 | | - |
40 | | - pub fn retain_columns_by_header<F, E>(&mut self, mut f: F) -> Result<(), E> |
41 | | - where |
42 | | - F: FnMut(&TableCell) -> Result<bool, E>, |
43 | | - { |
44 | | - let Some(first_row) = self.rows.first() else { |
45 | | - return Ok(()); |
46 | | - }; |
47 | | - let mut keeper_indices = IndexKeeper::new(); |
48 | | - keeper_indices.retain_when(first_row, |_, cell| f(cell))?; |
49 | | - |
50 | | - match keeper_indices.count_keeps() { |
51 | | - 0 => { |
52 | | - // no columns match: clear everything out |
53 | | - self.alignments.clear(); |
54 | | - self.rows.clear(); |
55 | | - } |
56 | | - n if n == first_row.len() => { |
57 | | - // all columns match: no need to go one by one, just return without modifications |
58 | | - } |
59 | | - _ => { |
60 | | - // some columns match: retain those, and discard the rest |
61 | | - self.alignments.retain_with_index(keeper_indices.retain_fn())?; |
62 | | - for row in self.rows.iter_mut() { |
63 | | - row.retain_with_index(keeper_indices.retain_fn())?; |
64 | | - } |
65 | | - } |
66 | | - } |
67 | | - Ok(()) |
68 | | - } |
69 | | - |
70 | | - pub fn retain_rows<F, E>(&mut self, mut f: F) -> Result<(), E> |
71 | | - where |
72 | | - F: FnMut(&TableCell) -> Result<bool, E>, |
73 | | - { |
74 | | - self.rows.retain_with_index(|idx, row| { |
75 | | - if idx == 0 { |
76 | | - return Ok(true); |
77 | | - } |
78 | | - for cell in row { |
79 | | - if f(cell)? { |
80 | | - return Ok(true); |
81 | | - } |
82 | | - } |
83 | | - Ok(false) |
84 | | - }) |
85 | | - } |
86 | | - |
87 | | - pub fn is_empty(&self) -> bool { |
88 | | - // We always keep the first row; but if we then removed all the other rows, this TableSlice is empty. |
89 | | - if self.rows.len() <= 1 { |
90 | | - return true; |
91 | | - } |
92 | | - self.rows.iter().all(Vec::is_empty) |
93 | | - } |
94 | | - } |
95 | | -} |
96 | | - |
97 | | -#[cfg(test)] |
98 | | -mod tests { |
99 | | - use super::*; |
100 | | - |
101 | | - mod tables { |
102 | | - use super::*; |
103 | | - |
104 | | - #[test] |
105 | | - fn retain_col() { |
106 | | - let mut table = new_table(vec![ |
107 | | - vec!["KEEPER a", "header b", "header c"], |
108 | | - vec!["data 1 a", "data 1 b", "data 1 c"], |
109 | | - vec!["data 2 a", "data 2 b", "KEEPER c"], |
110 | | - ]); |
111 | | - table.retain_columns_by_header(cell_matches("KEEPER")).unwrap(); |
112 | | - |
113 | | - // note: "KEEPER" is in the last column, but not in the header; only the header gets |
114 | | - // matched. |
115 | | - assert_eq!(table.alignments, vec![Some(ColumnAlignment::Left)]); |
116 | | - assert_eq!( |
117 | | - table.rows, |
118 | | - vec![vec![cell("KEEPER a")], vec![cell("data 1 a")], vec![cell("data 2 a")],] |
119 | | - ); |
120 | | - } |
121 | | - |
122 | | - #[test] |
123 | | - fn retain_all_columns_on_jagged_normalized_table() { |
124 | | - let mut table = new_table(vec![ |
125 | | - vec!["header a", "header b"], |
126 | | - vec!["data 1 a", "data 1 b", "data 1 c"], |
127 | | - vec!["data 2 a"], |
128 | | - ]); |
129 | | - table.normalize(); |
130 | | - |
131 | | - let mut seen_lines = Vec::with_capacity(3); |
132 | | - table |
133 | | - .retain_columns_by_header(|line| { |
134 | | - seen_lines.push(simple_to_string(line)); |
135 | | - Ok::<_, ()>(true) |
136 | | - }) |
137 | | - .unwrap(); |
138 | | - |
139 | | - // normalization |
140 | | - assert_eq!( |
141 | | - table.alignments, |
142 | | - vec![Some(ColumnAlignment::Left), Some(ColumnAlignment::Right), None] |
143 | | - ); |
144 | | - assert_eq!( |
145 | | - table.rows, |
146 | | - vec![ |
147 | | - vec![cell("header a"), cell("header b"), Vec::new()], |
148 | | - vec![cell("data 1 a"), cell("data 1 b"), cell("data 1 c")], |
149 | | - vec![cell("data 2 a"), Vec::new(), Vec::new()], |
150 | | - ] |
151 | | - ); |
152 | | - assert_eq!( |
153 | | - seen_lines, |
154 | | - vec!["header a".to_string(), "header b".to_string(), "".to_string(),], |
155 | | - ); |
156 | | - } |
157 | | - |
158 | | - #[test] |
159 | | - fn retain_row() { |
160 | | - let mut table = new_table(vec![ |
161 | | - vec!["header a", "header b", "header c"], |
162 | | - vec!["data 1 a", "data 1 b", "data 1 c"], |
163 | | - vec!["data 2 a", "KEEPER b", "data 2 c"], |
164 | | - ]); |
165 | | - table.retain_rows(cell_matches("KEEPER")).unwrap(); |
166 | | - |
167 | | - assert_eq!( |
168 | | - table.alignments, |
169 | | - vec![ |
170 | | - Some(ColumnAlignment::Left), |
171 | | - Some(ColumnAlignment::Right), |
172 | | - Some(ColumnAlignment::Center), |
173 | | - ] |
174 | | - ); |
175 | | - // note: header row always gets kept |
176 | | - assert_eq!( |
177 | | - table.rows, |
178 | | - vec![ |
179 | | - vec![cell("header a"), cell("header b"), cell("header c")], |
180 | | - vec![cell("data 2 a"), cell("KEEPER b"), cell("data 2 c")], |
181 | | - ] |
182 | | - ); |
183 | | - } |
184 | | - |
185 | | - #[test] |
186 | | - fn retain_rows_on_jagged_normalized_table() { |
187 | | - let mut table = new_table(vec![ |
188 | | - vec!["header a", "header b"], |
189 | | - vec!["data 1 a", "data 1 b", "data 1 c"], |
190 | | - vec!["data 2 a"], |
191 | | - ]); |
192 | | - table.normalize(); |
193 | | - |
194 | | - let mut seen_lines = Vec::with_capacity(3); |
195 | | - // retain only the rows with empty cells. This lets us get around the short-circuiting |
196 | | - // of retain_rows (it short-circuits within each row as soon as it finds a matching |
197 | | - // cell), to validate that the normalization works as expected. |
198 | | - table |
199 | | - .retain_rows(|line| { |
200 | | - seen_lines.push(simple_to_string(line)); |
201 | | - Ok::<_, ()>(line.is_empty()) |
202 | | - }) |
203 | | - .unwrap(); |
204 | | - |
205 | | - // normalization |
206 | | - assert_eq!( |
207 | | - table.alignments, |
208 | | - vec![Some(ColumnAlignment::Left), Some(ColumnAlignment::Right), None] |
209 | | - ); |
210 | | - assert_eq!( |
211 | | - table.rows, |
212 | | - vec![ |
213 | | - vec![cell("header a"), cell("header b"), Vec::new()], |
214 | | - vec![cell("data 2 a"), Vec::new(), Vec::new()], |
215 | | - ] |
216 | | - ); |
217 | | - assert_eq!( |
218 | | - seen_lines, |
219 | | - vec![ |
220 | | - // header row gets skipped, since it's always retained |
221 | | - // second row: |
222 | | - "data 1 a".to_string(), |
223 | | - "data 1 b".to_string(), |
224 | | - "data 1 c".to_string(), |
225 | | - // third row: note that the 2nd cell short-circuits the row, so there is no 3rd |
226 | | - "data 2 a".to_string(), |
227 | | - "".to_string(), |
228 | | - ], |
229 | | - ); |
230 | | - } |
231 | | - |
232 | | - fn cell_matches(substring: &str) -> impl Fn(&TableCell) -> Result<bool, ()> + '_ { |
233 | | - move |line| { |
234 | | - let line_str = format!("{line:?}"); |
235 | | - Ok(line_str.contains(substring)) |
236 | | - } |
237 | | - } |
238 | | - |
239 | | - fn new_table(cells: Vec<Vec<&str>>) -> Table { |
240 | | - let mut rows_iter = cells.iter().peekable(); |
241 | | - let Some(first_row) = rows_iter.peek() else { |
242 | | - return Table { |
243 | | - alignments: vec![], |
244 | | - rows: vec![], |
245 | | - }; |
246 | | - }; |
247 | | - |
248 | | - // for alignments, just cycle [L, R, C]. |
249 | | - let alignments = [ |
250 | | - Some(ColumnAlignment::Left), |
251 | | - Some(ColumnAlignment::Right), |
252 | | - Some(ColumnAlignment::Center), |
253 | | - ] |
254 | | - .iter() |
255 | | - .cycle() |
256 | | - .take(first_row.len()) |
257 | | - .map(ToOwned::to_owned) |
258 | | - .collect(); |
259 | | - let mut rows = Vec::with_capacity(cells.len()); |
260 | | - |
261 | | - for row_strings in rows_iter { |
262 | | - let mut row = Vec::with_capacity(row_strings.len()); |
263 | | - for cell_string in row_strings { |
264 | | - row.push(cell(cell_string)); |
265 | | - } |
266 | | - rows.push(row); |
267 | | - } |
268 | | - |
269 | | - Table { alignments, rows } |
270 | | - } |
271 | | - |
272 | | - fn cell(value: &str) -> TableCell { |
273 | | - vec![Inline::Text(Text { |
274 | | - variant: TextVariant::Plain, |
275 | | - value: value.to_string(), |
276 | | - })] |
277 | | - } |
278 | | - |
279 | | - fn simple_to_string(line: &TableCell) -> String { |
280 | | - let mut result = String::with_capacity(32); |
281 | | - for segment in line { |
282 | | - match segment { |
283 | | - Inline::Text(Text { variant, value }) if variant == &TextVariant::Plain => { |
284 | | - result.push_str(value); |
285 | | - } |
286 | | - _ => { |
287 | | - panic!("test error: unimplemented inline segment in simple_to_string"); |
288 | | - } |
289 | | - } |
290 | | - } |
291 | | - result |
292 | | - } |
293 | 37 | } |
294 | 38 | } |
0 commit comments