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
60mod by_column_name;
61mod by_condition;
62mod by_content;
63mod locator;
64
65pub use by_column_name::ByColumnName;
66pub use by_condition::ByCondition;
67pub use by_content::ByContent;
68pub use locator::Locator;
69
70use core::ops::Bound;
71use std::{
72 iter::{self, Once},
73 ops::{Range, RangeBounds},
74};
75
76use 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.
83pub 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
94impl<B, R> Location<R> for Columns<B>
95where
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
111impl<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
120impl<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
129impl<R> Location<R> for LastColumn
130where
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
145impl<B, R> Location<R> for Rows<B>
146where
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
164impl<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
173impl<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
182impl<R> Location<R> for LastRow
183where
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
198fn 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)]
219mod 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