browser_automation_cli/
color.rs1use std::env;
8use std::sync::OnceLock;
9
10fn env_is_truthy(name: &str) -> Option<bool> {
11 env::var(name)
12 .ok()
13 .map(|val| !matches!(val.to_lowercase().as_str(), "0" | "false" | "no"))
14}
15
16pub fn is_enabled() -> bool {
21 static COLORS_ENABLED: OnceLock<bool> = OnceLock::new();
22 *COLORS_ENABLED.get_or_init(|| {
23 if env::var_os("NO_COLOR").is_some() {
24 return false;
25 }
26 env_is_truthy("BROWSER_AUTOMATION_CLI_COLOR").unwrap_or(false)
27 })
28}
29
30pub fn red(text: &str) -> String {
32 if is_enabled() {
33 format!("\x1b[31m{}\x1b[0m", text)
34 } else {
35 text.to_string()
36 }
37}
38
39pub fn green(text: &str) -> String {
41 if is_enabled() {
42 format!("\x1b[32m{}\x1b[0m", text)
43 } else {
44 text.to_string()
45 }
46}
47
48pub fn yellow(text: &str) -> String {
50 if is_enabled() {
51 format!("\x1b[33m{}\x1b[0m", text)
52 } else {
53 text.to_string()
54 }
55}
56
57pub fn cyan(text: &str) -> String {
59 if is_enabled() {
60 format!("\x1b[36m{}\x1b[0m", text)
61 } else {
62 text.to_string()
63 }
64}
65
66pub fn bold(text: &str) -> String {
68 if is_enabled() {
69 format!("\x1b[1m{}\x1b[0m", text)
70 } else {
71 text.to_string()
72 }
73}
74
75pub fn dim(text: &str) -> String {
77 if is_enabled() {
78 format!("\x1b[2m{}\x1b[0m", text)
79 } else {
80 text.to_string()
81 }
82}
83
84pub fn error_indicator() -> &'static str {
86 static INDICATOR: OnceLock<String> = OnceLock::new();
87 INDICATOR.get_or_init(|| {
88 if is_enabled() {
89 "\x1b[31m✗\x1b[0m".to_string()
90 } else {
91 "✗".to_string()
92 }
93 })
94}
95
96pub fn success_indicator() -> &'static str {
98 static INDICATOR: OnceLock<String> = OnceLock::new();
99 INDICATOR.get_or_init(|| {
100 if is_enabled() {
101 "\x1b[32m✓\x1b[0m".to_string()
102 } else {
103 "✓".to_string()
104 }
105 })
106}
107
108pub fn warning_indicator() -> &'static str {
110 static INDICATOR: OnceLock<String> = OnceLock::new();
111 INDICATOR.get_or_init(|| {
112 if is_enabled() {
113 "\x1b[33m⚠\x1b[0m".to_string()
114 } else {
115 "⚠".to_string()
116 }
117 })
118}
119
120pub fn console_level_prefix(level: &str) -> String {
122 if !is_enabled() {
123 return format!("[{}]", level);
124 }
125
126 let color = match level {
127 "error" => "\x1b[31m",
128 "warning" => "\x1b[33m",
129 "info" => "\x1b[36m",
130 _ => "",
131 };
132 if color.is_empty() {
133 format!("[{}]", level)
134 } else {
135 format!("{}[{}]\x1b[0m", color, level)
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_red_contains_ansi_codes() {
145 let formatted = format!("\x1b[31m{}\x1b[0m", "error");
147 assert!(formatted.contains("\x1b[31m"));
148 assert!(formatted.contains("\x1b[0m"));
149 }
150
151 #[test]
152 fn test_green_contains_ansi_codes() {
153 let formatted = format!("\x1b[32m{}\x1b[0m", "success");
154 assert!(formatted.contains("\x1b[32m"));
155 }
156
157 #[test]
158 fn test_console_level_prefix_contains_level() {
159 assert!(console_level_prefix("error").contains("error"));
161 assert!(console_level_prefix("warning").contains("warning"));
162 assert!(console_level_prefix("info").contains("info"));
163 assert!(console_level_prefix("log").contains("log"));
164 }
165
166 #[test]
167 fn test_indicators_contain_symbols() {
168 assert!(error_indicator().contains('✗'));
170 assert!(success_indicator().contains('✓'));
171 assert!(warning_indicator().contains('⚠'));
172 }
173}