term_table/
table_cell.rs

1use lazy_static;
2use regex::Regex;
3use std::cmp;
4use std::collections::HashSet;
5
6use unicode_width::UnicodeWidthChar;
7use unicode_width::UnicodeWidthStr;
8
9/// Represents the horizontal alignment of content within a cell.
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub enum Alignment {
12    Left,
13    Right,
14    Center,
15}
16
17///A table cell containing some str data.
18///
19///A cell may span multiple columns by setting the value of `col_span`.
20///
21///`pad_content` will add a space to either side of the cell's content.AsRef
22#[derive(Debug, Clone)]
23pub struct TableCell {
24    pub data: String,
25    pub col_span: usize,
26    pub alignment: Alignment,
27    pub pad_content: bool,
28}
29
30impl TableCell {
31    pub fn new<T>(data: T) -> TableCell
32    where
33        T: ToString,
34    {
35        Self {
36            data: data.to_string(),
37            col_span: 1,
38            alignment: Alignment::Left,
39            pad_content: true,
40        }
41    }
42
43    pub fn builder<T>(data: T) -> TableCellBuilder
44    where
45        T: ToString,
46    {
47        TableCellBuilder::new(data.to_string())
48    }
49
50    #[deprecated(since = "1.4.0", note = "Use builder instead")]
51    pub fn new_with_col_span<T>(data: T, col_span: usize) -> TableCell
52    where
53        T: ToString,
54    {
55        Self {
56            data: data.to_string(),
57            alignment: Alignment::Left,
58            pad_content: true,
59            col_span,
60        }
61    }
62
63    #[deprecated(since = "1.4.0", note = "Use builder instead")]
64    pub fn new_with_alignment<T>(data: T, col_span: usize, alignment: Alignment) -> TableCell
65    where
66        T: ToString,
67    {
68        Self {
69            data: data.to_string(),
70            pad_content: true,
71            col_span,
72            alignment,
73        }
74    }
75
76    #[deprecated(since = "1.4.0", note = "Use builder instead")]
77    pub fn new_with_alignment_and_padding<T>(
78        data: T,
79        col_span: usize,
80        alignment: Alignment,
81        pad_content: bool,
82    ) -> TableCell
83    where
84        T: ToString,
85    {
86        Self {
87            data: data.to_string(),
88            col_span,
89            alignment,
90            pad_content,
91        }
92    }
93
94    /// Calculates the width of the cell.
95    ///
96    /// New line characters are taken into account during the calculation.
97    pub fn width(&self) -> usize {
98        let wrapped = self.wrapped_content(std::usize::MAX);
99        let mut max = 0;
100        for s in wrapped {
101            let str_width = string_width(&s);
102            max = cmp::max(max, str_width);
103        }
104        max
105    }
106
107    /// The width of the cell's content divided by its `col_span` value.
108    pub fn split_width(&self) -> f32 {
109        let res = self.width() as f32 / self.col_span as f32;
110        res
111    }
112
113    /// The minium width required to display the cell properly
114    pub fn min_width(&self) -> usize {
115        let mut max_char_width: usize = 0;
116        for c in self.data.chars() {
117            max_char_width = cmp::max(max_char_width, c.width().unwrap_or(1) as usize);
118        }
119
120        if self.pad_content {
121            max_char_width + ' '.width().unwrap_or(1) as usize * 2
122        } else {
123            max_char_width
124        }
125    }
126
127    /// Wraps the cell's content to the provided width.
128    ///
129    /// New line characters are taken into account.
130    pub fn wrapped_content(&self, width: usize) -> Vec<String> {
131        let pad_char = if self.pad_content { ' ' } else { '\0' };
132        let hidden: HashSet<usize> = STRIP_ANSI_RE
133            .find_iter(&self.data)
134            .flat_map(|m| m.start()..m.end())
135            .collect();
136        let mut res: Vec<String> = Vec::new();
137        let mut buf = String::new();
138        buf.push(pad_char);
139        let mut byte_index = 0;
140        for c in self.data.chars() {
141            if !hidden.contains(&byte_index)
142                && (string_width(&buf) >= width - pad_char.width().unwrap_or(1) || c == '\n')
143            {
144                buf.push(pad_char);
145                res.push(buf);
146                buf = String::new();
147                buf.push(pad_char);
148                if c == '\n' {
149                    byte_index += 1;
150                    continue;
151                }
152            }
153            byte_index += c.len_utf8();
154            buf.push(c);
155        }
156        buf.push(pad_char);
157        res.push(buf);
158
159        res
160    }
161}
162
163impl<T> From<T> for TableCell
164where
165    T: ToString,
166{
167    fn from(other: T) -> Self {
168        TableCell::new(other)
169    }
170}
171
172pub struct TableCellBuilder {
173    data: String,
174    col_span: usize,
175    alignment: Alignment,
176    pad_content: bool,
177}
178
179impl Into<TableCell> for TableCellBuilder {
180    fn into(self) -> TableCell {
181        self.build()
182    }
183}
184
185impl Into<TableCell> for &mut TableCellBuilder {
186    fn into(self) -> TableCell {
187        self.build()
188    }
189}
190
191impl TableCellBuilder {
192    fn new(data: String) -> TableCellBuilder {
193        TableCellBuilder {
194            data,
195            col_span: 1,
196            alignment: Alignment::Left,
197            pad_content: true,
198        }
199    }
200
201    pub fn col_span(&mut self, col_span: usize) -> &mut Self {
202        self.col_span = col_span;
203        self
204    }
205
206    pub fn alignment(&mut self, alignment: Alignment) -> &mut Self {
207        self.alignment = alignment;
208        self
209    }
210
211    pub fn pad_content(&mut self, pad_content: bool) -> &mut Self {
212        self.pad_content = pad_content;
213        self
214    }
215
216    pub fn build(&self) -> TableCell {
217        TableCell {
218            data: self.data.clone(),
219            col_span: self.col_span,
220            alignment: self.alignment,
221            pad_content: self.pad_content,
222        }
223    }
224}
225
226// Taken from https://github.com/mitsuhiko/console
227lazy_static! {
228    static ref STRIP_ANSI_RE: Regex =
229        Regex::new(r"[\x1b\x9b][\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]")
230            .unwrap();
231}
232
233// The width of a string. Strips ansi characters
234pub fn string_width(string: &str) -> usize {
235    let stripped = STRIP_ANSI_RE.replace_all(string, "");
236    stripped.width()
237}