cli_table/
cell.rs

1use std::{
2    fmt::Display,
3    io::{Result, Write},
4};
5
6use termcolor::{Buffer, BufferWriter, Color, ColorSpec, WriteColor};
7
8use crate::{
9    row::Dimension as RowDimension,
10    style::{Style, StyleStruct},
11    utils::display_width,
12};
13
14/// Concrete cell of a table
15pub struct CellStruct {
16    data: Vec<String>,
17    format: CellFormat,
18    style: StyleStruct,
19}
20
21impl CellStruct {
22    /// Used to horizontally justify contents of a cell
23    pub fn justify(mut self, justify: Justify) -> CellStruct {
24        self.format.justify = justify;
25        self
26    }
27
28    /// Used to vertically align the contents of a cell
29    pub fn align(mut self, align: Align) -> CellStruct {
30        self.format.align = align;
31        self
32    }
33
34    /// Used to add padding to the contents of a cell
35    pub fn padding(mut self, padding: Padding) -> CellStruct {
36        self.format.padding = padding;
37        self
38    }
39
40    fn color_spec(&self) -> ColorSpec {
41        self.style.color_spec()
42    }
43
44    /// Returns the minimum dimensions required by the cell
45    pub(crate) fn required_dimension(&self) -> Dimension {
46        let height = self.data.len() + self.format.padding.top + self.format.padding.bottom;
47        let width = self
48            .data
49            .iter()
50            .map(|x| display_width(x))
51            .max()
52            .unwrap_or_default()
53            + self.format.padding.left
54            + self.format.padding.right;
55
56        Dimension { width, height }
57    }
58
59    pub(crate) fn buffers(
60        &self,
61        writer: &BufferWriter,
62        available_dimension: Dimension,
63    ) -> Result<Vec<Buffer>> {
64        let required_dimension = self.required_dimension();
65        let mut buffers = Vec::with_capacity(available_dimension.height);
66
67        assert!(
68            available_dimension >= required_dimension,
69            "Available dimensions for a cell are smaller than required. Please create an issue in https://github.com/devashishdxt/cli-table"
70        );
71
72        let top_blank_lines = self.top_blank_lines(available_dimension, required_dimension);
73
74        for _ in 0..top_blank_lines {
75            buffers.push(self.buffer(writer, available_dimension, required_dimension, "")?);
76        }
77
78        for line in self.data.clone().iter() {
79            buffers.push(self.buffer(writer, available_dimension, required_dimension, line)?);
80        }
81
82        for _ in 0..(available_dimension.height - (self.data.len() + top_blank_lines)) {
83            buffers.push(self.buffer(writer, available_dimension, required_dimension, "")?);
84        }
85
86        Ok(buffers)
87    }
88
89    fn buffer(
90        &self,
91        writer: &BufferWriter,
92        available_dimension: Dimension,
93        required_dimension: Dimension,
94        data: &str,
95    ) -> Result<Buffer> {
96        let empty_chars = match self.format.justify {
97            Justify::Left => self.format.padding.left,
98            Justify::Right => {
99                (available_dimension.width - required_dimension.width) + self.format.padding.left
100            }
101            Justify::Center => {
102                ((available_dimension.width - required_dimension.width) / 2)
103                    + self.format.padding.left
104            }
105        };
106
107        let mut buffer = writer.buffer();
108        buffer.set_color(&self.color_spec())?;
109
110        for _ in 0..empty_chars {
111            write!(buffer, " ")?;
112        }
113
114        write!(buffer, "{}", data)?;
115
116        for _ in 0..(available_dimension.width - (display_width(data) + empty_chars)) {
117            write!(buffer, " ")?;
118        }
119
120        Ok(buffer)
121    }
122
123    fn top_blank_lines(
124        &self,
125        available_dimension: Dimension,
126        required_dimension: Dimension,
127    ) -> usize {
128        match self.format.align {
129            Align::Top => self.format.padding.top,
130            Align::Bottom => {
131                (available_dimension.height - required_dimension.height) + self.format.padding.top
132            }
133            Align::Center => {
134                ((available_dimension.height - required_dimension.height) / 2)
135                    + self.format.padding.top
136            }
137        }
138    }
139}
140
141/// Trait to convert raw types into cells
142pub trait Cell {
143    /// Converts raw type to cell of a table
144    fn cell(self) -> CellStruct;
145}
146
147impl<T> Cell for T
148where
149    T: Display,
150{
151    fn cell(self) -> CellStruct {
152        let data = self.to_string().lines().map(ToString::to_string).collect();
153
154        CellStruct {
155            data,
156            format: Default::default(),
157            style: Default::default(),
158        }
159    }
160}
161
162impl Cell for CellStruct {
163    fn cell(self) -> CellStruct {
164        self
165    }
166}
167
168impl Style for CellStruct {
169    fn foreground_color(mut self, foreground_color: Option<Color>) -> Self {
170        self.style = self.style.foreground_color(foreground_color);
171        self
172    }
173
174    fn background_color(mut self, background_color: Option<Color>) -> Self {
175        self.style = self.style.background_color(background_color);
176        self
177    }
178
179    fn bold(mut self, bold: bool) -> Self {
180        self.style = self.style.bold(bold);
181        self
182    }
183
184    fn underline(mut self, underline: bool) -> Self {
185        self.style = self.style.underline(underline);
186        self
187    }
188
189    fn italic(mut self, italic: bool) -> Self {
190        self.style = self.style.italic(italic);
191        self
192    }
193
194    fn intense(mut self, intense: bool) -> Self {
195        self.style = self.style.intense(intense);
196        self
197    }
198
199    fn dimmed(mut self, dimmed: bool) -> Self {
200        self.style = self.style.dimmed(dimmed);
201        self
202    }
203}
204
205/// Struct for configuring a cell's format
206#[derive(Debug, Clone, Copy, Default)]
207struct CellFormat {
208    justify: Justify,
209    align: Align,
210    padding: Padding,
211}
212
213/// Used to horizontally justify contents of a cell
214#[derive(Debug, Clone, Copy)]
215pub enum Justify {
216    /// Justifies contents to left
217    Left,
218    /// Justifies contents to right
219    Right,
220    /// Justifies contents to center
221    Center,
222}
223
224impl Default for Justify {
225    fn default() -> Self {
226        Self::Left
227    }
228}
229
230/// Used to vertically align contents of a cell
231#[derive(Debug, Clone, Copy)]
232pub enum Align {
233    /// Aligns contents to top
234    Top,
235    /// Aligns contents to bottom
236    Bottom,
237    /// Aligns contents to center
238    Center,
239}
240
241impl Default for Align {
242    fn default() -> Self {
243        Self::Top
244    }
245}
246
247/// Used to add padding to the contents of a cell
248#[derive(Debug, Clone, Copy, Default)]
249pub struct Padding {
250    /// Left padding
251    pub(crate) left: usize,
252    /// Right padding
253    pub(crate) right: usize,
254    /// Top padding
255    pub(crate) top: usize,
256    /// Bottom padding
257    pub(crate) bottom: usize,
258}
259
260impl Padding {
261    /// Creates a new builder for padding
262    pub fn builder() -> PaddingBuilder {
263        Default::default()
264    }
265}
266
267/// Builder for padding
268#[derive(Debug, Default)]
269pub struct PaddingBuilder(Padding);
270
271impl PaddingBuilder {
272    /// Sets left padding of a cell
273    pub fn left(mut self, left_padding: usize) -> Self {
274        self.0.left = left_padding;
275        self
276    }
277
278    /// Sets right padding of a cell
279    pub fn right(mut self, right_padding: usize) -> Self {
280        self.0.right = right_padding;
281        self
282    }
283
284    /// Sets top padding of a cell
285    pub fn top(mut self, top_padding: usize) -> Self {
286        self.0.top = top_padding;
287        self
288    }
289
290    /// Sets bottom padding of a cell
291    pub fn bottom(mut self, bottom_padding: usize) -> Self {
292        self.0.bottom = bottom_padding;
293        self
294    }
295
296    /// Build padding
297    pub fn build(self) -> Padding {
298        self.0
299    }
300}
301
302/// Dimensions of a cell
303#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
304pub(crate) struct Dimension {
305    /// Width of a cell
306    pub(crate) width: usize,
307    /// Height of a cell
308    pub(crate) height: usize,
309}
310
311impl From<RowDimension> for Vec<Dimension> {
312    fn from(row_dimension: RowDimension) -> Self {
313        let height = row_dimension.height;
314
315        row_dimension
316            .widths
317            .into_iter()
318            .map(|width| Dimension { width, height })
319            .collect()
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn test_into_cell() {
329        let cell = "Hello".cell();
330        assert_eq!(1, cell.data.len());
331        assert_eq!("Hello", cell.data[0]);
332    }
333}