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 from_str(box_str: &str, ascii: bool) -> Self {
73 let lines: Vec<&str> = box_str.lines().collect();
74 assert_eq!(lines.len(), 8, "Box definition must have exactly 8 lines");
75
76 let line_chars: Vec<Vec<char>> = lines.iter()
77 .map(|l| l.chars().collect())
78 .collect();
79
80 for (i, chars) in line_chars.iter().enumerate() {
82 assert_eq!(chars.len(), 4, "Line {i} must have exactly 4 characters");
83 }
84
85 let l = &line_chars;
86 Self {
87 top_left: l[0][0], top: l[0][1], top_divider: l[0][2], top_right: l[0][3],
88 head_left: l[1][0], head_horizontal: l[1][1], head_vertical: l[1][2], head_right: l[1][3],
89 head_row_left: l[2][0], head_row_horizontal: l[2][1], head_row_cross: l[2][2], head_row_right: l[2][3],
90 mid_left: l[3][0], mid_horizontal: l[3][1], mid_vertical: l[3][2], mid_right: l[3][3],
91 row_left: l[4][0], row_horizontal: l[4][1], row_cross: l[4][2], row_right: l[4][3],
92 foot_row_left: l[5][0], foot_row_horizontal: l[5][1], foot_row_cross: l[5][2], foot_row_right: l[5][3],
93 foot_left: l[6][0], foot_horizontal: l[6][1], foot_vertical: l[6][2], foot_right: l[6][3],
94 bottom_left: l[7][0], bottom: l[7][1], bottom_divider: l[7][2], bottom_right: l[7][3],
95 ascii,
96 }
97 }
98
99 pub fn to_string(&self) -> String {
101 format!(
102 "{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}\n{}{}{}{}",
103 self.top_left, self.top, self.top_divider, self.top_right,
104 self.head_left, self.head_horizontal, self.head_vertical, self.head_right,
105 self.head_row_left, self.head_row_horizontal, self.head_row_cross, self.head_row_right,
106 self.mid_left, self.mid_horizontal, self.mid_vertical, self.mid_right,
107 self.row_left, self.row_horizontal, self.row_cross, self.row_right,
108 self.foot_row_left, self.foot_row_horizontal, self.foot_row_cross, self.foot_row_right,
109 self.foot_left, self.foot_horizontal, self.foot_vertical, self.foot_right,
110 self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right,
111 )
112 }
113}
114
115impl std::fmt::Display for BoxStyle {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 write!(f, "{}", self.to_string())
118 }
119}
120
121pub const ASCII: &str = "\
127+--+
128| ||
129|-+|
130| ||
131|-+|
132|-+|
133| ||
134+-++";
135
136pub const ASCII2: &str = "\
138+-++
139| ||
140| ||
141| ||
142| ||
143| ||
144| ||
145+-++";
146
147pub const SQUARE_DOUBLE_HEAD: &str = "\
149┌─┬┐
150│ ║│
151├─╪┤
152│ ││
153├─┼┤
154├─┼┤
155│ ││
156└─┴┘";
157
158pub const MINIMAL_DOUBLE_HEAD: &str = " ═ \n ═ \n ═ \n \n \n \n ═ \n ";
160
161pub const SIMPLE_HEAD: &str = " \n \n \n \n \n \n ━┿ \n ";
163
164pub const ASCII_DOUBLE_HEAD: &str = "\
166+-++
167| ||
168+=+|
169| ||
170|-+|
171|-+|
172| ||
173+-++";
174
175pub const ROUNDED: &str = "\
177╭─┬╮
178│ ││
179├─┼┤
180│ ││
181├─┼┤
182├─┼┤
183│ ││
184╰─┴╯";
185
186pub const SQUARE: &str = "\
188┌─┬┐
189│ ││
190├─┼┤
191│ ││
192├─┼┤
193├─┼┤
194│ ││
195└─┴┘";
196
197pub const HEAVY: &str = "\
199┏━┳┓
200┃ ┃┃
201┣━╋┫
202┃ ┃┃
203┣━╋┫
204┣━╋┫
205┃ ┃┃
206┗━┻┛";
207
208pub const HEAVY_EDGE: &str = "\
210┏━┯┓
211┃ │┃
212┠─┼┨
213┃ │┃
214┠─┼┨
215┠─┼┨
216┃ │┃
217┗━┷┛";
218
219pub const HEAVY_HEAD: &str = "\
221┏━┳┓
222┃ ┃┃
223┡━╇┩
224│ ││
225├─┼┤
226├─┼┤
227│ ││
228└─┴┘";
229
230pub const DOUBLE: &str = "\
232╔═╦╗
233║ ║║
234╠═╬╣
235║ ║║
236╠═╬╣
237╠═╬╣
238║ ║║
239╚═╩╝";
240
241pub const DOUBLE_EDGE: &str = "\
243╔═╤╗
244║ │║
245╟─┼╢
246║ │║
247╟─┼╢
248╟─┼╢
249║ │║
250╚═╧╝";
251
252pub const SIMPLE: &str = "\
254
255
256
257
258
259
260
261
262";
263
264pub const SIMPLE_HEAVY: &str = "\
266
267
268
269
270
271
272
273 ━┿
274
275
276";
277
278pub const MINIMAL: &str = "\
280 ╌
281 ╌
282 ╌
283
284
285
286
287 ╌ ";
288
289pub const MINIMAL_HEAVY: &str = "\
291 ╍
292 ╍
293 ╍
294
295
296
297
298 ╍ ";
299
300use once_cell::sync::Lazy;
305
306pub static BOX_ROUNDED: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(ROUNDED, false));
308pub static BOX_SQUARE: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(SQUARE, false));
309pub static BOX_HEAVY: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(HEAVY, false));
310pub static BOX_HEAVY_EDGE: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(HEAVY_EDGE, false));
311pub static BOX_HEAVY_HEAD: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(HEAVY_HEAD, false));
312pub static BOX_DOUBLE: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(DOUBLE, false));
313pub static BOX_DOUBLE_EDGE: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(DOUBLE_EDGE, false));
314pub static BOX_SIMPLE: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(SIMPLE, false));
315pub static BOX_SIMPLE_HEAVY: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(SIMPLE_HEAVY, false));
316pub static BOX_MINIMAL: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(MINIMAL, false));
317pub static BOX_MINIMAL_HEAVY: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(MINIMAL_HEAVY, false));
318pub static BOX_ASCII: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(ASCII, true));
319pub static BOX_ASCII2: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(ASCII2, true));
320pub static BOX_SQUARE_DOUBLE_HEAD: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(SQUARE_DOUBLE_HEAD, false));
321pub static BOX_MINIMAL_DOUBLE_HEAD: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(MINIMAL_DOUBLE_HEAD, false));
322pub static BOX_SIMPLE_HEAD: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(SIMPLE_HEAD, false));
323pub static BOX_ASCII_DOUBLE_HEAD: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(ASCII_DOUBLE_HEAD, true));
324
325pub const MARKDOWN: &str = "\
330
331| ||
332|-||
333| ||
334|-||
335|-||
336| ||
337 ";
338
339pub static BOX_MARKDOWN: Lazy<BoxStyle> = Lazy::new(|| BoxStyle::from_str(MARKDOWN, false));
340
341pub fn get_safe_box(box_style: &BoxStyle, ascii_only: bool) -> BoxStyle {
347 if ascii_only && !box_style.ascii {
348 BOX_ASCII.clone()
349 } else {
350 box_style.clone()
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 #[test]
359 fn test_rounded_box() {
360 let b = &*BOX_ROUNDED;
361 assert_eq!(b.top_left, '╭');
362 assert_eq!(b.bottom_right, '╯');
363 }
364
365 #[test]
366 fn test_box_from_str() {
367 let b = BoxStyle::from_str(ROUNDED, false);
368 assert_eq!(b, *BOX_ROUNDED);
369 }
370
371 #[test]
372 fn test_new_box_styles_parse() {
373 let _ = &*BOX_SQUARE_DOUBLE_HEAD;
375 let _ = &*BOX_MINIMAL_DOUBLE_HEAD;
376 let _ = &*BOX_SIMPLE_HEAD;
377 let _ = &*BOX_ASCII_DOUBLE_HEAD;
378
379 let sq = &*BOX_SQUARE_DOUBLE_HEAD;
381 assert_eq!(sq.top_left, '┌');
382 assert_eq!(sq.head_vertical, '║');
383 assert_eq!(sq.head_row_horizontal, '─');
384
385 let ac = &*BOX_ASCII_DOUBLE_HEAD;
386 assert_eq!(ac.head_row_left, '+');
387 assert_eq!(ac.head_row_horizontal, '=');
388 }
389}