1use terminal_size::{terminal_size, Width};
2use console::measure_text_width;
3
4pub enum TableMode {
5 Flex,
6 Fixed(usize),
7 Full,
8}
9
10pub enum TableStyle {
11 Ascii,
12 Rounded,
13 Heavy,
14}
15
16pub fn render_table(headers: &[&str], rows: &[Vec<&str>], mode: TableMode, style: TableStyle) {
17 let term_width = terminal_size().map(|(Width(w), _)| w as usize).unwrap_or(80);
18 let col_count = headers.len().max(1);
19 let padding = 1;
20 let total_padding = (col_count - 1) * padding;
21
22 let col_width = match mode {
23 TableMode::Fixed(width) => width,
24 TableMode::Full => {
25 let border_space = col_count + 1; let usable = term_width.saturating_sub(border_space);
27 usable / col_count
28 },
29 TableMode::Flex => {
30 let content_max = headers.iter()
31 .map(|h| measure_text_width(h))
32 .chain(rows.iter().flat_map(|r| r.iter().map(|c| measure_text_width(c))))
33 .max()
34 .unwrap_or(10);
35 content_max.min((term_width.saturating_sub(total_padding)) / col_count)
36 }
37 };
38
39 let border = match style {
40 TableStyle::Ascii => BorderSet::ascii(),
41 TableStyle::Rounded => BorderSet::rounded(),
42 TableStyle::Heavy => BorderSet::heavy(),
43 };
44
45 print!("{}", border.top_left);
47 for i in 0..col_count {
48 print!("{}", border.horizontal.to_string().repeat(col_width));
49 if i < col_count - 1 {
50 print!("{}", border.top_cross);
51 }
52 }
53 println!("{}", border.top_right);
54
55 print!("{}", border.vertical);
57 for (_i, h) in headers.iter().enumerate() {
58 print!("{}{}", pad_cell(h, col_width), border.vertical);
59 }
60 println!();
61
62 print!("{}", border.mid_left);
64 for i in 0..col_count {
65 print!("{}", border.inner_horizontal.to_string().repeat(col_width));
66 if i < col_count - 1 {
67 print!("{}", border.mid_cross);
68 }
69 }
70 println!("{}", border.mid_right);
71
72 for row in rows {
74 print!("{}", border.vertical);
75 for cell in row {
76 print!("{}{}", pad_cell(cell, col_width), border.vertical);
77 }
78 println!();
79 }
80
81 print!("{}", border.bottom_left);
83 for i in 0..col_count {
84 print!("{}", border.horizontal.to_string().repeat(col_width));
85 if i < col_count - 1 {
86 print!("{}", border.bottom_cross);
87 }
88 }
89 println!("{}", border.bottom_right);
90}
91
92fn pad_cell(cell: &str, width: usize) -> String {
93 let visual = measure_text_width(cell);
94 let pad = width.saturating_sub(visual);
95 format!("{}{}", cell, " ".repeat(pad))
96}
97
98struct BorderSet {
99 top_left: char,
100 top_right: char,
101 bottom_left: char,
102 bottom_right: char,
103 top_cross: char,
104 bottom_cross: char,
105 mid_cross: char,
106 mid_left: char,
107 mid_right: char,
108 horizontal: char,
109 inner_horizontal: char,
110 vertical: char,
111}
112
113impl BorderSet {
114 fn ascii() -> Self {
115 Self {
116 top_left: '+',
117 top_right: '+',
118 bottom_left: '+',
119 bottom_right: '+',
120 top_cross: '+',
121 bottom_cross: '+',
122 mid_cross: '+',
123 mid_left: '+',
124 mid_right: '+',
125 horizontal: '-',
126 inner_horizontal: '-',
127 vertical: '|',
128 }
129 }
130
131 fn rounded() -> Self {
132 Self {
133 top_left: '╭',
134 top_right: '╮',
135 bottom_left: '╰',
136 bottom_right: '╯',
137 top_cross: '┬',
138 bottom_cross: '┴',
139 mid_cross: '┼',
140 mid_left: '├',
141 mid_right: '┤',
142 horizontal: '─',
143 inner_horizontal: '─',
144 vertical: '│',
145 }
146 }
147
148 fn heavy() -> Self {
149 Self {
150 top_left: '┏',
151 top_right: '┓',
152 bottom_left: '┗',
153 bottom_right: '┛',
154 top_cross: '┳',
155 bottom_cross: '┻',
156 mid_cross: '╋',
157 mid_left: '┣',
158 mid_right: '┫',
159 horizontal: '━',
160 inner_horizontal: '━',
161 vertical: '┃',
162 }
163 }
164}