1#![allow(dead_code)] use console::{Style, Term};
11use serde::Serialize;
12use std::io::IsTerminal;
13use std::sync::atomic::{AtomicBool, Ordering};
14
15static JSON_MODE: AtomicBool = AtomicBool::new(false);
16
17#[derive(Debug, Clone, Serialize)]
21pub struct JsonResponse<T: Serialize> {
22 pub success: bool,
24 pub command: String,
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub data: Option<T>,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub error: Option<String>,
32}
33
34impl<T: Serialize> JsonResponse<T> {
35 pub fn success(command: impl Into<String>, data: T) -> Self {
37 Self {
38 success: true,
39 command: command.into(),
40 data: Some(data),
41 error: None,
42 }
43 }
44
45 pub fn error(command: impl Into<String>, error: impl Into<String>) -> JsonResponse<()> {
47 JsonResponse {
48 success: false,
49 command: command.into(),
50 data: None,
51 error: Some(error.into()),
52 }
53 }
54
55 pub fn print(&self) -> Result<(), serde_json::Error> {
57 println!("{}", serde_json::to_string_pretty(self)?);
58 Ok(())
59 }
60}
61
62pub fn is_json_mode() -> bool {
64 JSON_MODE.load(Ordering::Relaxed)
65}
66
67pub struct Output {
69 term: Term,
70 colors_enabled: bool,
71 success_style: Style,
73 error_style: Style,
74 warn_style: Style,
75 info_style: Style,
76 bold_style: Style,
77 dim_style: Style,
78}
79
80impl Default for Output {
81 fn default() -> Self {
82 Self::new()
83 }
84}
85
86impl Output {
87 pub fn new() -> Self {
89 let term = Term::stderr();
90 let colors_enabled = Self::should_use_colors(&term);
91
92 Self {
93 term,
94 colors_enabled,
95 success_style: Style::new().green(),
96 error_style: Style::new().red(),
97 warn_style: Style::new().yellow(),
98 info_style: Style::new().cyan(),
99 bold_style: Style::new().bold(),
100 dim_style: Style::new().dim(),
101 }
102 }
103
104 pub fn stdout() -> Self {
106 let term = Term::stdout();
107 let colors_enabled = Self::should_use_colors(&term);
108
109 Self {
110 term,
111 colors_enabled,
112 success_style: Style::new().green(),
113 error_style: Style::new().red(),
114 warn_style: Style::new().yellow(),
115 info_style: Style::new().cyan(),
116 bold_style: Style::new().bold(),
117 dim_style: Style::new().dim(),
118 }
119 }
120
121 fn should_use_colors(term: &Term) -> bool {
123 if JSON_MODE.load(Ordering::Relaxed) {
124 return false;
125 }
126
127 if std::env::var("NO_COLOR").is_ok() {
129 return false;
130 }
131
132 if !term.is_term() {
134 return false;
135 }
136
137 if !std::io::stderr().is_terminal() {
139 return false;
140 }
141
142 true
143 }
144
145 pub fn success(&self, text: &str) -> String {
147 if self.colors_enabled {
148 self.success_style.apply_to(text).to_string()
149 } else {
150 text.to_string()
151 }
152 }
153
154 pub fn error(&self, text: &str) -> String {
156 if self.colors_enabled {
157 self.error_style.apply_to(text).to_string()
158 } else {
159 text.to_string()
160 }
161 }
162
163 pub fn warn(&self, text: &str) -> String {
165 if self.colors_enabled {
166 self.warn_style.apply_to(text).to_string()
167 } else {
168 text.to_string()
169 }
170 }
171
172 pub fn info(&self, text: &str) -> String {
174 if self.colors_enabled {
175 self.info_style.apply_to(text).to_string()
176 } else {
177 text.to_string()
178 }
179 }
180
181 pub fn bold(&self, text: &str) -> String {
183 if self.colors_enabled {
184 self.bold_style.apply_to(text).to_string()
185 } else {
186 text.to_string()
187 }
188 }
189
190 pub fn dim(&self, text: &str) -> String {
192 if self.colors_enabled {
193 self.dim_style.apply_to(text).to_string()
194 } else {
195 text.to_string()
196 }
197 }
198
199 pub fn print_success(&self, message: &str) {
201 let icon = if self.colors_enabled {
202 self.success_style.apply_to("\u{2713}").to_string()
203 } else {
204 "[OK]".to_string()
205 };
206 eprintln!("{} {}", icon, message);
207 }
208
209 pub fn print_error(&self, message: &str) {
211 let icon = if self.colors_enabled {
212 self.error_style.apply_to("\u{2717}").to_string()
213 } else {
214 "[ERROR]".to_string()
215 };
216 eprintln!("{} {}", icon, message);
217 }
218
219 pub fn print_warn(&self, message: &str) {
221 let icon = if self.colors_enabled {
222 self.warn_style.apply_to("!").to_string()
223 } else {
224 "[WARN]".to_string()
225 };
226 eprintln!("{} {}", icon, message);
227 }
228
229 pub fn print_info(&self, message: &str) {
231 let icon = if self.colors_enabled {
232 self.info_style.apply_to("i").to_string()
233 } else {
234 "[INFO]".to_string()
235 };
236 eprintln!("{} {}", icon, message);
237 }
238
239 pub fn print_heading(&self, text: &str) {
241 let styled = if self.colors_enabled {
242 self.bold_style.apply_to(text).to_string()
243 } else {
244 text.to_string()
245 };
246 eprintln!("{}", styled);
247 }
248
249 pub fn println(&self, text: &str) {
251 eprintln!("{}", text);
252 }
253
254 pub fn newline(&self) {
256 eprintln!();
257 }
258
259 pub fn key_value(&self, key: &str, value: &str) -> String {
261 if self.colors_enabled {
262 format!(
263 "{}: {}",
264 self.dim_style.apply_to(key),
265 self.info_style.apply_to(value)
266 )
267 } else {
268 format!("{}: {}", key, value)
269 }
270 }
271
272 pub fn status(&self, passed: bool) -> &'static str {
274 if passed {
275 if self.colors_enabled {
276 "\u{2713}"
277 } else {
278 "[PASS]"
279 }
280 } else if self.colors_enabled {
281 "\u{2717}"
282 } else {
283 "[FAIL]"
284 }
285 }
286}
287
288pub fn set_json_mode(enabled: bool) {
292 JSON_MODE.store(enabled, Ordering::Relaxed);
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 impl Output {
300 fn new_without_colors() -> Self {
302 let term = Term::stderr();
303 Self {
304 term,
305 colors_enabled: false,
306 success_style: Style::new().green(),
307 error_style: Style::new().red(),
308 warn_style: Style::new().yellow(),
309 info_style: Style::new().cyan(),
310 bold_style: Style::new().bold(),
311 dim_style: Style::new().dim(),
312 }
313 }
314 }
315
316 #[test]
317 fn test_output_no_colors_in_test() {
318 let output = Output::new();
320 let success = output.success("test");
322 assert!(success.contains("test"));
323 }
324
325 #[test]
326 fn test_json_mode() {
327 let output = Output::new_without_colors();
329 let styled = output.success("test");
331 assert_eq!(styled, "test");
332 }
333
334 #[test]
335 fn test_key_value_format() {
336 let output = Output::new_without_colors();
338 let kv = output.key_value("name", "value");
339 assert_eq!(kv, "name: value");
340 }
341}