1use indicatif::{ProgressBar, ProgressStyle};
2use std::fmt;
3use std::time::Duration;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum LogLevel {
7 Error,
8 Warning,
9 Info,
10 Debug,
11 Verbose,
12}
13
14impl fmt::Display for LogLevel {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 match self {
17 LogLevel::Error => write!(f, "ERROR"),
18 LogLevel::Warning => write!(f, "WARN"),
19 LogLevel::Info => write!(f, "INFO"),
20 LogLevel::Debug => write!(f, "DEBUG"),
21 LogLevel::Verbose => write!(f, "VERBOSE"),
22 }
23 }
24}
25
26#[derive(Debug, Clone)]
27pub struct Logger {
28 verbose: bool,
29 debug: bool,
30}
31
32impl Logger {
33 pub fn new(verbose: bool, debug: bool) -> Self {
34 Self { verbose, debug }
35 }
36
37 pub fn should_log(&self, level: LogLevel) -> bool {
38 match level {
39 LogLevel::Error | LogLevel::Warning | LogLevel::Info => true,
40 LogLevel::Debug => self.debug || self.verbose,
41 LogLevel::Verbose => self.verbose,
42 }
43 }
44
45 pub fn log(&self, level: LogLevel, message: &str) {
46 if self.should_log(level) {
47 let icon = match level {
48 LogLevel::Error => "ā",
49 LogLevel::Warning => "ā ļø",
50 LogLevel::Info => "",
51 LogLevel::Debug => "š",
52 LogLevel::Verbose => "š¬",
53 };
54 if icon.is_empty() {
55 println!("{}", message);
56 } else {
57 println!("{} {}", icon, message);
58 }
59 }
60 }
61
62 pub fn error(&self, message: &str) {
63 self.log(LogLevel::Error, message);
64 }
65
66 pub fn warning(&self, message: &str) {
67 self.log(LogLevel::Warning, message);
68 }
69
70 pub fn info(&self, message: &str) {
71 self.log(LogLevel::Info, message);
72 }
73
74 pub fn debug(&self, message: &str) {
75 self.log(LogLevel::Debug, message);
76 }
77
78 pub fn verbose(&self, message: &str) {
79 self.log(LogLevel::Verbose, message);
80 }
81
82 pub fn is_verbose(&self) -> bool {
83 self.verbose
84 }
85}
86
87pub struct ProgressReporter {
88 logger: Logger,
89 progress_bar: Option<ProgressBar>,
90 current_step: usize,
91 total_steps: usize,
92 step_name: String,
93}
94
95impl ProgressReporter {
96 pub fn new(logger: Logger, total_steps: usize) -> Self {
97 let progress_bar = if !logger.is_verbose() {
98 let pb = ProgressBar::new_spinner();
99 pb.set_style(
100 ProgressStyle::default_spinner()
101 .template("{spinner:.cyan} {msg}")
102 .unwrap()
103 .tick_strings(&["ā ", "ā ", "ā ¹", "ā ø", "ā ¼", "ā “", "ā ¦", "ā §", "ā ", "ā "]),
104 );
105 pb.enable_steady_tick(Duration::from_millis(100));
106 Some(pb)
107 } else {
108 None
109 };
110
111 Self {
112 logger,
113 progress_bar,
114 current_step: 0,
115 total_steps,
116 step_name: String::new(),
117 }
118 }
119
120 pub fn start_step(&mut self, step_name: &str) {
121 self.current_step += 1;
122 self.step_name = step_name.to_string();
123
124 if self.logger.is_verbose() {
125 let progress = if self.total_steps > 0 {
127 format!(" ({}/{})", self.current_step, self.total_steps)
128 } else {
129 String::new()
130 };
131 self.logger.info(&format!("š {}{}", step_name, progress));
132 } else {
133 if let Some(ref pb) = self.progress_bar {
135 pb.set_message(format!(
136 "{} ({}/{})",
137 step_name, self.current_step, self.total_steps
138 ));
139 }
140 }
141 }
142
143 pub fn complete_step(&mut self, message: Option<&str>) {
144 if self.logger.is_verbose() {
145 if let Some(msg) = message {
147 self.logger
148 .info(&format!("ā
{} - {}", self.step_name, msg));
149 } else {
150 self.logger.info(&format!("ā
{}", self.step_name));
151 }
152 }
153 }
155
156 pub fn fail_step(&mut self, error: &str) {
157 if let Some(ref pb) = self.progress_bar {
158 pb.finish_with_message(format!("ā {} - {}", self.step_name, error));
159 }
160 self.logger
161 .error(&format!("Failed {}: {}", self.step_name, error));
162 }
163
164 pub fn update_progress(&self, message: &str) {
165 self.logger.verbose(message);
167 }
168
169 pub fn finish(&self, total_message: &str) {
170 if let Some(ref pb) = self.progress_bar {
171 pb.finish_and_clear();
172 }
173 println!("ā {}", total_message);
174 }
175}
176
177impl Drop for ProgressReporter {
178 fn drop(&mut self) {
179 if let Some(ref pb) = self.progress_bar {
181 pb.finish_and_clear();
182 }
183 }
184}
185
186pub fn print_usage_info(output_path: &str, generated_files: &[String], command_count: usize) {
187 println!(
188 "\nā Generated TypeScript bindings for {} command{}",
189 command_count,
190 if command_count == 1 { "" } else { "s" }
191 );
192 println!("š Location: {}", output_path);
193
194 println!("\nš” Import in your frontend:");
195 for file in generated_files {
196 if file.ends_with("index.ts") || file.ends_with("index.js") {
197 println!(
198 " import {{ /* commands */ }} from '{}'",
199 output_path.trim_end_matches('/')
200 );
201 break;
202 }
203 }
204}
205
206pub fn print_dependency_visualization_info(output_path: &str) {
207 println!("\nš Dependency visualization generated:");
208 println!(" š {}/dependency-graph.txt", output_path);
209 println!(" š {}/dependency-graph.dot", output_path);
210 println!(
211 "\nš” To generate a visual graph: dot -Tpng {}/dependency-graph.dot -o graph.png",
212 output_path
213 );
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_logger_verbose_mode() {
222 let logger = Logger::new(true, false);
223 assert!(logger.should_log(LogLevel::Verbose));
224 assert!(logger.should_log(LogLevel::Info));
225 assert!(logger.should_log(LogLevel::Error));
226 assert!(logger.should_log(LogLevel::Debug)); }
228
229 #[test]
230 fn test_logger_normal_mode() {
231 let logger = Logger::new(false, false);
232 assert!(!logger.should_log(LogLevel::Verbose));
233 assert!(!logger.should_log(LogLevel::Debug));
234 assert!(logger.should_log(LogLevel::Info));
235 assert!(logger.should_log(LogLevel::Error));
236 }
237
238 #[test]
239 fn test_logger_debug_mode() {
240 let logger = Logger::new(false, true);
241 assert!(!logger.should_log(LogLevel::Verbose));
242 assert!(logger.should_log(LogLevel::Debug));
243 assert!(logger.should_log(LogLevel::Info));
244 }
245
246 #[test]
247 fn test_progress_reporter() {
248 let logger = Logger::new(false, false);
249 let mut reporter = ProgressReporter::new(logger, 3);
250
251 assert_eq!(reporter.current_step, 0);
253
254 reporter.start_step("First Step");
255 assert_eq!(reporter.current_step, 1);
256 assert_eq!(reporter.step_name, "First Step");
257
258 reporter.start_step("Second Step");
259 assert_eq!(reporter.current_step, 2);
260 assert_eq!(reporter.step_name, "Second Step");
261 }
262}