1use std::{borrow::Cow, time::Duration};
2
3use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry};
4
5use crate::{error::HylixError, error::HylixResult};
6
7pub 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) .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
46pub 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
53pub 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 let mut output_bars = Vec::new();
76 let max_lines = 15;
77
78 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 let (tx, mut rx) = mpsc::channel::<(String, bool)>(100); let tx_stdout = tx.clone();
95 let tx_stderr = tx;
96
97 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 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 let mut output_buffer = VecDeque::new();
119 let mut display_buffer = VecDeque::new();
120
121 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 if display_buffer.len() > max_lines {
128 display_buffer.pop_front();
129 }
130
131 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 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 let status = cmd
164 .wait()
165 .map_err(|e| HylixError::process(format!("Failed to wait for {command_name}: {e}")))?;
166
167 tokio::time::sleep(Duration::from_millis(800)).await;
169
170 for pb in &output_bars {
172 pb.finish_and_clear();
173 }
174
175 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
189pub 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
198pub 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
207pub 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
216pub 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
225pub 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}