atlassian_cli_output/
colors.rs1use colored::Colorize;
2use std::env;
3use std::io::{self, IsTerminal};
4
5pub fn should_use_colors() -> bool {
7 if env::var("NO_COLOR").is_ok() {
9 return false;
10 }
11
12 if env::var("TERM")
14 .map(|t| t.eq_ignore_ascii_case("dumb"))
15 .unwrap_or(false)
16 {
17 return false;
18 }
19
20 if env::var("CLICOLOR_FORCE").is_ok() {
22 return true;
23 }
24
25 io::stdout().is_terminal()
27}
28
29pub struct StatusFormatter {
31 use_colors: bool,
32}
33
34impl StatusFormatter {
35 pub fn new() -> Self {
37 Self {
38 use_colors: should_use_colors(),
39 }
40 }
41
42 pub fn with_colors(use_colors: bool) -> Self {
44 Self { use_colors }
45 }
46
47 pub fn format(&self, status: &str, icon: &str) -> String {
49 if !self.use_colors {
50 return format!("{} {}", status, icon);
51 }
52
53 let status_upper = status.to_uppercase();
54 match status_upper.as_str() {
55 "SUCCESSFUL" | "COMPLETED" => {
56 format!("{} {}", status.green().bold(), icon)
57 }
58 "FAILED" | "ERROR" => {
59 format!("{} {}", status.red().bold(), icon)
60 }
61 "IN_PROGRESS" | "RUNNING" => {
62 format!("{} {}", status.yellow().bold(), icon)
63 }
64 "STOPPED" => {
65 format!("{} {}", status.bright_red().bold(), icon)
66 }
67 "PENDING" | "NOT_RUN" => {
68 format!("{} {}", status.cyan(), icon)
69 }
70 "PAUSED" => {
71 format!("{} {}", status.magenta(), icon)
72 }
73 _ => {
74 format!("{} {}", status.white(), icon)
75 }
76 }
77 }
78
79 pub fn format_status(&self, status: &str) -> String {
81 if !self.use_colors {
82 return status.to_string();
83 }
84
85 let status_upper = status.to_uppercase();
86 match status_upper.as_str() {
87 "SUCCESSFUL" | "COMPLETED" => status.green().bold().to_string(),
88 "FAILED" | "ERROR" => status.red().bold().to_string(),
89 "IN_PROGRESS" | "RUNNING" => status.yellow().bold().to_string(),
90 "STOPPED" => status.bright_red().bold().to_string(),
91 "PENDING" | "NOT_RUN" => status.cyan().to_string(),
92 "PAUSED" => status.magenta().to_string(),
93 _ => status.white().to_string(),
94 }
95 }
96}
97
98impl Default for StatusFormatter {
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use std::sync::Mutex;
108
109 static ENV_LOCK: Mutex<()> = Mutex::new(());
120
121 fn env_lock() -> std::sync::MutexGuard<'static, ()> {
124 ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner())
125 }
126
127 struct EnvGuard {
129 vars: Vec<(String, Option<String>)>,
130 }
131
132 impl EnvGuard {
133 fn new() -> Self {
134 Self { vars: Vec::new() }
135 }
136
137 fn set(&mut self, key: &str, value: &str) {
138 let old_value = env::var(key).ok();
139 self.vars.push((key.to_string(), old_value));
140 env::set_var(key, value);
141 }
142
143 fn remove(&mut self, key: &str) {
144 let old_value = env::var(key).ok();
145 self.vars.push((key.to_string(), old_value));
146 env::remove_var(key);
147 }
148 }
149
150 impl Drop for EnvGuard {
151 fn drop(&mut self) {
152 for (key, value) in self.vars.iter().rev() {
153 match value {
154 Some(v) => env::set_var(key, v),
155 None => env::remove_var(key),
156 }
157 }
158 }
159 }
160
161 #[test]
162 fn test_no_color_env_disables_colors() {
163 let _env = env_lock();
164 let mut guard = EnvGuard::new();
165 guard.set("NO_COLOR", "1");
166 assert!(!should_use_colors());
167 }
168
169 #[test]
170 fn test_term_dumb_disables_colors() {
171 let _env = env_lock();
172 let mut guard = EnvGuard::new();
173 guard.remove("NO_COLOR");
174 guard.remove("CLICOLOR_FORCE");
175 guard.set("TERM", "dumb");
176 assert!(!should_use_colors());
177 }
178
179 #[test]
180 fn test_clicolor_force_enables_colors() {
181 let _env = env_lock();
182 let mut guard = EnvGuard::new();
183 guard.remove("NO_COLOR");
184 guard.set("CLICOLOR_FORCE", "1");
185 assert!(should_use_colors());
186 }
187
188 #[test]
189 fn test_no_color_takes_precedence_over_clicolor_force() {
190 let _env = env_lock();
191 let mut guard = EnvGuard::new();
192 guard.set("NO_COLOR", "1");
193 guard.set("CLICOLOR_FORCE", "1");
194 assert!(!should_use_colors());
195 }
196
197 #[test]
198 fn test_formatter_successful_status() {
199 let formatter = StatusFormatter::with_colors(false);
200 let result = formatter.format("SUCCESSFUL", "✅");
201 assert!(result.contains("SUCCESSFUL"));
202 assert!(result.contains("✅"));
203 }
204
205 #[test]
206 fn test_formatter_failed_status() {
207 let formatter = StatusFormatter::with_colors(false);
208 let result = formatter.format("FAILED", "❌");
209 assert!(result.contains("FAILED"));
210 assert!(result.contains("❌"));
211 }
212
213 #[test]
214 fn test_formatter_in_progress_status() {
215 let formatter = StatusFormatter::with_colors(false);
216 let result = formatter.format("IN_PROGRESS", "🔄");
217 assert!(result.contains("IN_PROGRESS"));
218 assert!(result.contains("🔄"));
219 }
220
221 #[test]
222 fn test_formatter_pending_status() {
223 let formatter = StatusFormatter::with_colors(false);
224 let result = formatter.format("PENDING", "⏳");
225 assert!(result.contains("PENDING"));
226 assert!(result.contains("⏳"));
227 }
228
229 #[test]
230 fn test_formatter_stopped_status() {
231 let formatter = StatusFormatter::with_colors(false);
232 let result = formatter.format("STOPPED", "⏹");
233 assert!(result.contains("STOPPED"));
234 assert!(result.contains("⏹"));
235 }
236
237 #[test]
238 fn test_formatter_paused_status() {
239 let formatter = StatusFormatter::with_colors(false);
240 let result = formatter.format("PAUSED", "⏸");
241 assert!(result.contains("PAUSED"));
242 assert!(result.contains("⏸"));
243 }
244
245 #[test]
246 fn test_formatter_unknown_status() {
247 let formatter = StatusFormatter::with_colors(false);
248 let result = formatter.format("UNKNOWN", "❓");
249 assert!(result.contains("UNKNOWN"));
250 assert!(result.contains("❓"));
251 }
252
253 #[test]
254 fn test_formatter_with_colors_enabled() {
255 let formatter = StatusFormatter::with_colors(true);
256 let result = formatter.format("SUCCESSFUL", "✅");
257 assert!(result.contains("✅"));
260 }
261
262 #[test]
263 fn test_format_status_without_icon() {
264 let formatter = StatusFormatter::with_colors(false);
265 let result = formatter.format_status("FAILED");
266 assert_eq!(result, "FAILED");
267 }
268
269 #[test]
270 fn test_formatter_case_insensitive() {
271 let formatter = StatusFormatter::with_colors(false);
272 let result1 = formatter.format("successful", "✅");
273 let result2 = formatter.format("SUCCESSFUL", "✅");
274 let result3 = formatter.format("Successful", "✅");
275
276 assert!(result1.contains("successful"));
277 assert!(result2.contains("SUCCESSFUL"));
278 assert!(result3.contains("Successful"));
279 }
280}