1use serde::Deserialize;
2use tabled::{
3 settings::{
4 formatting::Justification,
5 object::{Object, Rows, Segment},
6 style::BorderColor,
7 style::Style,
8 themes::Colorization,
9 Color, Padding,
10 },
11 Table,
12};
13
14#[derive(Debug, Deserialize)]
15pub struct TableStyleConfig {
16 pub table: TableConfig,
17 #[serde(default)]
18 pub title: Option<TitleConfig>,
19 pub header: HeaderConfig,
20 pub rows: RowsConfig,
21 pub style: StyleConfig,
22 #[serde(default)]
23 pub footer: Option<FooterConfig>,
24}
25
26#[derive(Debug, Deserialize, Clone)]
31pub struct TitleConfig {
32 #[serde(default)]
34 pub enabled: bool,
35 #[serde(default)]
36 pub bg_color: Option<String>,
37 #[serde(default)]
38 pub fg_color: Option<String>,
39 #[serde(default)]
40 pub justification_char: Option<String>,
41 #[serde(default)]
42 pub vertical_char: Option<String>,
43 #[serde(default)]
44 pub vertical_fg_color: Option<String>,
45 #[serde(default)]
46 pub vertical_bg_color: Option<String>,
47}
48
49#[derive(Debug, Deserialize, Clone)]
54pub struct FooterConfig {
55 #[serde(default)]
57 pub enabled: bool,
58 #[serde(default)]
59 pub bg_color: Option<String>,
60 #[serde(default)]
61 pub fg_color: Option<String>,
62 #[serde(default)]
63 pub justification_char: Option<String>,
64 #[serde(default)]
65 pub vertical_char: Option<String>,
66 #[serde(default)]
67 pub vertical_fg_color: Option<String>,
68 #[serde(default)]
69 pub vertical_bg_color: Option<String>,
70}
71
72#[derive(Debug, Deserialize)]
73pub struct TableConfig {
74 pub padding_left: usize,
75 pub padding_right: usize,
76 pub padding_top: usize,
77 pub padding_bottom: usize,
78}
79
80#[derive(Debug, Deserialize)]
81pub struct HeaderConfig {
82 pub bg_color: String,
83 pub fg_color: String,
84 pub justification_char: String,
85 #[serde(default)]
86 pub vertical_char: Option<String>,
87 #[serde(default)]
88 pub vertical_fg_color: Option<String>,
89 #[serde(default)]
90 pub vertical_bg_color: Option<String>,
91}
92
93#[derive(Debug, Deserialize)]
94pub struct RowsConfig {
95 pub colors: Vec<RowColor>,
96 #[serde(default)]
97 pub justification_char: Option<String>,
98}
99
100#[derive(Debug, Deserialize)]
101pub struct RowColor {
102 pub bg: String,
103 pub fg: String,
104}
105
106#[derive(Debug, Deserialize)]
107pub struct StyleConfig {
108 #[serde(default)]
109 pub vertical_char: Option<String>,
110 #[serde(default)]
111 pub vertical_fg_color: Option<String>,
112 #[serde(default)]
113 pub vertical_bg_color: Option<String>,
114}
115
116impl TableStyleConfig {
117 pub fn apply_to_table<T>(&self, table: &mut Table)
119 where
120 T: tabled::Tabled,
121 {
122 table.with(Style::empty());
124
125 let global_vert_char = self
129 .header
130 .vertical_char
131 .as_deref()
132 .or(self.style.vertical_char.as_deref())
133 .unwrap_or("");
134
135 if !global_vert_char.is_empty() {
137 let vert_char = global_vert_char.chars().next().unwrap_or(' ');
138 table.with(Style::empty().vertical(vert_char));
139 }
140
141 table.with(Padding::new(
143 self.table.padding_left,
144 self.table.padding_right,
145 self.table.padding_top,
146 self.table.padding_bottom,
147 ));
148
149 let row_colors: Vec<Color> =
151 self.rows.colors.iter().map(|rc| parse_color(&rc.bg, &rc.fg)).collect();
152 table.with(Colorization::rows(row_colors));
153
154 let title_enabled = self.title.as_ref().map(|t| t.enabled).unwrap_or(false);
156
157 if let Some(title) = &self.title {
159 if title.enabled {
160 let title_bg_str = title.bg_color.as_deref().unwrap_or(&self.header.bg_color);
162 let title_fg_str = title.fg_color.as_deref().unwrap_or(&self.header.fg_color);
163 let title_color = parse_color(title_bg_str, title_fg_str);
164 let title_bg = parse_bg_color(title_bg_str);
165
166 table.modify(Rows::first(), title_color);
168
169 let title_just_char = title
171 .justification_char
172 .as_deref()
173 .or(Some(&self.header.justification_char))
174 .and_then(|s| s.chars().next())
175 .unwrap_or(' ');
176 table.modify(
177 Rows::first(),
178 Justification::new(title_just_char).color(title_bg.clone()),
179 );
180 }
181 }
182
183 let header_color = parse_color(&self.header.bg_color, &self.header.fg_color);
185 table.modify(Rows::new(1..2), header_color);
186
187 let just_char = self.header.justification_char.chars().next().unwrap_or(' ');
189 let header_bg = parse_bg_color(&self.header.bg_color);
190 table.modify(Rows::new(1..2), Justification::new(just_char).color(header_bg.clone()));
191
192 let footer_enabled = self.footer.as_ref().map(|f| f.enabled).unwrap_or(false);
194
195 if let Some(data_just_char) =
197 self.rows.justification_char.as_ref().and_then(|s| s.chars().next())
198 {
199 let data_bg = parse_bg_color(&self.rows.colors[0].bg);
202
203 match footer_enabled {
205 true => {
206 table.modify(
208 Segment::all().not(Rows::first()).not(Rows::new(1..2)).not(Rows::last()),
209 Justification::new(data_just_char).color(data_bg),
210 );
211 }
212 false => {
213 table.modify(
215 Segment::all().not(Rows::first()).not(Rows::new(1..2)),
216 Justification::new(data_just_char).color(data_bg),
217 );
218 }
219 }
220 }
221
222 if self.style.vertical_fg_color.is_some() || self.style.vertical_bg_color.is_some() {
226 let fg = self.style.vertical_fg_color.as_deref().unwrap_or("white");
227 let bg = self.style.vertical_bg_color.as_deref().unwrap_or("black");
228 let vert_color = parse_color(bg, fg);
229
230 match (title_enabled, footer_enabled) {
233 (true, true) => {
234 table.modify(
235 Segment::all().not(Rows::first()).not(Rows::new(1..2)).not(Rows::last()),
236 BorderColor::filled(vert_color),
237 );
238 }
239 (true, false) => {
240 table.modify(
241 Segment::all().not(Rows::first()).not(Rows::new(1..2)),
242 BorderColor::filled(vert_color),
243 );
244 }
245 (false, true) => {
246 table.modify(
247 Segment::all().not(Rows::first()).not(Rows::last()),
248 BorderColor::filled(vert_color),
249 );
250 }
251 (false, false) => {
252 table
253 .modify(Segment::all().not(Rows::first()), BorderColor::filled(vert_color));
254 }
255 }
256 }
257
258 if let Some(title) = &self.title {
260 if title.enabled {
261 let title_bg_str = title.bg_color.as_deref().unwrap_or(&self.header.bg_color);
262
263 if title.vertical_fg_color.is_some() || title.vertical_bg_color.is_some() {
264 let fg = title.vertical_fg_color.as_deref().unwrap_or("white");
265 let bg = title.vertical_bg_color.as_deref().unwrap_or(title_bg_str);
266 let title_vert_color = parse_color(bg, fg);
267
268 table.modify(
269 Segment::all().intersect(Rows::first()),
270 BorderColor::filled(title_vert_color),
271 );
272 } else {
273 let title_bg = parse_bg_color(title_bg_str);
275 table.modify(
276 Segment::all().intersect(Rows::first()),
277 BorderColor::filled(title_bg),
278 );
279 }
280 }
281 }
282
283 if self.header.vertical_fg_color.is_some() || self.header.vertical_bg_color.is_some() {
285 let fg = self.header.vertical_fg_color.as_deref().unwrap_or("white");
286 let bg =
287 self.header.vertical_bg_color.as_deref().unwrap_or_else(|| &self.header.bg_color);
288 let header_vert_color = parse_color(bg, fg);
289
290 table.modify(
291 Segment::all().intersect(Rows::new(1..2)),
292 BorderColor::filled(header_vert_color),
293 );
294 } else {
295 table.modify(Segment::all().intersect(Rows::new(1..2)), BorderColor::filled(header_bg));
297 }
298
299 if let Some(footer) = &self.footer {
301 if footer.enabled {
302 let footer_bg_str = footer.bg_color.as_deref().unwrap_or(&self.header.bg_color);
304 let footer_fg_str = footer.fg_color.as_deref().unwrap_or(&self.header.fg_color);
305 let footer_color = parse_color(footer_bg_str, footer_fg_str);
306 let footer_bg = parse_bg_color(footer_bg_str);
307
308 table.modify(Rows::last(), footer_color);
310
311 let footer_just_char = footer
313 .justification_char
314 .as_deref()
315 .or(Some(&self.header.justification_char))
316 .and_then(|s| s.chars().next())
317 .unwrap_or(' ');
318 table.modify(
319 Rows::last(),
320 Justification::new(footer_just_char).color(footer_bg.clone()),
321 );
322
323 if footer.vertical_fg_color.is_some() || footer.vertical_bg_color.is_some() {
325 let fg = footer.vertical_fg_color.as_deref().unwrap_or("white");
326 let bg = footer.vertical_bg_color.as_deref().unwrap_or(footer_bg_str);
327 let footer_vert_color = parse_color(bg, fg);
328
329 table.modify(
330 Segment::all().intersect(Rows::last()),
331 BorderColor::filled(footer_vert_color),
332 );
333 } else {
334 table.modify(
336 Segment::all().intersect(Rows::last()),
337 BorderColor::filled(footer_bg),
338 );
339 }
340 }
341 }
342 }
343}
344
345fn parse_single_color(color: &str) -> Color {
347 if let Some(rgb) = parse_hex_color(color) {
349 return Color::rgb_fg(rgb.0, rgb.1, rgb.2);
350 }
351
352 match color.to_lowercase().as_str() {
353 "black" => Color::FG_BLACK,
355 "red" => Color::FG_RED,
356 "green" => Color::FG_GREEN,
357 "yellow" => Color::FG_YELLOW,
358 "blue" => Color::FG_BLUE,
359 "magenta" => Color::FG_MAGENTA,
360 "cyan" => Color::FG_CYAN,
361 "white" => Color::FG_WHITE,
362 "bright_black" | "gray" | "grey" => Color::FG_BRIGHT_BLACK,
363 "bright_red" => Color::FG_BRIGHT_RED,
364 "bright_green" => Color::FG_BRIGHT_GREEN,
365 "bright_yellow" => Color::FG_BRIGHT_YELLOW,
366 "bright_blue" => Color::FG_BRIGHT_BLUE,
367 "bright_magenta" => Color::FG_BRIGHT_MAGENTA,
368 "bright_cyan" => Color::FG_BRIGHT_CYAN,
369 "bright_white" => Color::FG_BRIGHT_WHITE,
370 _ => Color::FG_WHITE, }
372}
373
374fn parse_color(bg: &str, fg: &str) -> Color {
376 let bg_color = if let Some(rgb) = parse_hex_color(bg) {
378 Color::rgb_bg(rgb.0, rgb.1, rgb.2)
379 } else {
380 match bg.to_lowercase().as_str() {
381 "black" => Color::BG_BLACK,
382 "red" => Color::BG_RED,
383 "green" => Color::BG_GREEN,
384 "yellow" => Color::BG_YELLOW,
385 "blue" => Color::BG_BLUE,
386 "magenta" => Color::BG_MAGENTA,
387 "cyan" => Color::BG_CYAN,
388 "white" => Color::BG_WHITE,
389 "bright_black" | "gray" | "grey" => Color::BG_BRIGHT_BLACK,
390 "bright_red" => Color::BG_BRIGHT_RED,
391 "bright_green" => Color::BG_BRIGHT_GREEN,
392 "bright_yellow" => Color::BG_BRIGHT_YELLOW,
393 "bright_blue" => Color::BG_BRIGHT_BLUE,
394 "bright_magenta" => Color::BG_BRIGHT_MAGENTA,
395 "bright_cyan" => Color::BG_BRIGHT_CYAN,
396 "bright_white" => Color::BG_BRIGHT_WHITE,
397 _ => Color::BG_BLACK, }
399 };
400
401 let fg_color = parse_single_color(fg);
402
403 bg_color | fg_color
404}
405
406pub fn parse_bg_color(bg: &str) -> Color {
408 if let Some(rgb) = parse_hex_color(bg) {
410 return Color::rgb_bg(rgb.0, rgb.1, rgb.2);
411 }
412
413 match bg.to_lowercase().as_str() {
414 "black" => Color::BG_BLACK,
415 "red" => Color::BG_RED,
416 "green" => Color::BG_GREEN,
417 "yellow" => Color::BG_YELLOW,
418 "blue" => Color::BG_BLUE,
419 "magenta" => Color::BG_MAGENTA,
420 "cyan" => Color::BG_CYAN,
421 "white" => Color::BG_WHITE,
422 "bright_black" | "gray" | "grey" => Color::BG_BRIGHT_BLACK,
423 "bright_red" => Color::BG_BRIGHT_RED,
424 "bright_green" => Color::BG_BRIGHT_GREEN,
425 "bright_yellow" => Color::BG_BRIGHT_YELLOW,
426 "bright_blue" => Color::BG_BRIGHT_BLUE,
427 "bright_magenta" => Color::BG_BRIGHT_MAGENTA,
428 "bright_cyan" => Color::BG_BRIGHT_CYAN,
429 "bright_white" => Color::BG_BRIGHT_WHITE,
430 _ => Color::BG_BLACK, }
432}
433
434fn parse_hex_color(hex: &str) -> Option<(u8, u8, u8)> {
437 let hex = hex.trim_start_matches('#');
438
439 if hex.len() == 3 {
441 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).ok()?;
442 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).ok()?;
443 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).ok()?;
444 Some((r, g, b))
445 } else if hex.len() == 6 {
446 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
447 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
448 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
449 Some((r, g, b))
450 } else {
451 None
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 #[test]
462 fn test_parse_hex_color_six_digit_with_hash() {
463 assert_eq!(parse_hex_color("#FF0000"), Some((255, 0, 0)));
464 assert_eq!(parse_hex_color("#00FF00"), Some((0, 255, 0)));
465 assert_eq!(parse_hex_color("#0000FF"), Some((0, 0, 255)));
466 assert_eq!(parse_hex_color("#FFFFFF"), Some((255, 255, 255)));
467 assert_eq!(parse_hex_color("#000000"), Some((0, 0, 0)));
468 }
469
470 #[test]
471 fn test_parse_hex_color_six_digit_without_hash() {
472 assert_eq!(parse_hex_color("FF0000"), Some((255, 0, 0)));
473 assert_eq!(parse_hex_color("00FF00"), Some((0, 255, 0)));
474 assert_eq!(parse_hex_color("0000FF"), Some((0, 0, 255)));
475 }
476
477 #[test]
478 fn test_parse_hex_color_three_digit_with_hash() {
479 assert_eq!(parse_hex_color("#F00"), Some((255, 0, 0)));
480 assert_eq!(parse_hex_color("#0F0"), Some((0, 255, 0)));
481 assert_eq!(parse_hex_color("#00F"), Some((0, 0, 255)));
482 assert_eq!(parse_hex_color("#FFF"), Some((255, 255, 255)));
483 assert_eq!(parse_hex_color("#000"), Some((0, 0, 0)));
484 }
485
486 #[test]
487 fn test_parse_hex_color_three_digit_without_hash() {
488 assert_eq!(parse_hex_color("F00"), Some((255, 0, 0)));
489 assert_eq!(parse_hex_color("0F0"), Some((0, 255, 0)));
490 assert_eq!(parse_hex_color("00F"), Some((0, 0, 255)));
491 }
492
493 #[test]
494 fn test_parse_hex_color_lowercase() {
495 assert_eq!(parse_hex_color("#ff0000"), Some((255, 0, 0)));
496 assert_eq!(parse_hex_color("#f00"), Some((255, 0, 0)));
497 assert_eq!(parse_hex_color("abcdef"), Some((171, 205, 239)));
498 }
499
500 #[test]
501 fn test_parse_hex_color_mixed_case() {
502 assert_eq!(parse_hex_color("#Ff0000"), Some((255, 0, 0)));
503 assert_eq!(parse_hex_color("#AbCdEf"), Some((171, 205, 239)));
504 }
505
506 #[test]
507 fn test_parse_hex_color_invalid_length() {
508 assert_eq!(parse_hex_color("#FF"), None);
509 assert_eq!(parse_hex_color("#FFFF"), None);
510 assert_eq!(parse_hex_color("#FFFFFFF"), None);
511 assert_eq!(parse_hex_color(""), None);
512 }
513
514 #[test]
515 fn test_parse_hex_color_invalid_characters() {
516 assert_eq!(parse_hex_color("#GGGGGG"), None);
517 assert_eq!(parse_hex_color("#ZZZZZZ"), None);
518 assert_eq!(parse_hex_color("#12345G"), None);
519 assert_eq!(parse_hex_color("notahex"), None);
520 }
521
522 #[test]
523 fn test_parse_hex_color_real_world_colors() {
524 assert_eq!(parse_hex_color("#F97316"), Some((249, 115, 22)));
526 assert_eq!(parse_hex_color("#D45500"), Some((212, 85, 0)));
527 assert_eq!(parse_hex_color("#451A03"), Some((69, 26, 3)));
528 assert_eq!(parse_hex_color("#FED7AA"), Some((254, 215, 170)));
529 assert_eq!(parse_hex_color("#FDBA74"), Some((253, 186, 116)));
530 assert_eq!(parse_hex_color("#803300"), Some((128, 51, 0)));
531 }
532
533 #[test]
536 fn test_parse_single_color_basic_colors() {
537 parse_single_color("black");
539 parse_single_color("red");
540 parse_single_color("green");
541 parse_single_color("yellow");
542 parse_single_color("blue");
543 parse_single_color("magenta");
544 parse_single_color("cyan");
545 parse_single_color("white");
546 }
547
548 #[test]
549 fn test_parse_single_color_bright_colors() {
550 parse_single_color("bright_black");
551 parse_single_color("bright_red");
552 parse_single_color("bright_green");
553 parse_single_color("bright_yellow");
554 parse_single_color("bright_blue");
555 parse_single_color("bright_magenta");
556 parse_single_color("bright_cyan");
557 parse_single_color("bright_white");
558 }
559
560 #[test]
561 fn test_parse_single_color_aliases() {
562 parse_single_color("gray");
563 parse_single_color("grey");
564 }
565
566 #[test]
567 fn test_parse_single_color_case_insensitive() {
568 parse_single_color("RED");
569 parse_single_color("Green");
570 parse_single_color("BLUE");
571 parse_single_color("bright_RED");
572 }
573
574 #[test]
575 fn test_parse_single_color_hex() {
576 parse_single_color("#FF0000");
578 parse_single_color("#F00");
579 }
580
581 #[test]
582 fn test_parse_single_color_unknown_defaults_to_white() {
583 parse_single_color("unknown");
585 parse_single_color("notacolor");
586 }
587
588 #[test]
591 fn test_parse_color_basic_combinations() {
592 parse_color("black", "white");
594 parse_color("red", "white");
595 parse_color("blue", "yellow");
596 }
597
598 #[test]
599 fn test_parse_color_hex_background() {
600 parse_color("#FF0000", "white");
601 parse_color("#F00", "black");
602 }
603
604 #[test]
605 fn test_parse_color_hex_foreground() {
606 parse_color("black", "#FFFFFF");
607 parse_color("red", "#FFF");
608 }
609
610 #[test]
611 fn test_parse_color_both_hex() {
612 parse_color("#FF0000", "#FFFFFF");
613 parse_color("#F00", "#FFF");
614 }
615
616 #[test]
617 fn test_parse_color_case_insensitive() {
618 parse_color("RED", "WHITE");
619 parse_color("Blue", "Yellow");
620 }
621
622 #[test]
625 fn test_parse_bg_color_basic_colors() {
626 parse_bg_color("black");
627 parse_bg_color("red");
628 parse_bg_color("green");
629 parse_bg_color("yellow");
630 parse_bg_color("blue");
631 parse_bg_color("magenta");
632 parse_bg_color("cyan");
633 parse_bg_color("white");
634 }
635
636 #[test]
637 fn test_parse_bg_color_bright_colors() {
638 parse_bg_color("bright_black");
639 parse_bg_color("bright_red");
640 parse_bg_color("bright_green");
641 parse_bg_color("bright_yellow");
642 parse_bg_color("bright_blue");
643 parse_bg_color("bright_magenta");
644 parse_bg_color("bright_cyan");
645 parse_bg_color("bright_white");
646 }
647
648 #[test]
649 fn test_parse_bg_color_aliases() {
650 parse_bg_color("gray");
651 parse_bg_color("grey");
652 }
653
654 #[test]
655 fn test_parse_bg_color_case_insensitive() {
656 parse_bg_color("RED");
657 parse_bg_color("Green");
658 parse_bg_color("BLUE");
659 }
660
661 #[test]
662 fn test_parse_bg_color_hex() {
663 parse_bg_color("#FF0000");
664 parse_bg_color("#F00");
665 }
666
667 #[test]
668 fn test_parse_bg_color_unknown_defaults_to_black() {
669 parse_bg_color("unknown");
671 parse_bg_color("notacolor");
672 }
673
674 #[test]
677 fn test_deserialize_minimal_config() {
678 let toml = r##"
679 [table]
680 padding_left = 1
681 padding_right = 1
682 padding_top = 0
683 padding_bottom = 0
684
685 [header]
686 bg_color = "black"
687 fg_color = "white"
688 justification_char = " "
689
690 [rows]
691 colors = [
692 { bg = "black", fg = "white" }
693 ]
694
695 [style]
696 "##;
697
698 let config: TableStyleConfig = toml::from_str(toml).unwrap();
699 assert_eq!(config.table.padding_left, 1);
700 assert_eq!(config.table.padding_right, 1);
701 assert_eq!(config.header.bg_color, "black");
702 assert_eq!(config.rows.colors.len(), 1);
703 }
704
705 #[test]
706 fn test_deserialize_with_title_and_footer() {
707 let toml = r##"
708 [table]
709 padding_left = 0
710 padding_right = 0
711 padding_top = 0
712 padding_bottom = 0
713
714 [title]
715 enabled = true
716 bg_color = "#FF0000"
717 fg_color = "#FFFFFF"
718
719 [header]
720 bg_color = "blue"
721 fg_color = "white"
722 justification_char = " "
723
724 [rows]
725 colors = [
726 { bg = "black", fg = "white" },
727 { bg = "gray", fg = "white" }
728 ]
729
730 [style]
731
732 [footer]
733 enabled = true
734 bg_color = "red"
735 fg_color = "white"
736 "##;
737
738 let config: TableStyleConfig = toml::from_str(toml).unwrap();
739 assert!(config.title.is_some());
740 assert!(config.footer.is_some());
741
742 let title = config.title.unwrap();
743 assert!(title.enabled);
744 assert_eq!(title.bg_color.as_deref(), Some("#FF0000"));
745
746 let footer = config.footer.unwrap();
747 assert!(footer.enabled);
748 assert_eq!(footer.bg_color.as_deref(), Some("red"));
749 }
750
751 #[test]
752 fn test_deserialize_optional_fields() {
753 let toml = r##"
754 [table]
755 padding_left = 0
756 padding_right = 0
757 padding_top = 0
758 padding_bottom = 0
759
760 [header]
761 bg_color = "black"
762 fg_color = "white"
763 justification_char = " "
764 vertical_char = "|"
765 vertical_fg_color = "red"
766 vertical_bg_color = "blue"
767
768 [rows]
769 colors = [{ bg = "black", fg = "white" }]
770 justification_char = " "
771
772 [style]
773 vertical_char = "|"
774 vertical_fg_color = "green"
775 vertical_bg_color = "yellow"
776 "##;
777
778 let config: TableStyleConfig = toml::from_str(toml).unwrap();
779 assert_eq!(config.header.vertical_char.as_deref(), Some("|"));
780 assert_eq!(config.header.vertical_fg_color.as_deref(), Some("red"));
781 assert_eq!(config.rows.justification_char.as_deref(), Some(" "));
782 assert_eq!(config.style.vertical_char.as_deref(), Some("|"));
783 }
784
785 #[test]
786 fn test_deserialize_without_optional_sections() {
787 let toml = r##"
788 [table]
789 padding_left = 0
790 padding_right = 0
791 padding_top = 0
792 padding_bottom = 0
793
794 [header]
795 bg_color = "black"
796 fg_color = "white"
797 justification_char = " "
798
799 [rows]
800 colors = [{ bg = "black", fg = "white" }]
801
802 [style]
803 "##;
804
805 let config: TableStyleConfig = toml::from_str(toml).unwrap();
806 assert!(config.title.is_none());
807 assert!(config.footer.is_none());
808 }
809
810 use tabled::{Table, Tabled};
813
814 #[derive(Tabled)]
815 struct TestRow {
816 #[tabled(rename = "Col1")]
817 col1: String,
818 #[tabled(rename = "Col2")]
819 col2: String,
820 }
821
822 #[test]
823 fn test_apply_to_table_basic() {
824 let config = TableStyleConfig::default();
825 let data = vec![TestRow { col1: "A".into(), col2: "B".into() }];
826 let mut table = Table::new(&data);
827
828 config.apply_to_table::<TestRow>(&mut table);
829
830 let output = table.to_string();
831 assert!(!output.is_empty());
832 }
833
834 #[test]
835 fn test_apply_to_table_no_title_or_footer() {
836 let toml = r##"
837 [table]
838 padding_left = 1
839 padding_right = 1
840 padding_top = 0
841 padding_bottom = 0
842
843 [header]
844 bg_color = "#FF0000"
845 fg_color = "#FFFFFF"
846 justification_char = " "
847
848 [rows]
849 colors = [
850 { bg = "#000000", fg = "#FFFFFF" }
851 ]
852
853 [style]
854 vertical_fg_color = "#00FF00"
855 vertical_bg_color = "#0000FF"
856 "##;
857
858 let config: TableStyleConfig = toml::from_str(toml).unwrap();
859 let data = vec![TestRow { col1: "A".into(), col2: "B".into() }];
860 let mut table = Table::new(&data);
861
862 config.apply_to_table::<TestRow>(&mut table);
863
864 let output = table.to_string();
865 assert!(!output.is_empty());
866 }
867
868 #[test]
869 fn test_apply_to_table_with_title_no_footer() {
870 let toml = r##"
871 [table]
872 padding_left = 0
873 padding_right = 0
874 padding_top = 0
875 padding_bottom = 0
876
877 [title]
878 enabled = true
879 bg_color = "#FF0000"
880 fg_color = "#FFFFFF"
881 justification_char = " "
882 vertical_fg_color = "#00FF00"
883 vertical_bg_color = "#0000FF"
884
885 [header]
886 bg_color = "blue"
887 fg_color = "white"
888 justification_char = " "
889
890 [rows]
891 colors = [{ bg = "black", fg = "white" }]
892
893 [style]
894 vertical_fg_color = "red"
895 vertical_bg_color = "green"
896 "##;
897
898 let config: TableStyleConfig = toml::from_str(toml).unwrap();
899 let data = vec![TestRow { col1: "A".into(), col2: "B".into() }];
900 let mut table = Table::new(&data);
901
902 config.apply_to_table::<TestRow>(&mut table);
903
904 let output = table.to_string();
905 assert!(!output.is_empty());
906 }
907
908 #[test]
909 fn test_apply_to_table_with_footer_no_title() {
910 let toml = r##"
911 [table]
912 padding_left = 0
913 padding_right = 0
914 padding_top = 0
915 padding_bottom = 0
916
917 [header]
918 bg_color = "blue"
919 fg_color = "white"
920 justification_char = " "
921
922 [rows]
923 colors = [{ bg = "black", fg = "white" }]
924
925 [style]
926 vertical_fg_color = "red"
927 vertical_bg_color = "green"
928
929 [footer]
930 enabled = true
931 bg_color = "#FF0000"
932 fg_color = "#FFFFFF"
933 justification_char = " "
934 vertical_fg_color = "#00FF00"
935 vertical_bg_color = "#0000FF"
936 "##;
937
938 let config: TableStyleConfig = toml::from_str(toml).unwrap();
939 let data = vec![TestRow { col1: "A".into(), col2: "B".into() }];
940 let mut table = Table::new(&data);
941
942 config.apply_to_table::<TestRow>(&mut table);
943
944 let output = table.to_string();
945 assert!(!output.is_empty());
946 }
947
948 #[test]
949 fn test_apply_to_table_with_both_title_and_footer() {
950 let toml = r##"
951 [table]
952 padding_left = 0
953 padding_right = 0
954 padding_top = 0
955 padding_bottom = 0
956
957 [title]
958 enabled = true
959 bg_color = "#AA0000"
960 fg_color = "#FFFFFF"
961
962 [header]
963 bg_color = "blue"
964 fg_color = "white"
965 justification_char = " "
966
967 [rows]
968 colors = [{ bg = "black", fg = "white" }]
969
970 [style]
971 vertical_fg_color = "red"
972 vertical_bg_color = "green"
973
974 [footer]
975 enabled = true
976 bg_color = "#00AA00"
977 fg_color = "#FFFFFF"
978 "##;
979
980 let config: TableStyleConfig = toml::from_str(toml).unwrap();
981 let data = vec![TestRow { col1: "A".into(), col2: "B".into() }];
982 let mut table = Table::new(&data);
983
984 config.apply_to_table::<TestRow>(&mut table);
985
986 let output = table.to_string();
987 assert!(!output.is_empty());
988 }
989
990 #[test]
991 fn test_apply_to_table_title_disabled() {
992 let toml = r##"
993 [table]
994 padding_left = 0
995 padding_right = 0
996 padding_top = 0
997 padding_bottom = 0
998
999 [title]
1000 enabled = false
1001 bg_color = "#FF0000"
1002 fg_color = "#FFFFFF"
1003
1004 [header]
1005 bg_color = "blue"
1006 fg_color = "white"
1007 justification_char = " "
1008
1009 [rows]
1010 colors = [{ bg = "black", fg = "white" }]
1011
1012 [style]
1013 "##;
1014
1015 let config: TableStyleConfig = toml::from_str(toml).unwrap();
1016 let data = vec![TestRow { col1: "A".into(), col2: "B".into() }];
1017 let mut table = Table::new(&data);
1018
1019 config.apply_to_table::<TestRow>(&mut table);
1020
1021 let output = table.to_string();
1022 assert!(!output.is_empty());
1023 }
1024
1025 #[test]
1026 fn test_apply_to_table_footer_disabled() {
1027 let toml = r##"
1028 [table]
1029 padding_left = 0
1030 padding_right = 0
1031 padding_top = 0
1032 padding_bottom = 0
1033
1034 [header]
1035 bg_color = "blue"
1036 fg_color = "white"
1037 justification_char = " "
1038
1039 [rows]
1040 colors = [{ bg = "black", fg = "white" }]
1041
1042 [style]
1043
1044 [footer]
1045 enabled = false
1046 bg_color = "#FF0000"
1047 fg_color = "#FFFFFF"
1048 "##;
1049
1050 let config: TableStyleConfig = toml::from_str(toml).unwrap();
1051 let data = vec![TestRow { col1: "A".into(), col2: "B".into() }];
1052 let mut table = Table::new(&data);
1053
1054 config.apply_to_table::<TestRow>(&mut table);
1055
1056 let output = table.to_string();
1057 assert!(!output.is_empty());
1058 }
1059
1060 #[test]
1061 fn test_apply_to_table_with_empty_vertical_char() {
1062 let toml = r##"
1063 [table]
1064 padding_left = 0
1065 padding_right = 0
1066 padding_top = 0
1067 padding_bottom = 0
1068
1069 [header]
1070 bg_color = "blue"
1071 fg_color = "white"
1072 justification_char = " "
1073 vertical_char = ""
1074
1075 [rows]
1076 colors = [{ bg = "black", fg = "white" }]
1077
1078 [style]
1079 vertical_char = ""
1080 "##;
1081
1082 let config: TableStyleConfig = toml::from_str(toml).unwrap();
1083 let data = vec![TestRow { col1: "A".into(), col2: "B".into() }];
1084 let mut table = Table::new(&data);
1085
1086 config.apply_to_table::<TestRow>(&mut table);
1087
1088 let output = table.to_string();
1089 assert!(!output.is_empty());
1090 }
1091}