blob: b48391bb79b537c5d925a5ee6ff1d6f0ad6614a9 [file] [log] [blame]
//! The module contains a [`Locator`] trait and implementations for it.
use core::ops::Bound;
use std::{
iter::{self, Once},
ops::{Range, RangeBounds},
};
use crate::{
grid::config::Entity,
grid::records::{ExactRecords, PeekableRecords, Records},
settings::object::{
Column, Columns, FirstColumn, FirstRow, LastColumn, LastRow, Object, Row, Rows,
},
};
/// Locator is an interface which searches for a particular thing in the [`Records`],
/// and returns coordinate of the foundings if any.
pub trait Locator<Records> {
/// A coordinate of the finding.
type Coordinate;
/// An iterator of the coordinates.
/// If it's empty it's considered that nothing is found.
type IntoIter: IntoIterator<Item = Self::Coordinate>;
/// Search for the thing in [`Records`], returning a list of coordinates.
fn locate(&mut self, records: Records) -> Self::IntoIter;
}
impl<B, R> Locator<R> for Columns<B>
where
B: RangeBounds<usize>,
R: Records,
{
type Coordinate = usize;
type IntoIter = Range<usize>;
fn locate(&mut self, records: R) -> Self::IntoIter {
let range = self.get_range();
let max = records.count_columns();
let (from, to) = bounds_to_usize(range.start_bound(), range.end_bound(), max);
from..to
}
}
impl<R> Locator<R> for Column {
type Coordinate = usize;
type IntoIter = Once<usize>;
fn locate(&mut self, _: R) -> Self::IntoIter {
iter::once((*self).into())
}
}
impl<R> Locator<R> for FirstColumn {
type Coordinate = usize;
type IntoIter = Once<usize>;
fn locate(&mut self, _: R) -> Self::IntoIter {
iter::once(0)
}
}
impl<R> Locator<R> for LastColumn
where
R: Records,
{
type Coordinate = usize;
type IntoIter = Once<usize>;
fn locate(&mut self, records: R) -> Self::IntoIter {
if records.count_columns() > 0 {
iter::once(records.count_columns() - 1)
} else {
iter::once(0)
}
}
}
impl<B, R> Locator<R> for Rows<B>
where
R: Records,
B: RangeBounds<usize>,
{
type Coordinate = usize;
type IntoIter = Range<usize>;
fn locate(&mut self, records: R) -> Self::IntoIter {
let (from, to) = bounds_to_usize(
self.get_range().start_bound(),
self.get_range().end_bound(),
records.count_columns(),
);
from..to
}
}
impl<R> Locator<R> for Row {
type Coordinate = usize;
type IntoIter = Once<usize>;
fn locate(&mut self, _: R) -> Self::IntoIter {
iter::once((*self).into())
}
}
impl<R> Locator<R> for FirstRow {
type Coordinate = usize;
type IntoIter = Once<usize>;
fn locate(&mut self, _: R) -> Self::IntoIter {
iter::once(0)
}
}
impl<R> Locator<R> for LastRow
where
R: ExactRecords,
{
type Coordinate = usize;
type IntoIter = Once<usize>;
fn locate(&mut self, records: R) -> Self::IntoIter {
if records.count_rows() > 0 {
iter::once(records.count_rows() - 1)
} else {
iter::once(0)
}
}
}
/// The structure is an implementation of [`Locator`] to search for a column by it's name.
/// A name is considered be a value in a first row.
///
/// So even if in reality there's no header, the first row will be considered to be one.
#[derive(Debug, Clone, Copy)]
pub struct ByColumnName<S>(S);
impl<S> ByColumnName<S> {
/// Constructs a new object of the structure.
pub fn new(text: S) -> Self
where
S: AsRef<str>,
{
Self(text)
}
}
impl<R, S> Locator<R> for ByColumnName<S>
where
S: AsRef<str>,
R: Records + ExactRecords + PeekableRecords,
{
type Coordinate = usize;
type IntoIter = Vec<usize>;
fn locate(&mut self, records: R) -> Self::IntoIter {
// todo: can be optimized by creating Iterator
(0..records.count_columns())
.filter(|col| records.get_text((0, *col)) == self.0.as_ref())
.collect::<Vec<_>>()
}
}
impl<S, R> Object<R> for ByColumnName<S>
where
S: AsRef<str>,
R: Records + PeekableRecords + ExactRecords,
{
type Iter = std::vec::IntoIter<Entity>;
fn cells(&self, records: &R) -> Self::Iter {
// todo: can be optimized by creating Iterator
(0..records.count_columns())
.filter(|col| records.get_text((0, *col)) == self.0.as_ref())
.map(Entity::Column)
.collect::<Vec<_>>()
.into_iter()
}
}
fn bounds_to_usize(
left: Bound<&usize>,
right: Bound<&usize>,
count_elements: usize,
) -> (usize, usize) {
match (left, right) {
(Bound::Included(x), Bound::Included(y)) => (*x, y + 1),
(Bound::Included(x), Bound::Excluded(y)) => (*x, *y),
(Bound::Included(x), Bound::Unbounded) => (*x, count_elements),
(Bound::Unbounded, Bound::Unbounded) => (0, count_elements),
(Bound::Unbounded, Bound::Included(y)) => (0, y + 1),
(Bound::Unbounded, Bound::Excluded(y)) => (0, *y),
(Bound::Excluded(_), Bound::Unbounded)
| (Bound::Excluded(_), Bound::Included(_))
| (Bound::Excluded(_), Bound::Excluded(_)) => {
unreachable!("A start bound can't be excluded")
}
}
}