1#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct BoxStyle {
26 pub top_left: char,
28 pub top: char,
29 pub top_divider: char,
30 pub top_right: char,
31 pub head_left: char,
33 pub head_horizontal: char,
34 pub head_vertical: char,
35 pub head_right: char,
36 pub head_row_left: char,
38 pub head_row_horizontal: char,
39 pub head_row_cross: char,
40 pub head_row_right: char,
41 pub mid_left: char,
43 pub mid_horizontal: char,
44 pub mid_vertical: char,
45 pub mid_right: char,
46 pub row_left: char,
48 pub row_horizontal: char,
49 pub row_cross: char,
50 pub row_right: char,
51 pub foot_row_left: char,
53 pub foot_row_horizontal: char,
54 pub foot_row_cross: char,
55 pub foot_row_right: char,
56 pub foot_left: char,
58 pub foot_horizontal: char,
59 pub foot_vertical: char,
60 pub foot_right: char,
61 pub bottom_left: char,
63 pub bottom: char,
64 pub bottom_divider: char,
65 pub bottom_right: char,
66 pub ascii: bool,
68}
69
70impl BoxStyle {
71 pub fn has_visible_edges(&self) -> bool {
76 self.top_left != ' '
78 || self.top_right != ' '
79 || self.bottom_left != ' '
80 || self.bottom_right != ' '
81 }
82
83 pub fn from_str(box_str: &str, ascii: bool) -> Self {
85 let lines: Vec<&str> = box_str.lines().collect();
86 assert_eq!(lines.len(), 8, "Box definition must have exactly 8 lines");
87
88 let line_chars: Vec<Vec<char>> = lines.iter().map(|l| l.chars().collect()).collect();
89
90 for (i, chars) in line_chars.iter().enumerate() {
92 assert_eq!(chars.len(), 4, "Line {i} must have exactly 4 characters");
93 }
94
95 let l = &line_chars;
96 Self {
97 top_left: l[0][0],
98 top: l[0][1],
99 top_divider: l[0][2],
100 top_right: l[0][3],
101 head_left: l[1][0],
102 head_horizontal: l[1][1],
103 head_vertical: l[1][2],
104 head_right: l[1][3],
105 head_row_left: l[2][0],
106 head_row_horizontal: l[2][1],
107 head_row_cross: l[2][2],
108 head_row_right: l[2][3],
109 mid_left: l[3][0],
110 mid_horizontal: l[3][1],
111 mid_vertical: l[3][2],
112 mid_right: l[3][3],
113 row_left: l[4][0],
114 row_horizontal: l[4][1],
115 row_cross: l[4][2],
116 row_right: l[4][3],
117 foot_row_left: l[5][0],
118 foot_row_horizontal: l[5][1],
119 foot_row_cross: l[5][2],
120 foot_row_right: l[5][3],
121 foot_left: l[6][0],
122 foot_horizontal: l[6][1],
123 foot_vertical: l[6][2],
124 foot_right: l[6][3],
125 bottom_left: l[7][0],
126 bottom: l[7][1],
127 bottom_divider: l[7][2],
128 bottom_right: l[7][3],
129 ascii,
130 }
131 }
132
133 pub fn to_plain_text(&self) -> String {
135 format!(
136 "{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}",
137 self.top_left,
138 self.top,
139 self.top_divider,
140 self.top_right,
141 self.head_left,
142 self.head_horizontal,
143 self.head_vertical,
144 self.head_right,
145 self.head_row_left,
146 self.head_row_horizontal,
147 self.head_row_cross,
148 self.head_row_right,
149 self.mid_left,
150 self.mid_horizontal,
151 self.mid_vertical,
152 self.mid_right,
153 self.row_left,
154 self.row_horizontal,
155 self.row_cross,
156 self.row_right,
157 self.foot_row_left,
158 self.foot_row_horizontal,
159 self.foot_row_cross,
160 self.foot_row_right,
161 self.foot_left,
162 self.foot_horizontal,
163 self.foot_vertical,
164 self.foot_right,
165 self.bottom_left,
166 self.bottom,
167 self.bottom_divider,
168 self.bottom_right,
169 )
170 }
171}
172
173impl std::fmt::Display for BoxStyle {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 write!(f, "{}", self.to_plain_text())
176 }
177}
178
179pub const ASCII: &str = "\
185+--+
186| ||
187|-+|
188| ||
189|-+|
190|-+|
191| ||
192+--+";
193
194pub const ASCII2: &str = "\
196+-++
197| ||
198+-++
199| ||
200+-++
201+-++
202| ||
203+-++";
204
205pub const SQUARE_DOUBLE_HEAD: &str = "\
207┌─┬┐
208│ ││
209╞═╪╡
210│ ││
211├─┼┤
212├─┼┤
213│ ││
214└─┴┘";
215
216pub const MINIMAL_DOUBLE_HEAD: &str = " ╷ \n │ \n ═╪ \n │ \n ─┼ \n ─┼ \n │ \n ╵ ";
218
219pub const SIMPLE_HEAD: &str = " \n \n ── \n \n \n \n \n ";
221
222pub const ASCII_DOUBLE_HEAD: &str = "\
224+-++
225| ||
226+=++
227| ||
228+-++
229+-++
230| ||
231+-++";
232
233pub const ROUNDED: &str = "\
235╭─┬╮
236│ ││
237├─┼┤
238│ ││
239├─┼┤
240├─┼┤
241│ ││
242╰─┴╯";
243
244pub const SQUARE: &str = "\
246┌─┬┐
247│ ││
248├─┼┤
249│ ││
250├─┼┤
251├─┼┤
252│ ││
253└─┴┘";
254
255pub const HEAVY: &str = "\
257┏━┳┓
258┃ ┃┃
259┣━╋┫
260┃ ┃┃
261┣━╋┫
262┣━╋┫
263┃ ┃┃
264┗━┻┛";
265
266pub const HEAVY_EDGE: &str = "\
268┏━┯┓
269┃ │┃
270┠─┼┨
271┃ │┃
272┠─┼┨
273┠─┼┨
274┃ │┃
275┗━┷┛";
276
277pub const HEAVY_HEAD: &str = "\
279┏━┳┓
280┃ ┃┃
281┡━╇┩
282│ ││
283├─┼┤
284├─┼┤
285│ ││
286└─┴┘";
287
288pub const DOUBLE: &str = "\
290╔═╦╗
291║ ║║
292╠═╬╣
293║ ║║
294╠═╬╣
295╠═╬╣
296║ ║║
297╚═╩╝";
298
299pub const DOUBLE_EDGE: &str = "\
301╔═╤╗
302║ │║
303╟─┼╢
304║ │║
305╟─┼╢
306╟─┼╢
307║ │║
308╚═╧╝";
309
310pub const SIMPLE: &str = " \n \n ── \n \n \n ── \n \n ";
312
313pub const SIMPLE_HEAVY: &str = " \n \n ━━ \n \n \n ━━ \n \n ";
315
316pub const MINIMAL: &str = " ╷ \n │ \n╶─┼╴\n │ \n╶─┼╴\n╶─┼╴\n │ \n ╵ ";
318
319pub const MINIMAL_HEAVY: &str = " ╷ \n │ \n╺━┿╸\n │ \n╶─┼╴\n╶─┼╴\n │ \n ╵ ";
321
322use std::sync::LazyLock;
327
328pub static BOX_ROUNDED: LazyLock<BoxStyle> = LazyLock::new(|| BoxStyle::from_str(ROUNDED, false));
330pub static BOX_SQUARE: LazyLock<BoxStyle> = LazyLock::new(|| BoxStyle::from_str(SQUARE, false));
332pub static BOX_HEAVY: LazyLock<BoxStyle> = LazyLock::new(|| BoxStyle::from_str(HEAVY, false));
334pub static BOX_HEAVY_EDGE: LazyLock<BoxStyle> =
336 LazyLock::new(|| BoxStyle::from_str(HEAVY_EDGE, false));
337pub static BOX_HEAVY_HEAD: LazyLock<BoxStyle> =
339 LazyLock::new(|| BoxStyle::from_str(HEAVY_HEAD, false));
340pub static BOX_DOUBLE: LazyLock<BoxStyle> = LazyLock::new(|| BoxStyle::from_str(DOUBLE, false));
342pub static BOX_DOUBLE_EDGE: LazyLock<BoxStyle> =
344 LazyLock::new(|| BoxStyle::from_str(DOUBLE_EDGE, false));
345pub static BOX_SIMPLE: LazyLock<BoxStyle> = LazyLock::new(|| BoxStyle::from_str(SIMPLE, false));
347pub static BOX_SIMPLE_HEAVY: LazyLock<BoxStyle> =
349 LazyLock::new(|| BoxStyle::from_str(SIMPLE_HEAVY, false));
350pub static BOX_MINIMAL: LazyLock<BoxStyle> = LazyLock::new(|| BoxStyle::from_str(MINIMAL, false));
352pub static BOX_MINIMAL_HEAVY: LazyLock<BoxStyle> =
354 LazyLock::new(|| BoxStyle::from_str(MINIMAL_HEAVY, false));
355pub static BOX_ASCII: LazyLock<BoxStyle> = LazyLock::new(|| BoxStyle::from_str(ASCII, true));
357pub static BOX_ASCII2: LazyLock<BoxStyle> = LazyLock::new(|| BoxStyle::from_str(ASCII2, true));
359pub static BOX_SQUARE_DOUBLE_HEAD: LazyLock<BoxStyle> =
361 LazyLock::new(|| BoxStyle::from_str(SQUARE_DOUBLE_HEAD, false));
362pub static BOX_MINIMAL_DOUBLE_HEAD: LazyLock<BoxStyle> =
364 LazyLock::new(|| BoxStyle::from_str(MINIMAL_DOUBLE_HEAD, false));
365pub static BOX_SIMPLE_HEAD: LazyLock<BoxStyle> =
367 LazyLock::new(|| BoxStyle::from_str(SIMPLE_HEAD, false));
368pub static BOX_ASCII_DOUBLE_HEAD: LazyLock<BoxStyle> =
370 LazyLock::new(|| BoxStyle::from_str(ASCII_DOUBLE_HEAD, true));
371
372pub const MARKDOWN: &str = " \n| ||\n|-||\n| ||\n|-||\n|-||\n| ||\n ";
378
379pub static BOX_MARKDOWN: LazyLock<BoxStyle> = LazyLock::new(|| BoxStyle::from_str(MARKDOWN, false));
381
382pub fn get_safe_box(box_style: &BoxStyle, ascii_only: bool) -> BoxStyle {
388 if ascii_only && !box_style.ascii {
389 BOX_ASCII.clone()
390 } else {
391 box_style.clone()
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 #[test]
400 fn test_rounded_box() {
401 let b = &*BOX_ROUNDED;
402 assert_eq!(b.top_left, '╭');
403 assert_eq!(b.bottom_right, '╯');
404 }
405
406 #[test]
407 fn test_box_from_str() {
408 let b = BoxStyle::from_str(ROUNDED, false);
409 assert_eq!(b, *BOX_ROUNDED);
410 }
411
412 #[test]
413 fn test_new_box_styles_parse() {
414 let _ = &*BOX_SQUARE_DOUBLE_HEAD;
416 let _ = &*BOX_MINIMAL_DOUBLE_HEAD;
417 let _ = &*BOX_SIMPLE_HEAD;
418 let _ = &*BOX_ASCII_DOUBLE_HEAD;
419
420 let sq = &*BOX_SQUARE_DOUBLE_HEAD;
422 assert_eq!(sq.top_left, '┌');
423 assert_eq!(sq.head_row_horizontal, '═');
424 assert_eq!(sq.head_row_left, '╞');
425
426 let ac = &*BOX_ASCII_DOUBLE_HEAD;
427 assert_eq!(ac.head_row_left, '+');
428 assert_eq!(ac.head_row_horizontal, '=');
429 assert_eq!(ac.row_left, '+');
430 }
431}