tabled/settings/location/
mod.rs

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