libtableformat/table/
cell.rs

1use crate::content::{Content, ContentIterator, ContentStyle, CellWidth};
2use crate::data_item::DataItem;
3use std::clone::Clone;
4
5pub struct TableCellContentIterator<'a> {
6    content: &'a Vec<Content>,
7    current_content_iterator: ContentIterator,
8    current_line_ix: usize,
9    base_style: ContentStyle,
10    width: usize,
11    target_height: usize,
12    current_height: usize,
13}
14
15impl<'a> Iterator for TableCellContentIterator<'a> {
16    type Item = String;
17
18    fn next(&mut self) -> Option<String> {
19        let line = 
20            if self.current_line_ix < self.content.len() {
21                // Get the next line from the current content iterator
22                self.current_content_iterator.next().map_or_else(|| {
23                    // Go to the next line
24                    self.current_line_ix += 1;
25
26                    // If there are more lines, get iterator for next line
27                    if self.current_line_ix < self.content.len() {
28                        self.current_content_iterator = 
29                            self.content[self.current_line_ix].get_iterator(
30                                &self.base_style.clone(), self.width);
31
32                        // Get the line from the iterator
33                        self.current_content_iterator.next()
34                    } else {
35                        // No more lines to get
36                        None
37                    }
38                }, Some)
39            } else {
40                None
41            };
42
43        match line {
44            Some(content) => {
45                Some(content)
46            } ,
47            None => {
48                if self.current_height < self.target_height {
49                    // An empty line of spaces the width of the column
50                    let result = 
51                        (0..self.width)
52                            .map(|_| " ")
53                            .collect::<String>();
54                    self.current_height += 1;
55                    Some(result)
56                } else {
57                    None
58                }
59            }
60        }
61    }
62}
63
64#[allow(unused_macros)]
65#[macro_export]
66macro_rules! cell_content {
67    ($($out:tt)*) => {
68        vec!($($out)*)
69    };
70}
71
72#[allow(unused_macros)]
73#[macro_export]
74macro_rules! cell {
75    ($style:expr, $($content:tt)*) => {
76        Cell::from_styled_content(
77            $style, 
78            crate::cell_content!($($content)*)
79        );
80    }
81}
82
83/// A table cell represents a single grid rectangle within a table.
84///
85/// Cells belong to a row.
86#[derive(Debug)]
87pub struct Cell {
88    contents: Vec<Content>,
89    base_style: ContentStyle,
90}
91
92impl Cell {
93    #[must_use]
94    pub fn empty() -> Cell {
95        Cell {
96            contents: Vec::new(),
97            base_style: ContentStyle::default(),
98        }
99    }
100
101    #[must_use]
102    pub fn new(
103        contents: Vec<Content>,
104        base_style: ContentStyle,
105    ) -> Cell {
106        Cell {
107            contents,
108            base_style
109        }
110    }
111
112    /// Returns a `Cell` from styled content.
113    ///
114    /// # Arguments
115    ///
116    /// * `format` - The style format.
117    /// * `contents` - The contents of the new cell.
118    #[must_use]
119    pub fn from_styled_content(
120        format: &str,
121        contents: Vec<&str>,
122    ) -> Cell {
123        // Split the format string into style tokens
124        let styles: Vec<&str> = format.split(' ').collect();
125        let mut table_cell = Cell::empty();
126
127        // Iterate the contents
128        for (style_ix, content) in contents.into_iter().enumerate() {
129            // Get next style (use default if no more styles provided)
130            let style = 
131                if style_ix < styles.len() { 
132                    ContentStyle::from_format(styles[style_ix]) }
133                else { ContentStyle::default() };
134
135            // Add the new styled content
136            table_cell.contents.push(
137                Content::new(content.to_string(), Some(style)));
138        }
139        table_cell
140    }
141
142    /// Returns a `Cell` from a `DataItem`.
143    ///
144    /// # Arguments
145    ///
146    /// * `data_item` - The data item from which to build the table cell.
147    /// * `base_style` - The base style to apply to the cell contents.
148    #[must_use]
149    pub fn from_data_item(
150        data_item: &DataItem,
151        base_style: ContentStyle,
152    ) -> Cell {
153        Cell::new(
154            data_item.lines.clone(),
155            base_style,
156        )
157    }
158
159    /// Returns the column break specified in the first content line of the
160    /// cell.
161    ///
162    /// This is used to determine the column break for cells used in the table
163    /// header row.
164    #[must_use]
165    pub fn get_cell_width(
166        self: &Cell
167    ) -> CellWidth {
168        if self.contents.is_empty() {
169            CellWidth::default() }
170        else {
171            match &self.contents[0].style {
172                Some(style) => style.width.clone(),
173                None => CellWidth::default()
174            }
175        }
176    }
177
178    /// Returns the next formatted line of content from this table cell.
179    ///
180    /// # Arguments
181    ///
182    /// * `self` - The table cell containing the line.
183    /// * `width` - The format width.
184    #[must_use]
185    pub fn get_iterator(
186        self: &Cell,
187        column_break: &CellWidth
188    ) -> TableCellContentIterator {
189        // Determine the render width of this cell
190        let cell_width = self.measure_width(column_break);
191
192        TableCellContentIterator {
193            content: &self.contents,
194            current_content_iterator: 
195                self.contents[0].get_iterator(&self.base_style.clone(), cell_width),
196            current_line_ix: 0,
197            base_style: self.base_style.clone(),
198            width: cell_width,
199            target_height: self.measure_height(column_break),
200            current_height: 0
201        }
202    }
203
204    /// Measures the height needed for this cell when formatting its contents
205    ///  into a specific column width.
206    ///
207    ///  # Arguments
208    ///
209    /// * `self` - The table cell being measured.
210    /// * `column_width` - The column width to measure against.
211    #[must_use]
212    pub fn measure_height(
213        self: &Cell,
214        column_break: &CellWidth,
215    ) -> usize {
216        let mut height = 0;
217
218        // Determine the render width of this cell
219        let cell_width = self.measure_width(column_break);
220
221        for line in &self.contents {
222            let line_width = line.measure_width();
223            // If line fits within column then line height is 1
224            if !line.will_wrap() || (line_width <= cell_width) {
225                height += 1
226            } else {
227                // Determine how many lines are needed when content is wrapped
228                height += line_width.div_euclid(cell_width) + 1;
229            }
230        }
231
232        height
233    }
234
235    /// Measures the width of this cell.
236    ///
237    /// # Arguments
238    ///
239    /// * `self` - The table cell being measured.
240    /// * `column_break` - The column break for this cell.
241    #[must_use]
242    pub fn measure_width(
243        self: &Cell,
244        column_break: &CellWidth,
245    ) -> usize {
246        match column_break {
247            CellWidth::Fixed(fixed) => *fixed,
248            CellWidth::Minimum(minimum_width) => {
249                let content_width = self.measure_content_width();
250                if minimum_width > &content_width {
251                    *minimum_width
252                } else {
253                    content_width
254                }
255            },
256            CellWidth::Content => {
257                self.measure_content_width()
258            }
259        }
260    }
261
262    /// Returns the width of the longest content item in this cell.
263    ///
264    /// This measure ignores wrapping or truncation and returns the raw width
265    ///  of the longest content item.
266    fn measure_content_width(
267        self: &Cell
268    ) -> usize {
269        let mut largest = 0;
270        for content in &self.contents {
271            let content_width = content.measure_width();
272            if content_width > largest {
273                largest = content_width;
274            }
275        }
276
277        largest
278    }
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284    use colored::Color;
285    use crate::content::{Alignment, Wrap};
286
287    #[test]
288    fn test_table_cell_macro() {
289        let tc = cell!("{r<;} {G-b^}", "testing", "this");
290
291        assert_eq!(tc.contents.len(), 2);
292
293        assert_eq!(
294            format!("{:?}", tc.contents[0]),
295            format!("{:?}", Content::new(
296                "testing".to_string(),
297                Some(ContentStyle::new(
298                    Some(Color::Red),
299                    None,
300                    Alignment::Left,
301                    Wrap::Wrap,
302                    CellWidth::Content
303                ))
304            ))
305        );
306    }
307}