error_forge/
console_theme.rs

1//! Console theming for error display in CLI applications
2//! 
3//! This module provides ANSI color formatting for error messages
4//! displayed in terminal environments.
5
6/// Color theme for console error output
7pub struct ConsoleTheme {
8    error_color: String,
9    warning_color: String,
10    info_color: String,
11    success_color: String,
12    caption_color: String,
13    reset: String,
14    bold: String,
15    dim: String,
16}
17
18impl Default for ConsoleTheme {
19    fn default() -> Self {
20        Self {
21            error_color: "\x1b[31m".to_string(),   // Red
22            warning_color: "\x1b[33m".to_string(), // Yellow
23            info_color: "\x1b[34m".to_string(),    // Blue
24            success_color: "\x1b[32m".to_string(), // Green
25            caption_color: "\x1b[36m".to_string(), // Cyan
26            reset: "\x1b[0m".to_string(),
27            bold: "\x1b[1m".to_string(),
28            dim: "\x1b[2m".to_string(),
29        }
30    }
31}
32
33impl ConsoleTheme {
34    /// Create a new theme with default colors
35    pub fn new() -> Self {
36        Self::default()
37    }
38    
39    /// Create a new theme with no colors (plain text)
40    pub fn plain() -> Self {
41        Self {
42            error_color: "".to_string(),
43            warning_color: "".to_string(),
44            info_color: "".to_string(),
45            success_color: "".to_string(),
46            caption_color: "".to_string(),
47            reset: "".to_string(),
48            bold: "".to_string(),
49            dim: "".to_string(),
50        }
51    }
52    
53    /// Format an error message with the error color
54    pub fn error(&self, text: &str) -> String {
55        format!("{}{}{}", self.error_color, text, self.reset)
56    }
57    
58    /// Format a warning message with the warning color
59    pub fn warning(&self, text: &str) -> String {
60        format!("{}{}{}", self.warning_color, text, self.reset)
61    }
62    
63    /// Format an info message with the info color
64    pub fn info(&self, text: &str) -> String {
65        format!("{}{}{}", self.info_color, text, self.reset)
66    }
67    
68    /// Format a success message with the success color
69    pub fn success(&self, text: &str) -> String {
70        format!("{}{}{}", self.success_color, text, self.reset)
71    }
72    
73    /// Format a caption with the caption color
74    pub fn caption(&self, text: &str) -> String {
75        format!("{}{}{}", self.caption_color, text, self.reset)
76    }
77    
78    /// Format text as bold
79    pub fn bold(&self, text: &str) -> String {
80        format!("{}{}{}", self.bold, text, self.reset)
81    }
82    
83    /// Format text as dim
84    pub fn dim(&self, text: &str) -> String {
85        format!("{}{}{}", self.dim, text, self.reset)
86    }
87    
88    /// Format an error display in a structured way
89    pub fn format_error<E: crate::error::ForgeError>(&self, err: &E) -> String {
90        let mut result = String::new();
91        
92        // Add the error caption
93        result.push_str(&format!("{}\n", self.caption(&format!("⚠️  {}", err.caption()))));
94        
95        // Add the error message
96        result.push_str(&format!("{}\n", self.error(&err.to_string())));
97        
98        // Add retryable status if applicable
99        if err.is_retryable() {
100            result.push_str(&format!("{}Retryable: {}{}\n", 
101                self.dim, 
102                self.success("Yes"), 
103                self.reset
104            ));
105        } else {
106            result.push_str(&format!("{}Retryable: {}{}\n", 
107                self.dim, 
108                self.error("No"), 
109                self.reset
110            ));
111        }
112        
113        // Add source error if available
114        if let Some(source) = err.source() {
115            result.push_str(&format!("{}Caused by: {}{}\n", 
116                self.dim, 
117                self.error(&source.to_string()), 
118                self.reset
119            ));
120        }
121        
122        result
123    }
124}
125
126/// Pretty-print an error to stderr with the default theme
127pub fn print_error<E: crate::error::ForgeError>(err: &E) {
128    let theme = ConsoleTheme::default();
129    eprintln!("{}", theme.format_error(err));
130}
131
132/// Install a panic hook that formats panics using the ConsoleTheme
133pub fn install_panic_hook() {
134    let theme = ConsoleTheme::default();
135    std::panic::set_hook(Box::new(move |panic_info| {
136        let message = match panic_info.payload().downcast_ref::<&str>() {
137            Some(s) => *s,
138            None => match panic_info.payload().downcast_ref::<String>() {
139                Some(s) => s.as_str(),
140                None => "Unknown panic",
141            },
142        };
143        
144        let location = if let Some(location) = panic_info.location() {
145            format!("at {}:{}", location.file(), location.line())
146        } else {
147            "at unknown location".to_string()
148        };
149        
150        eprintln!("{}", theme.caption("💥 PANIC"));
151        eprintln!("{}", theme.error(&format!("{} {}", message, theme.dim(&location))));
152    }));
153}