Skip to main content

hylix/
logging.rs

1use std::{borrow::Cow, time::Duration};
2
3use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry};
4
5use crate::{error::HylixError, error::HylixResult};
6
7/// Initialize logging for the Hylix CLI
8pub fn init_logging(verbose: bool, quiet: bool) -> color_eyre::Result<()> {
9    let filter = if quiet {
10        EnvFilter::new("error")
11    } else if verbose {
12        EnvFilter::new("debug")
13    } else {
14        EnvFilter::new("info")
15    };
16
17    let subscriber = Registry::default().with(filter).with(
18        fmt::layer()
19            .with_target(false)
20            .with_thread_ids(false)
21            .with_thread_names(false)
22            .with_file(false)
23            .with_line_number(false)
24            .with_ansi(false) // Disable ANSI in tracing-subscriber to avoid conflicts
25            .without_time()
26            .compact(),
27    );
28
29    tracing::subscriber::set_global_default(subscriber)?;
30
31    Ok(())
32}
33
34pub fn create_progress_bar() -> indicatif::ProgressBar {
35    let pb = indicatif::ProgressBar::new_spinner();
36    pb.enable_steady_tick(Duration::from_millis(100));
37    pb.set_style(
38        indicatif::ProgressStyle::default_spinner()
39            .template("{spinner:.green} {msg}")
40            .unwrap()
41            .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
42    );
43    pb
44}
45
46/// Create a progress bar for long-running operations
47pub fn create_progress_bar_with_msg(message: &str) -> indicatif::ProgressBar {
48    let pb = create_progress_bar();
49    pb.set_message(message.to_string());
50    pb
51}
52
53/// Helper function to execute a command with real-time progress output
54pub async fn execute_command_with_progress(
55    multi_progress: &indicatif::MultiProgress,
56    command_name: &str,
57    program: &str,
58    args: &[&str],
59    current_dir: Option<&str>,
60) -> HylixResult<bool> {
61    use std::collections::VecDeque;
62    use std::io::{BufRead, BufReader};
63    use std::process::Command;
64    use tokio::sync::mpsc;
65
66    let mut cmd = Command::new(program)
67        .current_dir(current_dir.unwrap_or("."))
68        .args(args)
69        .stdout(std::process::Stdio::piped())
70        .stderr(std::process::Stdio::piped())
71        .spawn()
72        .map_err(|e| HylixError::process(format!("Failed to spawn {command_name}: {e}")))?;
73
74    // Create progress bars dynamically as output arrives
75    let mut output_bars = Vec::new();
76    let max_lines = 15;
77
78    // Create initial progress bar for the command name
79    let initial_pb = multi_progress.add(indicatif::ProgressBar::new(1));
80    let template = console::style("  {msg}").blue().to_string();
81
82    initial_pb.set_style(
83        indicatif::ProgressStyle::default_bar()
84            .template(&template)
85            .unwrap()
86            .progress_chars("  "),
87    );
88
89    initial_pb.set_message(format!("{command_name}: Starting..."));
90    output_bars.push(initial_pb);
91
92    // Create channels for stdout and stderr
93    let (tx, mut rx) = mpsc::channel::<(String, bool)>(100); // (line, is_stderr)
94    let tx_stdout = tx.clone();
95    let tx_stderr = tx;
96
97    // Handle stdout
98    if let Some(stdout) = cmd.stdout.take() {
99        tokio::spawn(async move {
100            let reader = BufReader::new(stdout);
101            for line in reader.lines().map_while(Result::ok) {
102                let _ = tx_stdout.send((line, false)).await;
103            }
104        });
105    }
106
107    // Handle stderr
108    if let Some(stderr) = cmd.stderr.take() {
109        tokio::spawn(async move {
110            let reader = BufReader::new(stderr);
111            for line in reader.lines().map_while(Result::ok) {
112                let _ = tx_stderr.send((line, true)).await;
113            }
114        });
115    }
116
117    // Collect and display output in chronological order
118    let mut output_buffer = VecDeque::new();
119    let mut display_buffer = VecDeque::new();
120
121    // Process output as it comes in
122    while let Some((line, is_stderr)) = rx.recv().await {
123        output_buffer.push_back((line.clone(), is_stderr));
124        display_buffer.push_back((line.clone(), is_stderr));
125
126        // Keep only the last 15 lines for display purposes
127        if display_buffer.len() > max_lines {
128            display_buffer.pop_front();
129        }
130
131        // Create new progress bar if we have more output than progress bars
132        while output_bars.len() < output_buffer.len() && output_bars.len() < max_lines {
133            let new_pb = multi_progress.add(indicatif::ProgressBar::new(1));
134            let template = console::style("  {msg}").blue().to_string();
135
136            new_pb.set_style(
137                indicatif::ProgressStyle::default_bar()
138                    .template(&template)
139                    .unwrap()
140                    .progress_chars("  "),
141            );
142
143            output_bars.push(new_pb);
144        }
145
146        // Update all progress bars to show the current output
147        for (i, pb) in output_bars.iter().enumerate() {
148            if i < display_buffer.len() {
149                let (buf_line, buf_is_stderr) = &display_buffer[i];
150                let message = if *buf_is_stderr {
151                    format!("[stderr] {buf_line}")
152                } else {
153                    buf_line.clone()
154                };
155                pb.set_message(message);
156            } else {
157                pb.set_message("");
158            }
159        }
160    }
161
162    // Wait for command to complete
163    let status = cmd
164        .wait()
165        .map_err(|e| HylixError::process(format!("Failed to wait for {command_name}: {e}")))?;
166
167    // Give a moment to see the final output
168    tokio::time::sleep(Duration::from_millis(800)).await;
169
170    // Clear all output progress bars
171    for pb in &output_bars {
172        pb.finish_and_clear();
173    }
174
175    // If status is not success, print all logs with log_warning
176    if !status.success() {
177        log_warning(&format!(
178            "Command '{command_name}' failed with status: {status}"
179        ));
180        for (line, is_stderr) in &output_buffer {
181            let prefix = if *is_stderr { "[stderr]" } else { "[stdout]" };
182            log_warning(&format!("{prefix} {line}"));
183        }
184    }
185
186    Ok(status.success())
187}
188
189/// Log a success message with green color using console directly
190pub fn log_success(message: &str) {
191    if console::Term::stdout().features().colors_supported() {
192        println!("{} {}", console::style("✓").green(), message);
193    } else {
194        println!("✓ {message}");
195    }
196}
197
198/// Log an error message with red color using console directly
199pub fn log_error(message: &str) {
200    if console::Term::stdout().features().colors_supported() {
201        eprintln!("{} {}", console::style("✗").red(), message);
202    } else {
203        eprintln!("✗ {message}");
204    }
205}
206
207/// Log a warning message with yellow color using console directly
208pub fn log_warning(message: &str) {
209    if console::Term::stdout().features().colors_supported() {
210        println!("{} {}", console::style("⚠").yellow(), message);
211    } else {
212        println!("⚠ {message}");
213    }
214}
215
216/// Log an info message with blue color using console directly
217pub fn log_info(message: &str) {
218    if console::Term::stdout().features().colors_supported() {
219        println!("{} {}", console::style("ℹ").blue(), message);
220    } else {
221        println!("ℹ {message}");
222    }
223}
224
225/// Progress executor for multiple tasks
226pub struct ProgressExecutor {
227    mpb: indicatif::MultiProgress,
228}
229
230impl ProgressExecutor {
231    pub fn new() -> Self {
232        Self {
233            mpb: indicatif::MultiProgress::new(),
234        }
235    }
236
237    pub fn add_task<S: Into<Cow<'static, str>>>(&self, message: S) -> indicatif::ProgressBar {
238        let pb = self.mpb.add(create_progress_bar());
239        pb.set_message(message);
240        pb
241    }
242
243    pub async fn execute_command<S: Into<Cow<'static, str>>>(
244        &self,
245        message: S,
246        program: &str,
247        args: &[&str],
248        current_dir: Option<&str>,
249    ) -> HylixResult<bool> {
250        let _pb = self.add_task(message);
251
252        let command_name = format!("{} {}", program, args.join(" "));
253
254        execute_command_with_progress(&self.mpb, &command_name, program, args, current_dir).await
255    }
256
257    pub fn clear(&self) -> HylixResult<()> {
258        self.mpb
259            .clear()
260            .map_err(|e| HylixError::process(format!("Failed to clear progress bars: {e}")))
261    }
262}
263
264impl Default for ProgressExecutor {
265    fn default() -> Self {
266        Self::new()
267    }
268}