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 != ' ' || self.top_right != ' '
78 || self.bottom_left != ' ' || self.bottom_right != ' '
79 }
80
81 pub fn from_str(box_str: &str, ascii: bool) -> Self {
83 let lines: Vec<&str> = box_str.lines().collect();
84 assert_eq!(lines.len(), 8, "Box definition must have exactly 8 lines");
85
86 let line_chars: Vec<Vec<char>> = lines.iter()
87 .map(|l| l.chars().collect())
88 .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], top: l[0][1], top_divider: l[0][2], top_right: l[0][3],
98 head_left: l[1][0], head_horizontal: l[1][1], head_vertical: l[1][2], head_right: l[1][3],
99 head_row_left: l[2][0], head_row_horizontal: l[2][1], head_row_cross: l[2][2], head_row_right: l[2][3],
100 mid_left: l[3][0], mid_horizontal: l[3][1], mid_vertical: l[3][2], mid_right: l[3][3],
101 row_left: l[4][0], row_horizontal: l[4][1], row_cross: l[4][2], row_right: l[4][3],
102 foot_row_left: l[5][0], foot_row_horizontal: l[5][1], foot_row_cross: l[5][2], foot_row_right: l[5][3],
103 foot_left: l[6][0], foot_horizontal: l[6][1], foot_vertical: l[6][2], foot_right: l[6][3],
104 bottom_left: l[7][0], bottom: l[7][1], bottom_divider: l[7][2], bottom_right: l[7][3],
105 ascii,
106 }
107 }
108
109 pub fn to_string(&self) -> String {
111 format!(
112 "{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}",
113 self.top_left, self.top, self.top_divider, self.top_right,
114 self.head_left, self.head_horizontal, self.head_vertical, self.head_right,
115 self.head_row_left, self.head_row_horizontal, self.head_row_cross, self.head_row_right,
116 self.mid_left, self.mid_horizontal, self.mid_vertical, self.mid_right,
117 self.row_left, self.row_horizontal, self.row_cross, self.row_right,
118 self.foot_row_left, self.foot_row_horizontal, self.foot_row_cross, self.foot_row_right,
119 self.foot_left, self.foot_horizontal, self.foot_vertical, self.foot_right,
120 self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right,
121 )
122 }
123}
124
125impl std::fmt::Display for BoxStyle {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 write!(f, "{}", self.to_string())
128 }
129}
130
131pub const ASCII: &str = "\
137+--+
138| ||
139|-+|
140| ||
141|-+|
142|-+|
143| ||
144+--+";
145
146pub const ASCII2: &str = "\
148+-++
149| ||
150+-++
151| ||
152+-++
153+-++
154| ||
155+-++";
156
157pub const SQUARE_DOUBLE_HEAD: &str = "\
159┌─┬┐
160│ ││
161╞═╪╡
162│ ││
163├─┼┤
164├─┼┤
165│ ││
166└─┴┘";
167
168pub const MINIMAL_DOUBLE_HEAD: &str = " ╷ \n │ \n ═╪ \n │ \n ─┼ \n ─┼ \n │ \n ╵ ";
170
171pub const SIMPLE_HEAD: &str = " \n \n ── \n \n \n \n \n ";
173
174pub const ASCII_DOUBLE_HEAD: &str = "\
176+-++
177| ||
178+=++
179| ||
180+-++
181+-++
182| ||
183+-++";
184
185pub const ROUNDED: &str = "\
187╭─┬╮
188│ ││
189├─┼┤
190│ ││
191├─┼┤
192├─┼┤
193│ ││
194╰─┴╯";
195
196pub const SQUARE: &str = "\
198┌─┬┐
199│ ││
200├─┼┤
201│ ││
202├─┼┤
203├─┼┤
204│ ││
205└─┴┘";
206
207pub const HEAVY: &str = "\
209┏━┳┓
210┃ ┃┃
211┣━╋┫
212┃ ┃┃
213┣━╋┫
214┣━╋┫
215┃ ┃┃
216┗━┻┛";
217
218pub const HEAVY_EDGE: &str = "\
220┏━┯┓
221┃ │┃
222┠─┼┨
223┃ │┃
224┠─┼┨
225┠─┼┨
226┃ │┃
227┗━┷┛";
228
229pub const HEAVY_HEAD: &str = "\
231┏━┳┓
232┃ ┃┃
233┡━╇┩
234│ ││
235├─┼┤
236├─┼┤
237│ ││
238└─┴┘";
239
240pub const DOUBLE: &str = "\
242╔═╦╗
243║ ║║
244╠═╬╣
245║ ║║
246╠═╬╣
247╠═╬╣
248║ ║║
249╚═╩╝";
250
251pub const DOUBLE_EDGE: &str = "\
253╔═╤╗
254║ │║
255╟─┼╢
256║ │║
257╟─┼╢
258╟─┼╢
259║ │║
260╚═╧╝";
261
262pub const SIMPLE: &str = " \n \n ── \n \n \n ── \n \n ";
264
265pub const SIMPLE_HEAVY: &str = " \n \n ━━ \n \n \n ━━ \n \n ";
267
268pub const MINIMAL: &str = " ╷ \n │ \n╶─┼╴\n │ \n╶─┼╴\n╶─┼╴\n │ \n ╵ ";
270
271pub const MINIMAL_HEAVY: &str = " ╷ \n │ \n╺━┿╸\n │ \n╶─┼╴\n╶─┼╴\n │ \n ╵ ";
273
274use once_cell::sync::Lazy;
279
280pub static BOX_ROUNDED: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(ROUNDED, false));
282pub static BOX_SQUARE: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(SQUARE, false));
284pub static BOX_HEAVY: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(HEAVY, false));
286pub static BOX_HEAVY_EDGE: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(HEAVY_EDGE, false));
288pub static BOX_HEAVY_HEAD: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(HEAVY_HEAD, false));
290pub static BOX_DOUBLE: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(DOUBLE, false));
292pub static BOX_DOUBLE_EDGE: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(DOUBLE_EDGE, false));
294pub static BOX_SIMPLE: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(SIMPLE, false));
296pub static BOX_SIMPLE_HEAVY: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(SIMPLE_HEAVY, false));
298pub static BOX_MINIMAL: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(MINIMAL, false));
300pub static BOX_MINIMAL_HEAVY: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(MINIMAL_HEAVY, false));
302pub static BOX_ASCII: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(ASCII, true));
304pub static BOX_ASCII2: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(ASCII2, true));
306pub static BOX_SQUARE_DOUBLE_HEAD: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(SQUARE_DOUBLE_HEAD, false));
308pub static BOX_MINIMAL_DOUBLE_HEAD: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(MINIMAL_DOUBLE_HEAD, false));
310pub static BOX_SIMPLE_HEAD: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(SIMPLE_HEAD, false));
312pub static BOX_ASCII_DOUBLE_HEAD: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(ASCII_DOUBLE_HEAD, true));
314
315pub const MARKDOWN: &str = " \n| ||\n|-||\n| ||\n|-||\n|-||\n| ||\n ";
321
322pub static BOX_MARKDOWN: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(MARKDOWN, false));
324
325pub fn get_safe_box(box_style: &BoxStyle, ascii_only: bool) -> BoxStyle {
331 if ascii_only && !box_style.ascii {
332 BOX_ASCII.clone()
333 } else {
334 box_style.clone()
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn test_rounded_box() {
344 let b = &*BOX_ROUNDED;
345 assert_eq!(b.top_left, '╭');
346 assert_eq!(b.bottom_right, '╯');
347 }
348
349 #[test]
350 fn test_box_from_str() {
351 let b = BoxStyle::from_str(ROUNDED, false);
352 assert_eq!(b, *BOX_ROUNDED);
353 }
354
355 #[test]
356 fn test_new_box_styles_parse() {
357 let _ = &*BOX_SQUARE_DOUBLE_HEAD;
359 let _ = &*BOX_MINIMAL_DOUBLE_HEAD;
360 let _ = &*BOX_SIMPLE_HEAD;
361 let _ = &*BOX_ASCII_DOUBLE_HEAD;
362
363 let sq = &*BOX_SQUARE_DOUBLE_HEAD;
365 assert_eq!(sq.top_left, '┌');
366 assert_eq!(sq.head_row_horizontal, '═');
367 assert_eq!(sq.head_row_left, '╞');
368
369 let ac = &*BOX_ASCII_DOUBLE_HEAD;
370 assert_eq!(ac.head_row_left, '+');
371 assert_eq!(ac.head_row_horizontal, '=');
372 assert_eq!(ac.row_left, '+');
373 }
374}