term_data_table/
row.rs

1use crate::{Cell, TableStyle};
2use itertools::Itertools;
3use std::fmt::{self, Write};
4
5/// A set of table cells
6#[derive(Debug, Clone)]
7pub struct Row<'data> {
8    pub(crate) cells: Vec<Cell<'data>>,
9    /// Whether the row should have a top border or not
10    pub(crate) has_separator: bool,
11}
12
13impl<'data> Default for Row<'data> {
14    fn default() -> Self {
15        Self {
16            cells: vec![],
17            has_separator: true,
18        }
19    }
20}
21
22impl<'data> Row<'data> {
23    pub fn new() -> Self {
24        Default::default()
25    }
26
27    /// Whether the row should have a top border or not
28    pub fn with_separator(mut self, has_separator: bool) -> Self {
29        self.set_has_separator(has_separator);
30        self
31    }
32
33    /// Whether the row should have a top border or not
34    pub fn set_has_separator(&mut self, has_separator: bool) -> &mut Self {
35        self.has_separator = has_separator;
36        self
37    }
38
39    pub fn add_cell(&mut self, cell: impl Into<Cell<'data>>) -> &mut Self {
40        self.cells.push(cell.into());
41        self
42    }
43
44    pub fn with_cell(mut self, cell: impl Into<Cell<'data>>) -> Self {
45        self.add_cell(cell);
46        self
47    }
48
49    /// Number of columns in this row, taking into account col_span > 1.
50    pub(crate) fn columns(&self) -> usize {
51        self.cells.iter().map(|cell| cell.col_span).sum()
52    }
53
54    /// Ask the row to calculate its layout.
55    ///
56    /// Returns the number of lines required to display this row (without the top border).
57    pub(crate) fn layout(&self, column_widths: &[usize], border_width: usize) -> usize {
58        let mut max_lines = 0;
59        let mut idx = 0;
60        for cell in self.cells.iter() {
61            // start with the extra space for borders
62            let mut width = (cell.col_span - 1) * border_width;
63
64            // add in space for cell content.
65            for w in column_widths[idx..idx + cell.col_span].iter().copied() {
66                width += w;
67            }
68            let num_lines = cell.layout(Some(width));
69            idx += cell.col_span;
70            max_lines = max_lines.max(num_lines);
71        }
72        max_lines
73    }
74
75    pub fn render_top_separator(
76        &self,
77        cell_widths: &[usize],
78        style: &TableStyle,
79        f: &mut fmt::Formatter,
80    ) -> fmt::Result {
81        if !self.has_separator {
82            // don't draw anything
83            return Ok(());
84        }
85        // special-case the first cell
86        f.write_char(style.top_left_corner)?;
87        let mut widths = cell_widths;
88        let mut width;
89        let mut cells = self.cells.iter();
90        if let Some(first_cell) = cells.next() {
91            (width, widths) = first_cell.width(style.border_width(), widths);
92            for _ in 0..width {
93                f.write_char(style.horizontal)?;
94            }
95        }
96        for cell in cells {
97            f.write_char(style.outer_top_horizontal)?;
98            (width, widths) = cell.width(style.border_width(), widths);
99            for _ in 0..width {
100                f.write_char(style.horizontal)?;
101            }
102        }
103        f.write_char(style.top_right_corner)?;
104        writeln!(f)
105    }
106
107    pub fn render_bottom_separator(
108        &self,
109        cell_widths: &[usize],
110        style: &TableStyle,
111        f: &mut fmt::Formatter,
112    ) -> fmt::Result {
113        if !self.has_separator {
114            // don't draw anything
115            return Ok(());
116        }
117        // special-case the first cell
118        f.write_char(style.bottom_left_corner)?;
119        let mut widths = cell_widths;
120        let mut width;
121        let mut cells = self.cells.iter();
122        if let Some(first_cell) = cells.next() {
123            (width, widths) = first_cell.width(style.border_width(), widths);
124            for _ in 0..width {
125                f.write_char(style.horizontal)?;
126            }
127        }
128        for cell in cells {
129            f.write_char(style.outer_bottom_horizontal)?;
130            (width, widths) = cell.width(style.border_width(), widths);
131            for _ in 0..width {
132                f.write_char(style.horizontal)?;
133            }
134        }
135        f.write_char(style.bottom_right_corner)?;
136        writeln!(f)
137    }
138
139    pub fn render_separator(
140        &self,
141        prev: &Row,
142        cell_widths: &[usize],
143        style: &TableStyle,
144        f: &mut fmt::Formatter,
145    ) -> fmt::Result {
146        if !self.has_separator {
147            // don't draw anything
148            return Ok(());
149        }
150        f.write_char(style.outer_left_vertical)?;
151        let mut iter = cell_widths
152            .iter()
153            .copied()
154            .zip(self.iter_junctions(prev).skip(1))
155            .peekable();
156        while let Some((width, borders)) = iter.next() {
157            for _ in 0..width {
158                f.write_char(style.horizontal)?;
159            }
160            f.write_char(borders.joiner(style, iter.peek().is_none()))?;
161        }
162        writeln!(f)
163    }
164
165    /// Formats a row based on the provided table style
166    pub(crate) fn render_content(
167        &self,
168        column_widths: &[usize],
169        num_lines: usize,
170        style: &TableStyle,
171        f: &mut fmt::Formatter,
172    ) -> fmt::Result {
173        for line_num in 0..num_lines {
174            let mut width;
175            let mut widths = column_widths;
176            for cell in &self.cells {
177                f.write_char(style.vertical)?;
178                (width, widths) = cell.width(style.border_width(), widths);
179                cell.render_line(line_num, width, f)?;
180            }
181            f.write_char(style.vertical)?;
182            writeln!(f)?;
183        }
184        Ok(())
185    }
186    /// Number of columns in the row.
187    ///
188    /// This is the sum of all cell's col_span values
189    pub fn num_columns(&self) -> usize {
190        self.cells.iter().map(|x| x.col_span).sum()
191    }
192
193    /// What kind of join is at the beginning of each cell.
194    fn iter_joins(&'data self) -> impl Iterator<Item = BorderTy> + 'data {
195        struct IterJoins<'a> {
196            inner: std::slice::Iter<'a, Cell<'a>>,
197            // cols_remaining == 0 means we are past the end.
198            cols_remaining: usize,
199        }
200
201        impl<'a> Iterator for IterJoins<'a> {
202            type Item = BorderTy;
203            fn next(&mut self) -> Option<Self::Item> {
204                let out = Some(match &mut self.cols_remaining {
205                    // we are past the end
206                    0 => BorderTy::Empty,
207                    // we are at the end of a cell
208                    n @ 1 => {
209                        *n = self.inner.next().map(|cell| cell.col_span).unwrap_or(0);
210                        BorderTy::End
211                    }
212                    // we are in the middle of a cell
213                    n => {
214                        *n -= 1;
215                        BorderTy::Middle
216                    }
217                });
218                out
219            }
220        }
221        IterJoins {
222            inner: self.cells.iter(),
223            // start as if we just finished a cell
224            cols_remaining: 1,
225        }
226    }
227
228    /// The correct border given the previous and next rows.
229    fn iter_junctions(&'data self, prev: &'data Self) -> impl Iterator<Item = Borders> + 'data {
230        prev.iter_joins()
231            .zip(self.iter_joins())
232            .map(|(above, below)| {
233                let borders = Borders { above, below };
234                if borders == Borders::EMPTY {
235                    None
236                } else {
237                    Some(borders)
238                }
239            })
240            .while_some()
241    }
242}
243
244#[derive(Debug, Copy, Clone, Eq, PartialEq)]
245struct Borders {
246    above: BorderTy,
247    below: BorderTy,
248}
249
250#[derive(Debug, Copy, Clone, Eq, PartialEq)]
251enum BorderTy {
252    Empty,
253    Middle,
254    End,
255}
256
257impl Borders {
258    const EMPTY: Borders = Borders {
259        above: BorderTy::Empty,
260        below: BorderTy::Empty,
261    };
262
263    fn joiner(&self, style: &TableStyle, final_end: bool) -> char {
264        use BorderTy::*;
265        match (self.above, self.below) {
266            (Empty, Empty) => unreachable!(),
267            (Empty, Middle) | (Middle, Empty) | (Middle, Middle) => style.horizontal,
268            (Empty, End) | (Middle, End) => {
269                if final_end {
270                    style.top_right_corner
271                } else {
272                    style.outer_top_horizontal
273                }
274            }
275            (End, Empty) | (End, Middle) => {
276                if final_end {
277                    style.bottom_right_corner
278                } else {
279                    style.outer_bottom_horizontal
280                }
281            }
282            (End, End) => {
283                if final_end {
284                    style.outer_right_vertical
285                } else {
286                    style.intersection
287                }
288            }
289        }
290    }
291}
292
293// ------------------
294
295/// A trait for types that know how to turn themselves into a table row.
296///
297/// Note that the tuple implementations of these methods always copy strings.
298pub trait IntoRow {
299    /// Returns a set of cells that can be used as headers for the cells of data of this type.
300    fn headers(&self) -> Row;
301    /// Returns the row.
302    fn into_row(&self) -> Row;
303}
304
305macro_rules! impl_row_for_tuple {
306    () => {};
307
308    (($first_label:expr, $first_ty:ident) $(,($rest_label:expr, $rest_ty:ident))*) => {
309        impl<$first_ty, $($rest_ty,)*> IntoRow for ($first_ty, $($rest_ty),*)
310            where $first_ty: ::std::fmt::Display,
311                  $(
312                      $rest_ty: ::std::fmt::Display,
313                  )*
314        {
315            fn headers(&self) -> Row {
316                let mut row = Row::default();
317                row.add_cell(stringify!($first_ty));
318                $(
319                    row.add_cell(stringify!($rest_ty));
320                )*
321                row
322            }
323
324            fn into_row(&self) -> Row {
325                #[allow(non_snake_case)]
326                let (
327                    ref $first_ty,
328                    $(
329                        ref $rest_ty
330                    ),*
331                ) = &self;
332                let mut row = Row::default();
333                row.add_cell($first_ty.to_string());
334                $(
335                    row.add_cell($rest_ty.to_string());
336                )*
337                row
338            }
339        }
340
341        impl_row_for_tuple!($(($rest_label, $rest_ty)),*);
342    };
343}
344
345impl_row_for_tuple!(
346    ("_0", D0),
347    ("_1", D1),
348    ("_2", D2),
349    ("_3", D3),
350    ("_4", D4),
351    ("_5", D5),
352    ("_6", D6),
353    ("_7", D7),
354    ("_8", D8),
355    ("_9", D9)
356);