1use crate::{Cell, RowSource};
4
5pub fn filter_indices<S, F>(source: &S, mut predicate: F) -> Vec<usize>
10where
11 S: RowSource,
12 F: FnMut(&[Cell]) -> bool,
13{
14 (0..source.row_count())
15 .filter(|&i| {
16 let row = source.row(i);
17 predicate(&row)
18 })
19 .collect()
20}
21
22#[derive(Clone, Debug)]
24pub struct ColumnFilter {
25 pub column: usize,
27 pattern: String,
29}
30
31impl ColumnFilter {
32 pub fn new(column: usize, pattern: impl Into<String>) -> Self {
35 Self {
36 column,
37 pattern: pattern.into().to_lowercase(),
38 }
39 }
40
41 pub fn is_inactive(&self) -> bool {
43 self.pattern.is_empty()
44 }
45
46 pub fn matches(&self, row: &[Cell]) -> bool {
48 if self.is_inactive() {
49 return true;
50 }
51 match row.get(self.column) {
52 Some(cell) => cell.to_string().to_lowercase().contains(&self.pattern),
53 None => false,
54 }
55 }
56
57 pub fn apply<S: RowSource>(&self, source: &S) -> Vec<usize> {
59 filter_indices(source, |row| self.matches(row))
60 }
61}
62
63pub fn apply_all<S: RowSource>(source: &S, filters: &[ColumnFilter]) -> Vec<usize> {
66 filter_indices(source, |row| filters.iter().all(|f| f.matches(row)))
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::ColumnDef;
73
74 struct Data {
75 rows: Vec<Vec<Cell>>,
76 cols: Vec<ColumnDef>,
77 }
78 impl RowSource for Data {
79 fn row_count(&self) -> usize {
80 self.rows.len()
81 }
82 fn row(&self, i: usize) -> Vec<Cell> {
83 self.rows[i].clone()
84 }
85 fn column_defs(&self) -> &[ColumnDef] {
86 &self.cols
87 }
88 }
89
90 fn data() -> Data {
91 Data {
92 rows: vec![
93 vec![Cell::from("Alice"), Cell::Int(30)],
94 vec![Cell::from("Bob"), Cell::Int(25)],
95 vec![Cell::from("alfred"), Cell::Int(40)],
96 ],
97 cols: vec![],
98 }
99 }
100
101 #[test]
102 fn predicate_filter() {
103 let d = data();
104 let young = filter_indices(&d, |row| matches!(row[1], Cell::Int(n) if n < 35));
105 assert_eq!(young, vec![0, 1]);
106 }
107
108 #[test]
109 fn column_substring_case_insensitive() {
110 let d = data();
111 let f = ColumnFilter::new(0, "AL");
112 assert_eq!(f.apply(&d), vec![0, 2]);
114 }
115
116 #[test]
117 fn empty_pattern_matches_all() {
118 let d = data();
119 let f = ColumnFilter::new(0, "");
120 assert!(f.is_inactive());
121 assert_eq!(f.apply(&d), vec![0, 1, 2]);
122 }
123
124 #[test]
125 fn combined_and_filter() {
126 let d = data();
127 let filters = vec![ColumnFilter::new(0, "al"), ColumnFilter::new(1, "4")];
128 assert_eq!(apply_all(&d, &filters), vec![2]);
130 }
131}