libtableformat/table/
row.rs

1use super::border::Border;
2use super::cell::Cell;
3use crate::content::{CellWidth};
4
5pub struct CellIterator<'a> {
6    cells: &'a Vec<Cell>,
7    current_cell_ix: usize
8}
9
10impl<'a> Iterator for CellIterator<'a> {
11    type Item = &'a Cell;
12
13    fn next(&mut self) -> Option<&'a Cell> {
14        if self.current_cell_ix < self.cells.len() {
15            let cell: &Cell = &self.cells[self.current_cell_ix];
16            self.current_cell_ix += 1;
17            Some(cell)
18        } else {
19            None
20        }
21    }
22}
23
24#[allow(unused_macros)]
25#[macro_export]
26macro_rules! row {
27    ( $($style:expr => $content:expr),* ) => {
28        {
29            let mut r: Row = Row::new();
30            $( r.add_cell(crate::cell!($style, $content)); )*
31            r
32        }
33    };
34    ( $style:expr, $($content:expr),* ) => {
35        {
36            let mut r: Row = Row::new();
37            $( r.add_cell(crate::cell!($style, $content)); )*
38            r
39        }
40    };
41}
42
43/// Table rows represent horizontal breakpoints.
44#[derive(Debug)]
45pub struct Row {
46    cells: Vec<Cell>
47}
48
49impl Default for Row {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl Row {
56    #[must_use]
57    pub fn new() -> Row {
58        Row {
59            cells: Vec::new()
60        }
61    }
62
63    #[must_use]
64    pub fn from(
65        cells: Vec<Cell>
66    ) -> Row {
67        Row { cells }
68    }
69
70    pub fn add_cell(&mut self, cell: Cell) {
71        self.cells.push(cell);
72    }
73
74    #[must_use]
75    pub fn iter(
76        self: &Row,
77    ) -> CellIterator {
78        CellIterator {
79            cells: &self.cells,
80            current_cell_ix: 0
81        }
82    }
83
84    #[must_use]
85    pub fn len(self: &Row) -> usize {
86        self.cells.len()
87    }
88
89    #[must_use]
90    pub fn is_empty(self: &Row) -> bool {
91        self.len() == 0
92    }
93
94    /// Formats a table row.
95    ///
96    /// # Arguments
97    ///
98    /// * `self` - The table row to format.
99    /// * `border` - The table border.
100    /// * `column_breaks` - The breakpoints at which to wrap or truncate.
101    #[must_use]
102    #[allow(clippy::option_if_let_else)]
103    pub fn format(
104        self: &Row,
105        border: &Border,
106        column_breaks: &[CellWidth]
107    ) -> String {
108        let mut result: String = String::from("");
109
110        let row_height = self.measure_height(column_breaks);
111
112        // Get content iterators for each cell
113        let mut content_iterators = Vec::new();
114        for (cell_ix, cell) in self.cells.iter().enumerate() {
115            let column_break = &column_breaks[cell_ix];
116            content_iterators.push(cell.get_iterator(&column_break));
117        }
118
119        // Iterate the number of lines
120        let content_break = CellWidth::default();
121        for _line_ix in 0..row_height {
122            // Left border
123            result.push_str(&border.format_left());
124            // Write the contents for the current line of the cell
125            for cell_ix in 0..self.cells.len() {
126                let cell = &self.cells[cell_ix];
127                let column_break: &CellWidth =
128                    if cell_ix < column_breaks.len() {
129                        &column_breaks[cell_ix]
130                    } else {
131                        &content_break
132                    };
133                result.push_str(
134                    &if let Some(content) = content_iterators[cell_ix].next() {
135                        content.to_string()
136                    } else {
137                        // No more lines so fill height with empty space
138                        let cell_width = cell.measure_width(column_break);
139                        (0..cell_width)
140                            .map(|_| " ")
141                            .collect::<String>()
142                    }
143                );
144                // Vertical split (except for final column)
145                if cell_ix < column_breaks.len() - 1 {
146                    result.push_str(&border.format_vertical_split());
147                }
148            }
149            // Right border
150            result.push_str(&border.format_right());
151            result.push('\n');
152        }
153
154        result
155    }
156
157    /// Measures the height of a table row.
158    ///
159    /// # Arguments
160    ///
161    /// * `self` - The table row being measured.
162    /// * `columns` - The columns used to format the cells for this row.
163    #[must_use]
164    pub fn measure_height(
165        self: &Row,
166        column_breaks: &[CellWidth],
167    ) -> usize {
168        let mut tallest_height = 0;
169
170        // Iterate the row cells and measure based upon supplied column breaks
171        let column_break_ix = 0;
172        let content_break = CellWidth::Content;
173        for cell in &self.cells {
174            // Get the next column break (if one is available)
175            let column_break: &CellWidth = 
176                if column_break_ix < column_breaks.len() {
177                    &column_breaks[column_break_ix]
178                } else {
179                    // Use content-width break for additional columns
180                    &content_break
181                };
182            let cell_height = cell.measure_height(column_break);
183            if cell_height > tallest_height {
184                tallest_height = cell_height;
185            }
186        }
187
188        tallest_height
189    }
190}
191
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn test_row_macro_style_per_cell() {
199        assert_eq!(
200            format!("{:?}", row!("{c^}" => "Head 1", "{G-r>}" => "Head 2")),
201            format!("{:?}", Row::from(
202                vec!(
203                    crate::cell!("{c^}", "Head 1"),
204                    crate::cell!("{G-r>}", "Head 2")
205                )
206            ))
207        );
208    }
209
210    #[test]
211    fn test_row_macro_common_style() {
212        assert_eq!(
213            format!("{:?}", row!("{c^}", "Text 1", "Text 2", "Text 3")),
214            format!("{:?}", Row::from(
215                vec!(
216                    crate::cell!("{c^}", "Text 1"),
217                    crate::cell!("{c^}", "Text 2"),
218                    crate::cell!("{c^}", "Text 3")
219                )
220            ))
221        );
222    }
223}