1//! The module contains a [`Locator`] trait and implementations for it.
2
3use core::ops::Bound;
4use std::{
5 iter::{self, Once},
6 ops::{Range, RangeBounds},
7};
8
9use crate::{
10 grid::config::Entity,
11 grid::records::{ExactRecords, PeekableRecords, Records},
12 settings::object::{
13 Column, Columns, FirstColumn, FirstRow, LastColumn, LastRow, Object, Row, Rows,
14 },
15};
16
17/// Locator is an interface which searches for a particular thing in the [`Records`],
18/// and returns coordinate of the foundings if any.
19pub trait Locator<Records> {
20 /// A coordinate of the finding.
21 type Coordinate;
22 /// An iterator of the coordinates.
23 /// If it's empty it's considered that nothing is found.
24 type IntoIter: IntoIterator<Item = Self::Coordinate>;
25
26 /// Search for the thing in [`Records`], returning a list of coordinates.
27 fn locate(&mut self, records: Records) -> Self::IntoIter;
28}
29
30impl<B, R> Locator<R> for Columns<B>
31where
32 B: RangeBounds<usize>,
33 R: Records,
34{
35 type Coordinate = usize;
36 type IntoIter = Range<usize>;
37
38 fn locate(&mut self, records: R) -> Self::IntoIter {
39 let range: &B = self.get_range();
40 let max: usize = records.count_columns();
41 let (from: usize, to: usize) = bounds_to_usize(left:range.start_bound(), right:range.end_bound(), count_elements:max);
42
43 from..to
44 }
45}
46
47impl<R> Locator<R> for Column {
48 type Coordinate = usize;
49 type IntoIter = Once<usize>;
50
51 fn locate(&mut self, _: R) -> Self::IntoIter {
52 iter::once((*self).into())
53 }
54}
55
56impl<R> Locator<R> for FirstColumn {
57 type Coordinate = usize;
58 type IntoIter = Once<usize>;
59
60 fn locate(&mut self, _: R) -> Self::IntoIter {
61 iter::once(0)
62 }
63}
64
65impl<R> Locator<R> for LastColumn
66where
67 R: Records,
68{
69 type Coordinate = usize;
70 type IntoIter = Once<usize>;
71
72 fn locate(&mut self, records: R) -> Self::IntoIter {
73 if records.count_columns() > 0 {
74 iter::once(records.count_columns() - 1)
75 } else {
76 iter::once(0)
77 }
78 }
79}
80
81impl<B, R> Locator<R> for Rows<B>
82where
83 R: Records,
84 B: RangeBounds<usize>,
85{
86 type Coordinate = usize;
87 type IntoIter = Range<usize>;
88
89 fn locate(&mut self, records: R) -> Self::IntoIter {
90 let (from: usize, to: usize) = bounds_to_usize(
91 self.get_range().start_bound(),
92 self.get_range().end_bound(),
93 count_elements:records.count_columns(),
94 );
95
96 from..to
97 }
98}
99
100impl<R> Locator<R> for Row {
101 type Coordinate = usize;
102 type IntoIter = Once<usize>;
103
104 fn locate(&mut self, _: R) -> Self::IntoIter {
105 iter::once((*self).into())
106 }
107}
108
109impl<R> Locator<R> for FirstRow {
110 type Coordinate = usize;
111 type IntoIter = Once<usize>;
112
113 fn locate(&mut self, _: R) -> Self::IntoIter {
114 iter::once(0)
115 }
116}
117
118impl<R> Locator<R> for LastRow
119where
120 R: ExactRecords,
121{
122 type Coordinate = usize;
123 type IntoIter = Once<usize>;
124
125 fn locate(&mut self, records: R) -> Self::IntoIter {
126 if records.count_rows() > 0 {
127 iter::once(records.count_rows() - 1)
128 } else {
129 iter::once(0)
130 }
131 }
132}
133
134/// The structure is an implementation of [`Locator`] to search for a column by it's name.
135/// A name is considered be a value in a first row.
136///
137/// So even if in reality there's no header, the first row will be considered to be one.
138#[derive(Debug, Clone, Copy)]
139pub struct ByColumnName<S>(S);
140
141impl<S> ByColumnName<S> {
142 /// Constructs a new object of the structure.
143 pub fn new(text: S) -> Self
144 where
145 S: AsRef<str>,
146 {
147 Self(text)
148 }
149}
150
151impl<R, S> Locator<R> for ByColumnName<S>
152where
153 S: AsRef<str>,
154 R: Records + ExactRecords + PeekableRecords,
155{
156 type Coordinate = usize;
157 type IntoIter = Vec<usize>;
158
159 fn locate(&mut self, records: R) -> Self::IntoIter {
160 // todo: can be optimized by creating Iterator
161 (0..records.count_columns())
162 .filter(|col: &usize| records.get_text((0, *col)) == self.0.as_ref())
163 .collect::<Vec<_>>()
164 }
165}
166
167impl<S, R> Object<R> for ByColumnName<S>
168where
169 S: AsRef<str>,
170 R: Records + PeekableRecords + ExactRecords,
171{
172 type Iter = std::vec::IntoIter<Entity>;
173
174 fn cells(&self, records: &R) -> Self::Iter {
175 // todo: can be optimized by creating Iterator
176 (0..records.count_columns())
177 .filter(|col: &usize| records.get_text((0, *col)) == self.0.as_ref())
178 .map(Entity::Column)
179 .collect::<Vec<_>>()
180 .into_iter()
181 }
182}
183
184fn bounds_to_usize(
185 left: Bound<&usize>,
186 right: Bound<&usize>,
187 count_elements: usize,
188) -> (usize, usize) {
189 match (left, right) {
190 (Bound::Included(x: &usize), Bound::Included(y: &usize)) => (*x, y + 1),
191 (Bound::Included(x: &usize), Bound::Excluded(y: &usize)) => (*x, *y),
192 (Bound::Included(x: &usize), Bound::Unbounded) => (*x, count_elements),
193 (Bound::Unbounded, Bound::Unbounded) => (0, count_elements),
194 (Bound::Unbounded, Bound::Included(y: &usize)) => (0, y + 1),
195 (Bound::Unbounded, Bound::Excluded(y: &usize)) => (0, *y),
196 (Bound::Excluded(_), Bound::Unbounded)
197 | (Bound::Excluded(_), Bound::Included(_))
198 | (Bound::Excluded(_), Bound::Excluded(_)) => {
199 unreachable!("A start bound can't be excluded")
200 }
201 }
202}
203