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
108 struct EnvGuard {
110 vars: Vec<(String, Option<String>)>,
111 }
112
113 impl EnvGuard {
114 fn new() -> Self {
115 Self { vars: Vec::new() }
116 }
117
118 fn set(&mut self, key: &str, value: &str) {
119 let old_value = env::var(key).ok();
120 self.vars.push((key.to_string(), old_value));
121 env::set_var(key, value);
122 }
123
124 fn remove(&mut self, key: &str) {
125 let old_value = env::var(key).ok();
126 self.vars.push((key.to_string(), old_value));
127 env::remove_var(key);
128 }
129 }
130
131 impl Drop for EnvGuard {
132 fn drop(&mut self) {
133 for (key, value) in self.vars.iter().rev() {
134 match value {
135 Some(v) => env::set_var(key, v),
136 None => env::remove_var(key),
137 }
138 }
139 }
140 }
141
142 #[test]
143 fn test_no_color_env_disables_colors() {
144 let mut guard = EnvGuard::new();
145 guard.set("NO_COLOR", "1");
146 assert!(!should_use_colors());
147 }
148
149 #[test]
150 fn test_term_dumb_disables_colors() {
151 let mut guard = EnvGuard::new();
152 guard.remove("NO_COLOR");
153 guard.remove("CLICOLOR_FORCE");
154 guard.set("TERM", "dumb");
155 assert!(!should_use_colors());
156 }
157
158 #[test]
159 fn test_clicolor_force_enables_colors() {
160 let mut guard = EnvGuard::new();
161 guard.remove("NO_COLOR");
162 guard.set("CLICOLOR_FORCE", "1");
163 assert!(should_use_colors());
164 }
165
166 #[test]
167 fn test_no_color_takes_precedence_over_clicolor_force() {
168 let mut guard = EnvGuard::new();
169 guard.set("NO_COLOR", "1");
170 guard.set("CLICOLOR_FORCE", "1");
171 assert!(!should_use_colors());
172 }
173
174 #[test]
175 fn test_formatter_successful_status() {
176 let formatter = StatusFormatter::with_colors(false);
177 let result = formatter.format("SUCCESSFUL", "✅");
178 assert!(result.contains("SUCCESSFUL"));
179 assert!(result.contains("✅"));
180 }
181
182 #[test]
183 fn test_formatter_failed_status() {
184 let formatter = StatusFormatter::with_colors(false);
185 let result = formatter.format("FAILED", "❌");
186 assert!(result.contains("FAILED"));
187 assert!(result.contains("❌"));
188 }
189
190 #[test]
191 fn test_formatter_in_progress_status() {
192 let formatter = StatusFormatter::with_colors(false);
193 let result = formatter.format("IN_PROGRESS", "🔄");
194 assert!(result.contains("IN_PROGRESS"));
195 assert!(result.contains("🔄"));
196 }
197
198 #[test]
199 fn test_formatter_pending_status() {
200 let formatter = StatusFormatter::with_colors(false);
201 let result = formatter.format("PENDING", "⏳");
202 assert!(result.contains("PENDING"));
203 assert!(result.contains("⏳"));
204 }
205
206 #[test]
207 fn test_formatter_stopped_status() {
208 let formatter = StatusFormatter::with_colors(false);
209 let result = formatter.format("STOPPED", "⏹");
210 assert!(result.contains("STOPPED"));
211 assert!(result.contains("⏹"));
212 }
213
214 #[test]
215 fn test_formatter_paused_status() {
216 let formatter = StatusFormatter::with_colors(false);
217 let result = formatter.format("PAUSED", "⏸");
218 assert!(result.contains("PAUSED"));
219 assert!(result.contains("⏸"));
220 }
221
222 #[test]
223 fn test_formatter_unknown_status() {
224 let formatter = StatusFormatter::with_colors(false);
225 let result = formatter.format("UNKNOWN", "❓");
226 assert!(result.contains("UNKNOWN"));
227 assert!(result.contains("❓"));
228 }
229
230 #[test]
231 fn test_formatter_with_colors_enabled() {
232 let formatter = StatusFormatter::with_colors(true);
233 let result = formatter.format("SUCCESSFUL", "✅");
234 assert!(result.contains("✅"));
237 }
238
239 #[test]
240 fn test_format_status_without_icon() {
241 let formatter = StatusFormatter::with_colors(false);
242 let result = formatter.format_status("FAILED");
243 assert_eq!(result, "FAILED");
244 }
245
246 #[test]
247 fn test_formatter_case_insensitive() {
248 let formatter = StatusFormatter::with_colors(false);
249 let result1 = formatter.format("successful", "✅");
250 let result2 = formatter.format("SUCCESSFUL", "✅");
251 let result3 = formatter.format("Successful", "✅");
252
253 assert!(result1.contains("successful"));
254 assert!(result2.contains("SUCCESSFUL"));
255 assert!(result3.contains("Successful"));
256 }
257}