Skip to main content

lipgloss_table/
rows.rs

1/// Data is the interface that wraps the basic methods of a table model.
2pub trait Data {
3    /// At returns the contents of the cell at the given index.
4    fn at(&self, row: usize, cell: usize) -> String;
5
6    /// Rows returns the number of rows in the table.
7    fn rows(&self) -> usize;
8
9    /// Columns returns the number of columns in the table.
10    fn columns(&self) -> usize;
11}
12
13/// StringData is a string-based implementation of the Data interface.
14#[derive(Debug, Clone)]
15pub struct StringData {
16    rows: Vec<Vec<String>>,
17    columns: usize,
18}
19
20impl StringData {
21    /// Creates a new StringData with the given rows.
22    pub fn new(rows: Vec<Vec<String>>) -> Self {
23        let columns = rows.iter().map(|row| row.len()).max().unwrap_or(0);
24        Self { rows, columns }
25    }
26
27    /// Creates a new empty StringData.
28    pub fn empty() -> Self {
29        Self {
30            rows: Vec::new(),
31            columns: 0,
32        }
33    }
34
35    /// Appends the given row to the table.
36    pub fn append(&mut self, row: Vec<String>) {
37        self.columns = self.columns.max(row.len());
38        self.rows.push(row);
39    }
40
41    /// Item appends the given row to the table (builder pattern).
42    pub fn item(mut self, row: Vec<String>) -> Self {
43        self.append(row);
44        self
45    }
46}
47
48impl Data for StringData {
49    fn at(&self, row: usize, cell: usize) -> String {
50        if row >= self.rows.len() || cell >= self.rows[row].len() {
51            return String::new();
52        }
53        self.rows[row][cell].clone()
54    }
55
56    fn rows(&self) -> usize {
57        self.rows.len()
58    }
59
60    fn columns(&self) -> usize {
61        self.columns
62    }
63}
64
65/// Filter applies a filter on some data.
66pub struct Filter<D: Data> {
67    data: D,
68    filter: Option<Box<dyn Fn(usize) -> bool>>,
69}
70
71impl<D: Data> Filter<D> {
72    /// Creates a new Filter with the given data.
73    pub fn new(data: D) -> Self {
74        Self { data, filter: None }
75    }
76
77    /// Applies the given filter function to the data.
78    pub fn filter<F>(mut self, f: F) -> Self
79    where
80        F: Fn(usize) -> bool + 'static,
81    {
82        self.filter = Some(Box::new(f));
83        self
84    }
85}
86
87impl<D: Data> Data for Filter<D> {
88    fn at(&self, row: usize, cell: usize) -> String {
89        if let Some(ref filter) = self.filter {
90            let mut j = 0;
91            for i in 0..self.data.rows() {
92                if filter(i) {
93                    if j == row {
94                        return self.data.at(i, cell);
95                    }
96                    j += 1;
97                }
98            }
99            String::new()
100        } else {
101            self.data.at(row, cell)
102        }
103    }
104
105    fn rows(&self) -> usize {
106        if let Some(ref filter) = self.filter {
107            let mut count = 0;
108            for i in 0..self.data.rows() {
109                if filter(i) {
110                    count += 1;
111                }
112            }
113            count
114        } else {
115            self.data.rows()
116        }
117    }
118
119    fn columns(&self) -> usize {
120        self.data.columns()
121    }
122}
123
124/// Converts an object that implements the Data interface to a matrix.
125pub fn data_to_matrix<D: Data + ?Sized>(data: &D) -> Vec<Vec<String>> {
126    let num_rows = data.rows();
127    let num_cols = data.columns();
128    let mut rows = Vec::with_capacity(num_rows);
129
130    for i in 0..num_rows {
131        let mut row = Vec::with_capacity(num_cols);
132        for j in 0..num_cols {
133            row.push(data.at(i, j));
134        }
135        rows.push(row);
136    }
137    rows
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_string_data_basic() {
146        let data = StringData::new(vec![
147            vec!["A".to_string(), "B".to_string()],
148            vec!["C".to_string(), "D".to_string(), "E".to_string()],
149        ]);
150
151        assert_eq!(data.rows(), 2);
152        assert_eq!(data.columns(), 3); // Max columns
153        assert_eq!(data.at(0, 0), "A");
154        assert_eq!(data.at(0, 1), "B");
155        assert_eq!(data.at(1, 2), "E");
156        assert_eq!(data.at(0, 2), ""); // Missing cell
157        assert_eq!(data.at(2, 0), ""); // Out of bounds
158    }
159
160    #[test]
161    fn test_string_data_append() {
162        let mut data = StringData::empty();
163        assert_eq!(data.rows(), 0);
164        assert_eq!(data.columns(), 0);
165
166        data.append(vec!["A".to_string(), "B".to_string()]);
167        assert_eq!(data.rows(), 1);
168        assert_eq!(data.columns(), 2);
169
170        data.append(vec!["C".to_string()]);
171        assert_eq!(data.rows(), 2);
172        assert_eq!(data.columns(), 2); // Still 2, not reduced
173    }
174
175    #[test]
176    fn test_string_data_builder() {
177        let data = StringData::empty()
178            .item(vec!["Name".to_string(), "Age".to_string()])
179            .item(vec!["Alice".to_string(), "30".to_string()])
180            .item(vec![
181                "Bob".to_string(),
182                "25".to_string(),
183                "Engineer".to_string(),
184            ]);
185
186        assert_eq!(data.rows(), 3);
187        assert_eq!(data.columns(), 3);
188        assert_eq!(data.at(1, 0), "Alice");
189        assert_eq!(data.at(2, 2), "Engineer");
190    }
191
192    #[test]
193    fn test_filter_basic() {
194        let data = StringData::new(vec![
195            vec!["A".to_string(), "1".to_string()],
196            vec!["B".to_string(), "2".to_string()],
197            vec!["C".to_string(), "3".to_string()],
198            vec!["D".to_string(), "4".to_string()],
199        ]);
200
201        // Filter even rows (0, 2)
202        let filtered = Filter::new(data).filter(|row| row.is_multiple_of(2));
203
204        assert_eq!(filtered.rows(), 2);
205        assert_eq!(filtered.columns(), 2);
206        assert_eq!(filtered.at(0, 0), "A"); // Original row 0
207        assert_eq!(filtered.at(1, 0), "C"); // Original row 2
208        assert_eq!(filtered.at(0, 1), "1");
209        assert_eq!(filtered.at(1, 1), "3");
210    }
211
212    #[test]
213    fn test_data_to_matrix() {
214        let data = StringData::new(vec![
215            vec!["A".to_string(), "B".to_string()],
216            vec!["C".to_string()],
217        ]);
218
219        let matrix = data_to_matrix(&data);
220        assert_eq!(matrix.len(), 2);
221        assert_eq!(matrix[0], vec!["A", "B"]);
222        assert_eq!(matrix[1], vec!["C", ""]); // Padded with empty string
223    }
224}