tauri_typegen/interface/
output.rs

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            // Verbose mode: use old-style logging
126            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            // Non-verbose mode: update the single progress bar
134            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            // Verbose mode: use old-style logging
146            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        // In non-verbose mode, we just continue to the next step (no need to "complete")
154    }
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        // Only log in verbose mode
166        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        // Ensure progress bar is cleared when reporter is dropped
180        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)); // Verbose enables debug
227    }
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        // Test step progression
252        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}