1 | //! The module contains a [`Location`] trait and implementations for it. |
2 | //! |
3 | //! # Example |
4 | //! |
5 | #![cfg_attr (feature = "derive" , doc = "```" )] |
6 | #![cfg_attr (not(feature = "derive" ), doc = "```ignore" )] |
7 | //! use tabled::{ |
8 | //! settings::{ |
9 | //! location::Locator, |
10 | //! object::{Columns, Object}, |
11 | //! Alignment, Modify, Padding, |
12 | //! }, |
13 | //! Table, Tabled, |
14 | //! }; |
15 | //! |
16 | //! #[derive(Tabled)] |
17 | //! struct Reading { |
18 | //! link: &'static str, |
19 | //! comment: &'static str, |
20 | //! } |
21 | //! |
22 | //! let data = [ |
23 | //! Reading { link: "https://www.gnu.org/software/grub/manual/multiboot/multiboot.html" , comment: "todo" }, |
24 | //! Reading { link: "https://wiki.debian.org/initramfs" , comment: "todo" }, |
25 | //! Reading { link: "http://jdebp.uk/FGA/efi-boot-process.html" , comment: "todo,2" }, |
26 | //! Reading { link: "https://wiki.debian.org/UEFI" , comment: "todo,2" }, |
27 | //! ]; |
28 | //! |
29 | //! let mut table = Table::new(data); |
30 | //! table.with(Padding::zero()); |
31 | //! table.with(Modify::new(Locator::column("link" )).with(Alignment::right())); |
32 | //! table.with(Modify::new(Locator::content("todo" )).with("todo,1" )); |
33 | //! table.with( |
34 | //! Modify::new(Columns::single(1).intersect(Locator::by(|text| text.contains("todo" )))) |
35 | //! .with(Padding::new(4, 0, 0, 0)), |
36 | //! ); |
37 | //! |
38 | //! let output = table.to_string(); |
39 | //! |
40 | //! assert_eq!( |
41 | //! output, |
42 | //! concat!( |
43 | //! "+-----------------------------------------------------------------+----------+ \n" , |
44 | //! "| link|comment | \n" , |
45 | //! "+-----------------------------------------------------------------+----------+ \n" , |
46 | //! "|https://www.gnu.org/software/grub/manual/multiboot/multiboot.html| todo,1| \n" , |
47 | //! "+-----------------------------------------------------------------+----------+ \n" , |
48 | //! "| https://wiki.debian.org/initramfs| todo,1| \n" , |
49 | //! "+-----------------------------------------------------------------+----------+ \n" , |
50 | //! "| http://jdebp.uk/FGA/efi-boot-process.html| todo,2| \n" , |
51 | //! "+-----------------------------------------------------------------+----------+ \n" , |
52 | //! "| https://wiki.debian.org/UEFI| todo,2| \n" , |
53 | //! "+-----------------------------------------------------------------+----------+" , |
54 | //! ), |
55 | //! ); |
56 | //! ``` |
57 | |
58 | // todo: Add .modify method for Table |
59 | |
60 | mod by_column_name; |
61 | mod by_condition; |
62 | mod by_content; |
63 | mod locator; |
64 | |
65 | pub use by_column_name::ByColumnName; |
66 | pub use by_condition::ByCondition; |
67 | pub use by_content::ByContent; |
68 | pub use locator::Locator; |
69 | |
70 | use core::ops::Bound; |
71 | use std::{ |
72 | iter::{self, Once}, |
73 | ops::{Range, RangeBounds}, |
74 | }; |
75 | |
76 | use crate::{ |
77 | grid::records::{ExactRecords, Records}, |
78 | settings::object::{Column, Columns, FirstColumn, FirstRow, LastColumn, LastRow, Row, Rows}, |
79 | }; |
80 | |
81 | /// Location is an interface which searches for a particular thing in the [`Records`], |
82 | /// and returns coordinate of the foundings if any. |
83 | pub trait Location<Records> { |
84 | /// A coordinate of the finding. |
85 | type Coordinate; |
86 | /// An iterator of the coordinates. |
87 | /// If it's empty it's considered that nothing is found. |
88 | type IntoIter: IntoIterator<Item = Self::Coordinate>; |
89 | |
90 | /// Search for the thing in [`Records`], returning a list of coordinates. |
91 | fn locate(&mut self, records: &Records) -> Self::IntoIter; |
92 | } |
93 | |
94 | impl<B, R> Location<R> for Columns<B> |
95 | where |
96 | B: RangeBounds<usize>, |
97 | R: Records, |
98 | { |
99 | type Coordinate = usize; |
100 | type IntoIter = Range<usize>; |
101 | |
102 | fn locate(&mut self, records: &R) -> Self::IntoIter { |
103 | let range: &B = self.get_range(); |
104 | let max: usize = records.count_columns(); |
105 | let (from: usize, to: usize) = bounds_to_usize(left:range.start_bound(), right:range.end_bound(), count_elements:max); |
106 | |
107 | from..to |
108 | } |
109 | } |
110 | |
111 | impl<R> Location<R> for Column { |
112 | type Coordinate = usize; |
113 | type IntoIter = Once<usize>; |
114 | |
115 | fn locate(&mut self, _: &R) -> Self::IntoIter { |
116 | iter::once((*self).into()) |
117 | } |
118 | } |
119 | |
120 | impl<R> Location<R> for FirstColumn { |
121 | type Coordinate = usize; |
122 | type IntoIter = Once<usize>; |
123 | |
124 | fn locate(&mut self, _: &R) -> Self::IntoIter { |
125 | iter::once(0) |
126 | } |
127 | } |
128 | |
129 | impl<R> Location<R> for LastColumn |
130 | where |
131 | R: Records, |
132 | { |
133 | type Coordinate = usize; |
134 | type IntoIter = Once<usize>; |
135 | |
136 | fn locate(&mut self, records: &R) -> Self::IntoIter { |
137 | if records.count_columns() > 0 { |
138 | iter::once(records.count_columns() - 1) |
139 | } else { |
140 | iter::once(0) |
141 | } |
142 | } |
143 | } |
144 | |
145 | impl<B, R> Location<R> for Rows<B> |
146 | where |
147 | R: Records, |
148 | B: RangeBounds<usize>, |
149 | { |
150 | type Coordinate = usize; |
151 | type IntoIter = Range<usize>; |
152 | |
153 | fn locate(&mut self, records: &R) -> Self::IntoIter { |
154 | let (from: usize, to: usize) = bounds_to_usize( |
155 | self.get_range().start_bound(), |
156 | self.get_range().end_bound(), |
157 | count_elements:records.count_columns(), |
158 | ); |
159 | |
160 | from..to |
161 | } |
162 | } |
163 | |
164 | impl<R> Location<R> for Row { |
165 | type Coordinate = usize; |
166 | type IntoIter = Once<usize>; |
167 | |
168 | fn locate(&mut self, _: &R) -> Self::IntoIter { |
169 | iter::once((*self).into()) |
170 | } |
171 | } |
172 | |
173 | impl<R> Location<R> for FirstRow { |
174 | type Coordinate = usize; |
175 | type IntoIter = Once<usize>; |
176 | |
177 | fn locate(&mut self, _: &R) -> Self::IntoIter { |
178 | iter::once(0) |
179 | } |
180 | } |
181 | |
182 | impl<R> Location<R> for LastRow |
183 | where |
184 | R: ExactRecords, |
185 | { |
186 | type Coordinate = usize; |
187 | type IntoIter = Once<usize>; |
188 | |
189 | fn locate(&mut self, records: &R) -> Self::IntoIter { |
190 | if records.count_rows() > 0 { |
191 | iter::once(records.count_rows() - 1) |
192 | } else { |
193 | iter::once(0) |
194 | } |
195 | } |
196 | } |
197 | |
198 | fn bounds_to_usize( |
199 | left: Bound<&usize>, |
200 | right: Bound<&usize>, |
201 | count_elements: usize, |
202 | ) -> (usize, usize) { |
203 | match (left, right) { |
204 | (Bound::Included(x: &usize), Bound::Included(y: &usize)) => (*x, y + 1), |
205 | (Bound::Included(x: &usize), Bound::Excluded(y: &usize)) => (*x, *y), |
206 | (Bound::Included(x: &usize), Bound::Unbounded) => (*x, count_elements), |
207 | (Bound::Unbounded, Bound::Unbounded) => (0, count_elements), |
208 | (Bound::Unbounded, Bound::Included(y: &usize)) => (0, y + 1), |
209 | (Bound::Unbounded, Bound::Excluded(y: &usize)) => (0, *y), |
210 | (Bound::Excluded(_), Bound::Unbounded) |
211 | | (Bound::Excluded(_), Bound::Included(_)) |
212 | | (Bound::Excluded(_), Bound::Excluded(_)) => { |
213 | unreachable!("A start bound can't be excluded" ) |
214 | } |
215 | } |
216 | } |
217 | |
218 | #[cfg (test)] |
219 | mod tests { |
220 | use crate::{ |
221 | grid::config::Entity, |
222 | grid::records::vec_records::CellInfo, |
223 | grid::records::vec_records::VecRecords, |
224 | settings::location::{ByColumnName, ByCondition, ByContent}, |
225 | settings::object::Object, |
226 | }; |
227 | |
228 | use Entity::*; |
229 | |
230 | #[test ] |
231 | fn object_by_column_name_test() { |
232 | let data = [ |
233 | vec![vec![1, 2, 3], vec![1, 2, 3], vec![1, 2, 3]], |
234 | vec![vec![1, 2, 3], vec![1, 1, 3], vec![1, 2, 1]], |
235 | vec![vec![1, 1, 3], vec![1, 1, 3], vec![1, 1, 1]], |
236 | vec![vec![1, 1, 1], vec![1, 1, 3], vec![1, 1, 1]], |
237 | vec![vec![0, 1, 1], vec![1, 1, 3], vec![1, 1, 1]], |
238 | vec![vec![0, 0, 0], vec![1, 1, 3], vec![1, 1, 1]], |
239 | ]; |
240 | |
241 | assert_eq!(cells(by_colname("1" ), &data[0]), [Column(0)]); |
242 | assert_eq!(cells(by_colname("1" ), &data[1]), [Column(0)]); |
243 | assert_eq!(cells(by_colname("1" ), &data[2]), [Column(0), Column(1)]); |
244 | assert_eq!( |
245 | cells(by_colname("1" ), &data[3]), |
246 | [Column(0), Column(1), Column(2)] |
247 | ); |
248 | assert_eq!(cells(by_colname("1" ), &data[4]), [Column(1), Column(2)]); |
249 | assert_eq!(cells(by_colname("1" ), &data[5]), []); |
250 | } |
251 | |
252 | #[test ] |
253 | fn object_by_content_test() { |
254 | let data = [ |
255 | vec![vec![1, 2, 3], vec![1, 2, 3], vec![1, 2, 3]], |
256 | vec![vec![1, 2, 3], vec![1, 1, 3], vec![1, 2, 1]], |
257 | vec![vec![1, 1, 3], vec![1, 1, 3], vec![1, 1, 1]], |
258 | vec![vec![1, 1, 1], vec![1, 1, 3], vec![1, 1, 1]], |
259 | vec![vec![0, 1, 1], vec![1, 1, 3], vec![1, 1, 1]], |
260 | vec![vec![0, 0, 0], vec![1, 1, 3], vec![1, 1, 1]], |
261 | ]; |
262 | |
263 | assert_eq!(cells(by_content("1" ), &[]), []); |
264 | assert_eq!(cells(by_content("1" ), &[vec![], vec![], vec![]]), []); |
265 | assert_eq!( |
266 | cells(by_content("1" ), &data[0]), |
267 | [Cell(0, 0), Cell(1, 0), Cell(2, 0)] |
268 | ); |
269 | assert_eq!( |
270 | cells(by_content("1" ), &data[1]), |
271 | [Cell(0, 0), Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 2)] |
272 | ); |
273 | assert_eq!( |
274 | cells(by_content("1" ), &data[2]), |
275 | [ |
276 | Cell(0, 0), |
277 | Cell(0, 1), |
278 | Cell(1, 0), |
279 | Cell(1, 1), |
280 | Cell(2, 0), |
281 | Cell(2, 1), |
282 | Cell(2, 2) |
283 | ] |
284 | ); |
285 | assert_eq!( |
286 | cells(by_content("1" ), &data[3]), |
287 | [ |
288 | Cell(0, 0), |
289 | Cell(0, 1), |
290 | Cell(0, 2), |
291 | Cell(1, 0), |
292 | Cell(1, 1), |
293 | Cell(2, 0), |
294 | Cell(2, 1), |
295 | Cell(2, 2) |
296 | ] |
297 | ); |
298 | assert_eq!( |
299 | cells(by_content("1" ), &data[4]), |
300 | [ |
301 | Cell(0, 1), |
302 | Cell(0, 2), |
303 | Cell(1, 0), |
304 | Cell(1, 1), |
305 | Cell(2, 0), |
306 | Cell(2, 1), |
307 | Cell(2, 2) |
308 | ] |
309 | ); |
310 | assert_eq!( |
311 | cells(by_content("1" ), &data[5]), |
312 | [Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 1), Cell(2, 2)] |
313 | ); |
314 | } |
315 | |
316 | #[test ] |
317 | fn object_by_condition_test() { |
318 | let data = [ |
319 | vec![vec![1, 2, 3], vec![1, 2, 3], vec![1, 2, 3]], |
320 | vec![vec![1, 2, 3], vec![1, 1, 3], vec![1, 2, 1]], |
321 | vec![vec![1, 1, 3], vec![1, 1, 3], vec![1, 1, 1]], |
322 | vec![vec![1, 1, 1], vec![1, 1, 3], vec![1, 1, 1]], |
323 | vec![vec![0, 1, 1], vec![1, 1, 3], vec![1, 1, 1]], |
324 | vec![vec![0, 0, 0], vec![1, 1, 3], vec![1, 1, 1]], |
325 | ]; |
326 | |
327 | assert_eq!(cells(by_cond("1" ), &[]), []); |
328 | assert_eq!(cells(by_cond("1" ), &[vec![], vec![], vec![]]), []); |
329 | assert_eq!( |
330 | cells(by_cond("1" ), &data[0]), |
331 | [Cell(0, 0), Cell(1, 0), Cell(2, 0)] |
332 | ); |
333 | assert_eq!( |
334 | cells(by_cond("1" ), &data[1]), |
335 | [Cell(0, 0), Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 2)] |
336 | ); |
337 | assert_eq!( |
338 | cells(by_cond("1" ), &data[2]), |
339 | [ |
340 | Cell(0, 0), |
341 | Cell(0, 1), |
342 | Cell(1, 0), |
343 | Cell(1, 1), |
344 | Cell(2, 0), |
345 | Cell(2, 1), |
346 | Cell(2, 2) |
347 | ] |
348 | ); |
349 | assert_eq!( |
350 | cells(by_cond("1" ), &data[3]), |
351 | [ |
352 | Cell(0, 0), |
353 | Cell(0, 1), |
354 | Cell(0, 2), |
355 | Cell(1, 0), |
356 | Cell(1, 1), |
357 | Cell(2, 0), |
358 | Cell(2, 1), |
359 | Cell(2, 2) |
360 | ] |
361 | ); |
362 | assert_eq!( |
363 | cells(by_cond("1" ), &data[4]), |
364 | [ |
365 | Cell(0, 1), |
366 | Cell(0, 2), |
367 | Cell(1, 0), |
368 | Cell(1, 1), |
369 | Cell(2, 0), |
370 | Cell(2, 1), |
371 | Cell(2, 2) |
372 | ] |
373 | ); |
374 | assert_eq!( |
375 | cells(by_cond("1" ), &data[5]), |
376 | [Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 1), Cell(2, 2)] |
377 | ); |
378 | } |
379 | |
380 | fn by_colname(text: &str) -> ByColumnName<&str> { |
381 | ByColumnName::new(text) |
382 | } |
383 | |
384 | fn by_content(text: &str) -> ByContent<&str> { |
385 | ByContent::new(text) |
386 | } |
387 | |
388 | fn by_cond(text: &'static str) -> ByCondition<impl Fn(&str) -> bool> { |
389 | ByCondition::new(move |content| content == text) |
390 | } |
391 | |
392 | fn cells<O>(o: O, data: &[Vec<usize>]) -> Vec<Entity> |
393 | where |
394 | O: Object<VecRecords<CellInfo<String>>>, |
395 | { |
396 | let data = data |
397 | .iter() |
398 | .map(|row| { |
399 | row.iter() |
400 | .map(|n| n.to_string()) |
401 | .map(CellInfo::new) |
402 | .collect() |
403 | }) |
404 | .collect(); |
405 | |
406 | let records = VecRecords::new(data); |
407 | o.cells(&records).collect::<Vec<_>>() |
408 | } |
409 | } |
410 | |