1use super::*;
2
3#[derive(Debug, Clone, PartialEq)]
4pub struct Table {
5 cols: Vec<Column>,
6 rows: Vec<Row>,
7}
8
9impl Table {
10 pub fn new(columns: Vec<Column>) -> Self {
11 Self {
12 cols: columns,
13 rows: Vec::new(),
14 }
15 }
16 pub fn append_row(&mut self, row: Row) {
17 self.rows.push(row);
18 }
19
20 fn get_border(
21 &self,
22 row_idx: usize,
23 col_idx: usize,
24 horizontal: Option<Border>,
25 vertical: Option<Border>,
26 ) -> &str {
27 match (horizontal, vertical) {
28 (None, None) => panic!(""),
29 (None, Some(vertical)) => match vertical {
30 Border::Single => "│",
31 Border::Double => "║",
32 },
33 (Some(horizontal), None) => match horizontal {
34 Border::Single => "─",
35 Border::Double => "═",
36 },
37 (Some(horizontal), Some(vertical)) => {
38 let mut adjacent_border = 0;
40 if row_idx > 0 {
41 adjacent_border |= 0b0001; }
43 if row_idx + 1 < self.rows.len() {
44 adjacent_border |= 0b0100; }
46 if col_idx > 0 {
47 adjacent_border |= 0b1000; }
49 if col_idx + 1 < self.cols.len() {
50 adjacent_border |= 0b0010; }
52
53 use Border::*;
54 match adjacent_border {
55 0b0110 => match (horizontal, vertical) {
56 (Single, Single) => "┌",
57 (Double, Double) => "╔",
58 (Single, Double) => "╓",
59 (Double, Single) => "╒",
60 },
61 0b1110 => match (horizontal, vertical) {
62 (Single, Single) => "┬",
63 (Double, Double) => "╦",
64 (Single, Double) => "╥",
65 (Double, Single) => "╤",
66 },
67 0b1100 => match (horizontal, vertical) {
68 (Single, Single) => "┐",
69 (Double, Double) => "╗",
70 (Single, Double) => "╖",
71 (Double, Single) => "╕",
72 },
73 0b0111 => match (horizontal, vertical) {
74 (Single, Single) => "├",
75 (Double, Double) => "╠",
76 (Single, Double) => "╟",
77 (Double, Single) => "╞",
78 },
79 0b1111 => match (horizontal, vertical) {
80 (Single, Single) => "┼",
81 (Double, Double) => "╬",
82 (Single, Double) => "╫",
83 (Double, Single) => "╪",
84 },
85 0b1101 => match (horizontal, vertical) {
86 (Single, Single) => "┤",
87 (Double, Double) => "╣",
88 (Single, Double) => "╢",
89 (Double, Single) => "╡",
90 },
91 0b0011 => match (horizontal, vertical) {
92 (Single, Single) => "└",
93 (Double, Double) => "╚",
94 (Single, Double) => "╙",
95 (Double, Single) => "╘",
96 },
97 0b1011 => match (horizontal, vertical) {
98 (Single, Single) => "┴",
99 (Double, Double) => "╩",
100 (Single, Double) => "╨",
101 (Double, Single) => "╧",
102 },
103 0b1001 => match (horizontal, vertical) {
104 (Single, Single) => "┘",
105 (Double, Double) => "╝",
106 (Single, Double) => "╜",
107 (Double, Single) => "╛",
108 },
109 0b0010 | 0b1000 => match (horizontal, vertical) {
110 (Single, _) => "─",
111 (Double, _) => "═",
112 },
113 0b0001 | 0b0100 => match (horizontal, vertical) {
114 (_, Single) => "│",
115 (_, Double) => "║",
116 },
117 _ => unimplemented!(),
118 }
119 }
120 }
121 }
122}
123
124fn fill(s: &str, width: usize) -> String {
125 use unicode_width::UnicodeWidthStr;
126 let s_width = s.width();
127 assert!(width % s_width == 0);
128 let times = width / s_width;
129 s.repeat(times)
130}
131
132impl std::fmt::Display for Table {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 use unicode_width::UnicodeWidthStr;
135
136 let widths: Vec<usize> = {
138 let mut ws = Vec::new();
139 let mut cell_idx = 0;
140 for col in self.cols.iter() {
141 match col {
142 Column::VerticalBorder(_) => ws.push(1),
143 Column::Cells { width } => {
144 match width {
145 CellSize::Flexible => {
146 let max_width = self
148 .rows
149 .iter()
150 .filter_map(|row| {
151 row.cells().and_then(|cells| cells.get(cell_idx))
152 })
153 .map(|cell| cell.value.width() + cell.align.padding_size())
154 .fold(1 , std::cmp::max);
155 ws.push(max_width);
156 }
157 CellSize::Fixed(w) => ws.push(*w),
158 }
159 cell_idx += 1;
160 }
161 }
162 }
163 ws
164 };
165
166 for (ri, row) in self.rows.iter().enumerate() {
168 match row {
169 Row::HorizontalBorder(_) => {
170 for (ci, col) in self.cols.iter().enumerate() {
171 let c = self.get_border(ri, ci, row.border(), col.border());
172 write!(f, "{}", fill(c, widths[ci]))?;
173 }
174 writeln!(f)?;
175 }
176 Row::Cells { height, cells } => {
177 use std::borrow::Cow;
178 enum ActualContent<'a> {
179 Border(String),
180 Text(Vec<Cow<'a, str>>),
181 }
182 struct ActualCell<'a> {
183 width: usize,
184 align: Align,
185 style: ansi_term::Style,
186 content: ActualContent<'a>,
187 }
188
189 let cells: Vec<ActualCell> = {
190 let mut buf = Vec::new();
191 let mut cells_iter = cells.iter();
192 for (ci, col) in self.cols.iter().enumerate() {
193 match col {
194 Column::Cells { .. } => match cells_iter.next() {
195 Some(cell) => {
196 let width = widths[ci];
197 let wrap_opts = textwrap::Options::with_splitter(
198 width - cell.align.padding_size(),
199 textwrap::NoHyphenation,
200 );
201 buf.push(ActualCell {
202 width: width + cell.align.padding_size(),
203 align: cell.align,
204 style: cell.style,
205 content: ActualContent::Text(textwrap::wrap(
206 cell.value.as_str(),
207 wrap_opts,
208 )),
209 });
210 }
211 None => {
212 buf.push(ActualCell {
214 width: widths[ci],
215 align: Align::default(),
216 style: ansi_term::Style::default(),
217 content: ActualContent::Text(Vec::new()),
218 });
219 }
220 },
221 Column::VerticalBorder(b) => {
222 let border_str = self.get_border(ri, ci, None, Some(*b));
223 let repeated = fill(border_str, widths[ci]);
224 let align = Align::default();
225 buf.push(ActualCell {
226 width: widths[ci] + align.padding_size(),
227 align: Align::default(),
228 style: ansi_term::Style::default(),
229 content: ActualContent::Border(repeated),
230 });
231 }
232 }
233 }
234 buf
235 };
236
237 let height = match height {
238 CellSize::Flexible => {
239 cells
241 .iter()
242 .filter_map(|actual| match &actual.content {
243 ActualContent::Text(text) => Some(text.len()),
244 _ => None,
245 })
246 .fold(1, std::cmp::max)
247 }
248 CellSize::Fixed(h) => *h,
249 };
250
251 let mut lines = vec![String::new(); height];
252 for actual in cells {
253 match actual.content {
254 ActualContent::Text(text) => {
255 let text =
256 text.into_iter().chain(std::iter::repeat(Cow::Borrowed("")));
257 for (buf, w) in lines.iter_mut().zip(text) {
258 let sz = w.as_ref().width();
259 let pad = actual.width - actual.align.padding_size() - sz;
260 let (padl, padr) = match actual.align {
261 Align::Center => (pad / 2, pad - pad / 2),
262 Align::Left => (0, pad),
263 Align::Right => (pad, 0),
264 Align::CenterPadded { padl, padr } => {
265 (padl + (pad - padl - padr), padr)
266 }
267 Align::LeftPadded { padl } => (padl, pad - padl),
268 Align::RightPadded { padr } => (pad - padr, padr),
269 };
270 assert_eq!(pad, padl + padr);
271 buf.push_str(&fill(" ", padl));
272 buf.push_str(&actual.style.paint(w.as_ref()).to_string());
273 buf.push_str(&fill(" ", padr));
274 }
275 }
276 ActualContent::Border(border) => {
277 for buf in lines.iter_mut() {
278 buf.push_str(
279 &actual
280 .style
281 .paint(fill(&border, actual.width))
282 .to_string(),
283 );
284 }
285 }
286 }
287 }
288 for line in lines {
289 writeln!(f, "{}", line)?;
290 }
291 }
292 }
293 }
294 Ok(())
295 }
296}