1use terminal_size::{terminal_size, Width};
6
7use super::colors::*;
8
9const DEFAULT_TERMINAL_WIDTH: u16 = 80;
10const MIN_BANNER_WIDTH: usize = 20;
11const MAX_BANNER_WIDTH: usize = 80;
12
13#[derive(Debug, Clone, Copy, PartialEq)]
15pub enum BannerColor {
16 Cyan,
18 Green,
20 Red,
22 Yellow,
24}
25
26impl BannerColor {
27 pub fn ansi_code(&self) -> &'static str {
29 match self {
30 BannerColor::Cyan => CYAN,
31 BannerColor::Green => GREEN,
32 BannerColor::Red => RED,
33 BannerColor::Yellow => YELLOW,
34 }
35 }
36}
37
38fn get_terminal_width_for_banner() -> usize {
40 terminal_size()
41 .map(|(Width(w), _)| w as usize)
42 .unwrap_or(DEFAULT_TERMINAL_WIDTH as usize)
43}
44
45pub fn print_phase_banner(phase_name: &str, color: BannerColor) {
55 let terminal_width = get_terminal_width_for_banner();
56
57 let banner_width = terminal_width.clamp(MIN_BANNER_WIDTH, MAX_BANNER_WIDTH);
59
60 let phase_with_spaces = format!(" {} ", phase_name);
62 let phase_len = phase_with_spaces.chars().count();
63
64 let remaining = banner_width.saturating_sub(phase_len);
66 let left_padding = remaining / 2;
67 let right_padding = remaining - left_padding;
68
69 let color_code = color.ansi_code();
70
71 println!(
72 "{}{BOLD}{}{}{}{}",
73 color_code,
74 "━".repeat(left_padding),
75 phase_with_spaces,
76 "━".repeat(right_padding),
77 RESET
78 );
79}
80
81pub fn print_phase_footer(color: BannerColor) {
90 let terminal_width = get_terminal_width_for_banner();
91
92 let banner_width = terminal_width.clamp(MIN_BANNER_WIDTH, MAX_BANNER_WIDTH);
94
95 let color_code = color.ansi_code();
96
97 println!("{}{BOLD}{}{RESET}", color_code, "━".repeat(banner_width));
98 println!();
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn test_banner_color_ansi_codes() {
108 assert_eq!(BannerColor::Cyan.ansi_code(), CYAN);
109 assert_eq!(BannerColor::Green.ansi_code(), GREEN);
110 assert_eq!(BannerColor::Red.ansi_code(), RED);
111 assert_eq!(BannerColor::Yellow.ansi_code(), YELLOW);
112 }
113
114 #[test]
115 fn test_banner_color_equality() {
116 assert_eq!(BannerColor::Cyan, BannerColor::Cyan);
117 assert_ne!(BannerColor::Cyan, BannerColor::Green);
118 }
119
120 #[test]
121 fn test_get_terminal_width_returns_valid_width() {
122 let width = get_terminal_width_for_banner();
123 assert!(width >= MIN_BANNER_WIDTH);
124 }
125
126 #[test]
127 fn test_banner_width_clamping() {
128 assert!(MIN_BANNER_WIDTH < MAX_BANNER_WIDTH);
129 assert_eq!(MIN_BANNER_WIDTH, 20);
130 assert_eq!(MAX_BANNER_WIDTH, 80);
131 }
132
133 #[test]
134 fn test_print_phase_banner_all_colors_and_phases() {
135 let test_cases: &[(&str, BannerColor)] = &[
136 ("RUNNING", BannerColor::Cyan),
137 ("REVIEWING", BannerColor::Cyan),
138 ("CORRECTING", BannerColor::Yellow),
139 ("COMMITTING", BannerColor::Cyan),
140 ("SUCCESS", BannerColor::Green),
141 ("FAILURE", BannerColor::Red),
142 ];
143
144 for (phase_name, color) in test_cases {
145 print_phase_banner(phase_name, *color);
146 }
147 }
148
149 #[test]
150 fn test_print_phase_banner_edge_cases() {
151 print_phase_banner("", BannerColor::Cyan);
153
154 print_phase_banner(
156 "THIS_IS_A_VERY_LONG_PHASE_NAME_THAT_EXCEEDS_NORMAL_LENGTH",
157 BannerColor::Cyan,
158 );
159 }
160}