Skip to content

Commit

Permalink
tabled/ Add ObjectIterator
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiburt committed Nov 4, 2024
1 parent 035cdb1 commit b6b35bd
Show file tree
Hide file tree
Showing 4 changed files with 362 additions and 0 deletions.
303 changes: 303 additions & 0 deletions tabled/src/settings/object/iterator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
//! This module contains an [`ObjectIterator`].
use core::marker::PhantomData;

use crate::grid::config::Entity;
use crate::settings::object::Object;

/// A utility trait helps to modify an [`Object`],
/// by various functions.
pub trait ObjectIterator<R>: Object<R> {
/// Skip N entities.
fn skip(self, n: usize) -> SkipObject<Self, R>
where
Self: Sized,
{
SkipObject::new(self, n)
}

/// Make a step for an iteration of entities.
fn step_by(self, n: usize) -> StepByObject<Self, R>
where
Self: Sized,
{
StepByObject::new(self, n)
}

/// Use a filter while iterating over entities.
fn filter<F>(self, predicate: F) -> FilterObject<Self, F, R>
where
Self: Sized,
F: Fn(Entity) -> bool,
{
FilterObject::new(self, predicate)
}
}

impl<T, R> ObjectIterator<R> for T where T: Object<R> {}

/// Skip object for any [`Object`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct SkipObject<O, R> {
obj: O,
n: usize,
_records: PhantomData<R>,
}

impl<O, R> SkipObject<O, R> {
fn new(obj: O, n: usize) -> Self {
Self {
obj,
n,
_records: PhantomData,
}
}
}

impl<O, R> Object<R> for SkipObject<O, R>
where
O: Object<R>,
{
type Iter = SkipObjectIter<O::Iter>;

fn cells(&self, records: &R) -> Self::Iter {
SkipObjectIter::new(self.obj.cells(records), self.n)
}
}

/// Skip object iterator for any [`Object`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct SkipObjectIter<I> {
iter: I,
n: usize,
}

impl<I> SkipObjectIter<I> {
fn new(iter: I, n: usize) -> Self {
Self { iter, n }
}
}

impl<I> Iterator for SkipObjectIter<I>
where
I: Iterator,
{
type Item = I::Item;

fn next(&mut self) -> Option<Self::Item> {
while self.n > 0 {
self.n -= 1;
let _ = self.iter.next()?;
}

self.iter.next()
}
}

/// Step object for any [`Object`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct StepByObject<O, R> {
obj: O,
n: usize,
_records: PhantomData<R>,
}

impl<O, R> StepByObject<O, R> {
fn new(obj: O, n: usize) -> Self {
Self {
obj,
n,
_records: PhantomData,
}
}
}

impl<O, R> Object<R> for StepByObject<O, R>
where
O: Object<R>,
{
type Iter = StepByObjectIter<O::Iter>;

fn cells(&self, records: &R) -> Self::Iter {
StepByObjectIter::new(self.obj.cells(records), self.n)
}
}

/// Step object iterator for any [`Object`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct StepByObjectIter<I> {
iter: I,
step: usize,
end: bool,
}

impl<I> StepByObjectIter<I> {
fn new(iter: I, step: usize) -> Self {
Self {
iter,
step,
end: false,
}
}
}

impl<I> Iterator for StepByObjectIter<I>
where
I: Iterator,
{
type Item = I::Item;

fn next(&mut self) -> Option<Self::Item> {
if self.end {
return None;
}

let item = self.iter.next();
let _ = item.as_ref()?;

for _ in 0..self.step {
let next = self.iter.next();
if next.is_none() {
self.end = true;
break;
}
}

item
}
}

/// Filter object for any [`Object`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct FilterObject<O, F, R> {
obj: O,
f: F,
_records: PhantomData<R>,
}

impl<O, F, R> FilterObject<O, F, R> {
fn new(obj: O, f: F) -> Self {
Self {
obj,
f,
_records: PhantomData,
}
}
}

impl<O, R, F> Object<R> for FilterObject<O, F, R>
where
O: Object<R>,
F: Fn(Entity) -> bool + Clone,
{
type Iter = FilterObjectIter<O::Iter, F>;

fn cells(&self, records: &R) -> Self::Iter {
FilterObjectIter::new(self.obj.cells(records), self.f.clone())
}
}

/// Filter object iterator for any [`Object`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct FilterObjectIter<I, F> {
iter: I,
f: F,
}

impl<I, F> FilterObjectIter<I, F> {
fn new(iter: I, f: F) -> Self {
Self { iter, f }
}
}

impl<I, F> Iterator for FilterObjectIter<I, F>
where
I: Iterator<Item = Entity>,
F: Fn(Entity) -> bool,
{
type Item = I::Item;

fn next(&mut self) -> Option<Self::Item> {
loop {
match self.iter.next() {
Some(item) => {
if (self.f)(item) {
return Some(item);
}
}
None => return None,
}
}
}
}

