Repository: zhiburt/tabled
Pull request #436 (2024-11-04 20:45:21)

tabled/ Add ObjectIterator


Patch:
diff --git a/tabled/src/settings/object/iterator.rs b/tabled/src/settings/object/iterator.rs
new file mode 100644
index 0000000..c176da5
--- /dev/null
+++ b/tabled/src/settings/object/iterator.rs
@@ -0,0 +1,309 @@
+//! 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 {
+        let end = step == 0;
+
+        Self { iter, step, end }
+    }
+}
+
+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 - 1 {
+            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(0), 10, 10),
+            [Row(1), Row(2), Row(3), Row(4)]
+        );
+    }
+
+    #[test]
+    fn test_step_by_iterator() {
+        use Entity::*;
+
+        assert_eq!(cells(Rows::new(1..5).step_by(0), 10, 10), []);
+        assert_eq!(
+            cells(Rows::new(1..5).step_by(1), 10, 10),
+            [Row(1), Row(2), Row(3), Row(4)]
+        );
+        assert_eq!(cells(Rows::new(1..5).step_by(2), 10, 10), [Row(1), Row(3)]);
+
+        assert_eq!(
+            cells(Columns::new(5..).step_by(1), 10, 10),
+            [Column(5), Column(6), Column(7), Column(8), Column(9)]
+        );
+
+        assert_eq!(cells((1, 5).step_by(2), 10, 10), [Cell(1, 5)]);
+        assert_eq!(cells((1, 5).step_by(1), 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<_>>()
+    }
+}
diff --git a/tabled/src/settings/object/mod.rs b/tabled/src/settings/object/mod.rs
index 46893c7..bedc5e4 100644
--- a/tabled/src/settings/object/mod.rs
+++ b/tabled/src/settings/object/mod.rs
@@ -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};
@@ -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};
 


Test patch:
diff --git a/tabled/tests/settings/mod.rs b/tabled/tests/settings/mod.rs
index 7c4f3a3..04d6d54 100644
--- a/tabled/tests/settings/mod.rs
+++ b/tabled/tests/settings/mod.rs
@@ -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;
diff --git a/tabled/tests/settings/object_test.rs b/tabled/tests/settings/object_test.rs
new file mode 100644
index 0000000..a39af90
--- /dev/null
+++ b/tabled/tests/settings/object_test.rs
@@ -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(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 "
+);
+
+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 "
+);