unicode_prettytable/
table.rs1use crate::util::StringBuffer;
2
3use std::fmt;
4use derive_builder::Builder;
5
6use smart_default::SmartDefault;
8
9const VERTICAL: &str = "\u{2502}"; const HORIZONTAL: &str = "\u{2500}"; const HORIZONTAL_HEADER: &str = "\u{2550}"; const TOP_BRACE: &str = "\u{252C}"; const TOP_BRACE_HEADER: &str = "\u{2564}"; const BOTTOM_BRACE: &str = "\u{2534}"; const LEFT_BRACE: &str = "\u{251c}"; const LEFT_BRACE_HEADER: &str = "\u{255e}"; const RIGHT_BRACE: &str = "\u{2524}"; const RIGHT_BRACE_HEADER: &str = "\u{2561}"; const MIDDLE_BRACE: &str = "\u{253C}"; const MIDDLE_BRACE_HEADER: &str = "\u{256a}"; const TOP_LEFT_CORNER: &str = "\u{250c}"; const TOP_RIGHT_CORNER: &str = "\u{2510}"; const TOP_LEFT_CORNER_HEADER: &str = "\u{2552}"; const TOP_RIGHT_CORNER_HEADER: &str = "\u{2555}"; const BOTTOM_RIGHT_CORNER: &str = "\u{2518}"; const BOTTOM_LEFT_CORNER: &str = "\u{2514}"; #[derive(Builder, Clone, SmartDefault)]
36pub struct Header<'a, T>
37where
38 T: AsRef<str>,
39 &'a T: AsRef<str>
40{
41 double_bar: bool,
43 centered_text: bool,
45 #[builder(setter(strip_option), default)]
48 columns: Option<&'a Vec<T>>
49}
50
51#[derive(Builder)]
52pub struct Table<'a, T>
53where
54 T: AsRef<str>,
55 &'a T: AsRef<str>,
56{
57 #[builder(default)]
59 header: Header<'a, T>,
60 rows: &'a Vec<Vec<T>>,
62}
63
64impl <'a, T> Table<'a, T>
65where
66 T: AsRef<str>,
67 &'a T: AsRef<str>
68{
69 fn generate_horizontal_separators(column_widths: &Vec<usize>, horizontal_char: &str) -> Vec<Vec<u8>> {
71 column_widths
72 .iter()
73 .map(|&length| horizontal_char.repeat(length).into_bytes())
74 .collect::<Vec<_>>()
75 }
76
77 fn get_column_widths(&self, header_columns: &Vec<T>, row_offset: usize) -> Vec<usize>
79 where
80 T: AsRef<str>,
81 &'a T: AsRef<str>
82 {
83 let header_widths: Vec<usize> = {
84 let header_padding = {
85 if self.header.centered_text {
86 2
87 }
88 else {
89 0
90 }
91 };
92
93 header_columns.iter().map(|entry| entry.as_ref().chars().count() + header_padding).collect()
94 };
95
96 let mut widths = self.rows
97 .iter()
98 .map(|row| row
99 .into_iter()
100 .map(|entry| entry.as_ref().chars().count())
101 )
102 .skip(row_offset)
106 .fold(header_widths.clone(), |mut column_widths: Vec<usize>, row| {
107 for (a, b) in column_widths.iter_mut().zip(row) {
108 *a = b.max(*a);
110 }
111
112 column_widths
113 });
114
115 if self.header.centered_text {
116 for (col_width, header_width) in widths.iter_mut().zip(header_widths.into_iter()) {
118 let num_spaces = *col_width - header_width;
119
120 if num_spaces == 0 {
122 *col_width += 2;
123 }
124 else if num_spaces % 2 == 1 {
126 *col_width += 1;
127 }
128 }
129 }
130
131 widths
132 }
133}
134
135impl <'a, T> fmt::Display for Table<'a, T>
136where
137 T: AsRef<str>,
138 &'a T: AsRef<str>,
139{
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 let (header, row_offset) = {
142 if let Some(columns) = self.header.columns {
143 (columns, 0)
144 }
145 else if let Some(columns) = self.rows.get(0) {
146 (columns, 1)
147 }
148 else {
149 return Err(fmt::Error::default());
151 }
152 };
153
154 let column_widths = self.get_column_widths(&header, row_offset);
155
156 let total_width_per_row = {
157 let num_separators_per_row = column_widths.len() + 1;
159
160 let base_width_per_row: usize = column_widths.iter().sum();
161
162 base_width_per_row + num_separators_per_row
163 };
164
165 let string_length = {
166 let total_lines = {
167 let num_rows = self.rows.len();
168
169 let num_separator_lines = num_rows + 1;
171
172 num_separator_lines + num_rows
173 };
174
175 let num_newlines = total_lines - 1;
177
178 (total_width_per_row * total_lines) + num_newlines
179 };
180
181 let mut buffer = StringBuffer::with_capacity_fill(string_length, ' ');
183
184 let standard_horizontal_separators = Self::generate_horizontal_separators(&column_widths, HORIZONTAL);
185 let header_horizontal_separators = Self::generate_horizontal_separators(&column_widths, HORIZONTAL_HEADER);
186
187 let create_sep_row = |horizontal_separators: &Vec<Vec<u8>>, left_char, middle_char, right_char, newline| {
188 let mut sep_buffer = {
189 let newline_increment = if newline { 1 } else { 0 };
191 let capacity = total_width_per_row + newline_increment;
192 StringBuffer::with_capacity(capacity)
193 };
194
195 sep_buffer.push_chars(left_char);
196
197 let (last_sep, seps) = horizontal_separators.split_last().unwrap();
198
199 for sep in seps {
200 sep_buffer.push_bytes(sep);
201 sep_buffer.push_chars(middle_char);
202 }
203
204 sep_buffer.push_bytes(&last_sep);
205 sep_buffer.push_chars(right_char);
206
207 if newline {
208 sep_buffer.push_chars("\n")
209 }
210
211 sep_buffer.into_buffer()
212 };
213
214 let mut input_iterator = self.rows.iter().skip(row_offset).peekable();
215
216 macro_rules! push_data_row {
217 ($row_yielder:expr, $col_formatter:expr) => {
218 if let Some(row) = $row_yielder {
219 buffer.push_chars(VERTICAL);
220 for (col_index, col) in row.into_iter().enumerate() {
221 let base = col.as_ref();
222
223 buffer.push_chars_fixed_width($col_formatter(base, column_widths[col_index]), column_widths[col_index]);
224 buffer.push_chars(VERTICAL);
225 }
226
227 buffer.push_chars("\n");
228 }
229 };
230 ($col_formatter:expr) => {
232 push_data_row!(input_iterator.next(), $col_formatter)
233 }
234 }
235
236 let standard_separator = create_sep_row(&standard_horizontal_separators, LEFT_BRACE, MIDDLE_BRACE, RIGHT_BRACE, true);
237
238 macro_rules! push_header_data_row {
239 () => (
240 if self.header.centered_text {
241 push_data_row!(Some(header), |base_str, width| format!("{:^width$}", base_str, width = width));
243 }
244 else {
245 push_data_row!(Some(header), |base_str, _| base_str);
246 }
247 )
248 }
249
250 if self.header.double_bar {
251 let header_top = create_sep_row(&header_horizontal_separators, TOP_LEFT_CORNER_HEADER, TOP_BRACE_HEADER, TOP_RIGHT_CORNER_HEADER, true);
252 buffer.push_bytes(&header_top);
253
254 push_header_data_row!();
255
256 let header_bottom = create_sep_row(&header_horizontal_separators, LEFT_BRACE_HEADER, MIDDLE_BRACE_HEADER, RIGHT_BRACE_HEADER, true);
257 buffer.push_bytes(&header_bottom);
258
259 }
263 else {
264 let header = create_sep_row(&standard_horizontal_separators, TOP_LEFT_CORNER, TOP_BRACE, TOP_RIGHT_CORNER, true);
265 buffer.push_bytes(&header);
267 push_header_data_row!();
268
269 if input_iterator.peek().is_some() {
271 buffer.push_bytes(&standard_separator);
272 }
273 }
274
275 let footer = create_sep_row(&standard_horizontal_separators, BOTTOM_LEFT_CORNER, BOTTOM_BRACE, BOTTOM_RIGHT_CORNER, false);
276
277 loop {
278 push_data_row!(|base_str, _| base_str);
279
280 if input_iterator.peek().is_some() {
282 buffer.push_bytes(&standard_separator);
283 }
284 else {
285 break;
287 }
288 }
289
290 buffer.push_bytes(&footer);
292
293 write!(f, "{}", buffer.to_string())
294 }
295}