#[cfg(test)]
mod tests {
use crate::{
grid::records::vec_records::VecRecords,
settings::object::{Columns, Rows},
};

use super::*;

#[test]
fn test_skip_iterator() {
use Entity::*;

assert_eq!(
cells(Rows::new(1..5).skip(1), 10, 10),
[Row(2), Row(3), Row(4)]
);

assert_eq!(
cells(Columns::new(5..).skip(1), 10, 10),
[Column(6), Column(7), Column(8), Column(9)]
);

assert_eq!(cells((1, 5).skip(1), 10, 10), []);
assert_eq!(cells((1, 5).skip(0), 10, 10), [Cell(1, 5)]);

assert_eq!(cells(Rows::new(1..5).skip(00), 10, 10), []);
}

#[test]
fn test_step_by_iterator() {
use Entity::*;

assert_eq!(cells(Rows::new(1..5).step_by(1), 10, 10), [Row(1), Row(3)]);

assert_eq!(
cells(Columns::new(5..).step_by(0), 10, 10),
[Column(5), Column(6), Column(7), Column(8), Column(9)]
);

assert_eq!(cells((1, 5).step_by(1), 10, 10), [Cell(1, 5)]);
assert_eq!(cells((1, 5).step_by(0), 10, 10), [Cell(1, 5)]);

assert_eq!(cells(Rows::new(1..5).step_by(100), 10, 10), [Row(1)]);
}

#[test]
fn test_filter_iterator() {
use Entity::*;

assert_eq!(
cells(Rows::new(1..5).filter(|i| matches!(i, Row(3))), 10, 10),
[Row(3)]
);
assert_eq!(cells(Rows::new(1..5).filter(|_| false), 10, 10), []);
assert_eq!(
cells(Rows::new(1..5).filter(|_| true), 10, 10),
[Row(1), Row(2), Row(3), Row(4)]
);
}

fn cells<O>(o: O, count_rows: usize, count_cols: usize) -> Vec<Entity>
where
O: Object<VecRecords<String>>,
{
let data = vec![vec![String::default(); count_cols]; count_rows];
let records = VecRecords::new(data);
o.cells(&records).collect::<Vec<_>>()
}
}
6 changes: 6 additions & 0 deletions tabled/src/settings/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
mod cell;
mod columns;
mod frame;
mod iterator;
mod rows;
mod segment;

pub(crate) mod util;

use std::{collections::HashSet, marker::PhantomData};
Expand All @@ -22,6 +24,10 @@ use crate::{
pub use cell::{Cell, EntityOnce};
pub use columns::{Column, Columns, ColumnsIter, FirstColumn, LastColumn, LastColumnOffset};
pub use frame::{Frame, FrameIter};
pub use iterator::{
FilterObject, FilterObjectIter, ObjectIterator, SkipObject, SkipObjectIter, StepByObject,
StepByObjectIter,
};
pub use rows::{FirstRow, LastRow, LastRowOffset, Row, Rows, RowsIter};
pub use segment::{SectorIter, Segment, SegmentAll};

Expand Down
1 change: 1 addition & 0 deletions tabled/tests/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod highlingt_test;
mod layout_test;
mod margin_test;
mod merge_test;
mod object_test;
mod padding_test;
mod panel_test;
mod render_settings;
Expand Down
52 changes: 52 additions & 0 deletions tabled/tests/settings/object_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#![cfg(feature = "std")]

use tabled::grid::config::Entity;
use tabled::settings::{
object::{Columns, Object, ObjectIterator, Segment},
Alignment, Style,
};

use crate::matrix::Matrix;
use testing_table::test_table;

// todo: Columns::all()

test_table!(
skip,
Matrix::new(3, 3).with(Style::psql()).modify(Columns::new(..).not(Columns::first()).skip(2), Alignment::right()),
" N | column 0 | column 1 | column 2 "
"---+----------+----------+----------"
" 0 | 0-0 | 0-1 | 0-2 "
" 1 | 1-0 | 1-1 | 1-2 "
" 2 | 2-0 | 2-1 | 2-2 "
);

test_table!(
skip_segment_all,
Matrix::new(3, 3).with(Style::psql()).modify(Segment::all().skip(1), Alignment::right()),
" N | column 0 | column 1 | column 2 "
"---+----------+----------+----------"
" 0 | 0-0 | 0-1 | 0-2 "
" 1 | 1-0 | 1-1 | 1-2 "
" 2 | 2-0 | 2-1 | 2-2 "
);

test_table!(
step_by,
Matrix::new(3, 3).with(Style::psql()).modify(Columns::new(..).step_by(2), Alignment::right()),
" N | column 0 | column 1 | column 2 "
"---+----------+----------+----------"
" 0 | 0-0 | 0-1 | 0-2 "
" 1 | 1-0 | 1-1 | 1-2 "
" 2 | 2-0 | 2-1 | 2-2 "
);

test_table!(
filter,
Matrix::new(3, 3).with(Style::psql()).modify(Columns::new(..).filter(|e| matches!(e, Entity::Column(1 | 3))), Alignment::right()),
" N | column 0 | column 1 | column 2 "
"---+----------+----------+----------"
" 0 | 0-0 | 0-1 | 0-2 "
" 1 | 1-0 | 1-1 | 1-2 "
" 2 | 2-0 | 2-1 | 2-2 "
);

0 comments on commit b6b35bd

Please sign in to comment.