1use lazy_static;
2use regex::Regex;
3use std::cmp;
4use std::collections::HashSet;
5
6use unicode_width::UnicodeWidthChar;
7use unicode_width::UnicodeWidthStr;
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub enum Alignment {
12 Left,
13 Right,
14 Center,
15}
16
17#[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 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 pub fn split_width(&self) -> f32 {
109 let res = self.width() as f32 / self.col_span as f32;
110 res
111 }
112
113 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 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
226lazy_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
233pub fn string_width(string: &str) -> usize {
235 let stripped = STRIP_ANSI_RE.replace_all(string, "");
236 stripped.width()
237}