docker_image_pusher/output/
mod.rs

1//! Enhanced output control module with structured logging
2//!
3//! This module provides the [`OutputManager`] for controlling output verbosity, formatting logs,
4//! and tracking operation timing. It supports quiet, verbose, and structured output for CI and debugging.
5
6use std::io::{self, Write};
7use std::time::{Duration, Instant};
8
9#[derive(Clone, Debug)]
10pub struct OutputManager {
11    pub verbose: bool,
12    quiet: bool,
13    start_time: Option<Instant>,
14}
15
16impl OutputManager {
17    pub fn new(verbose: bool) -> Self {
18        Self {
19            verbose,
20            quiet: false,
21            start_time: Some(Instant::now()),
22        }
23    }
24
25    pub fn new_quiet() -> Self {
26        Self {
27            verbose: false,
28            quiet: true,
29            start_time: Some(Instant::now()),
30        }
31    }
32
33    // Structured logging levels
34    pub fn trace(&self, message: &str) {
35        if self.verbose && !self.quiet {
36            println!("šŸ” TRACE: {}", message);
37        }
38    }
39
40    pub fn debug(&self, message: &str) {
41        if self.verbose && !self.quiet {
42            println!("šŸ› DEBUG: {}", message);
43        }
44    }
45
46    pub fn verbose(&self, message: &str) {
47        if self.verbose && !self.quiet {
48            println!("šŸ“ {}", message);
49        }
50    }
51
52    pub fn info(&self, message: &str) {
53        if !self.quiet {
54            println!("ā„¹ļø  {}", message);
55        }
56    }
57
58    pub fn success(&self, message: &str) {
59        if !self.quiet {
60            println!("āœ… {}", message);
61        }
62    }
63
64    pub fn warning(&self, message: &str) {
65        if !self.quiet {
66            println!("āš ļø  WARNING: {}", message);
67        }
68    }
69
70    pub fn error(&self, message: &str) {
71        eprintln!("āŒ ERROR: {}", message);
72    }
73
74    // Progress indicators
75    pub fn progress(&self, message: &str) {
76        if !self.quiet {
77            print!("ā³ {}...", message);
78            let _ = io::stdout().flush();
79        }
80    }
81
82    pub fn progress_done(&self) {
83        if !self.quiet {
84            println!(" āœ“");
85        }
86    }
87
88    // Section headers
89    pub fn section(&self, title: &str) {
90        if !self.quiet {
91            println!("\nšŸ”§ {}", title);
92            println!("{}", "=".repeat(title.len() + 3));
93        }
94    }
95
96    pub fn subsection(&self, title: &str) {
97        if !self.quiet {
98            println!("\nšŸ“‚ {}", title);
99            println!("{}", "-".repeat(title.len() + 3));
100        }
101    }
102
103    pub fn step(&self, step: &str) {
104        if !self.quiet {
105            println!("  šŸš€ {}", step);
106        }
107    }
108
109    pub fn detail(&self, detail: &str) {
110        if self.verbose && !self.quiet {
111            println!("    šŸ“‹ {}", detail);
112        }
113    }
114
115    // Summary method for displaying structured information
116    pub fn summary(&self, title: &str, items: &[String]) {
117        if !self.quiet {
118            println!("\nšŸ“‹ {}", title);
119            println!("{}", "─".repeat(title.len() + 3));
120
121            for item in items {
122                println!("  • {}", item);
123            }
124
125            if items.is_empty() {
126                println!("  (No items to display)");
127            }
128        }
129    }
130
131    // Alternative summary method for key-value pairs
132    pub fn summary_kv(&self, title: &str, items: &[(&str, String)]) {
133        if !self.quiet {
134            println!("\nšŸ“‹ {}", title);
135            println!("{}", "─".repeat(title.len() + 3));
136
137            // Find the maximum key length for alignment
138            let max_key_len = items.iter().map(|(key, _)| key.len()).max().unwrap_or(0);
139
140            for (key, value) in items {
141                println!("  {:width$}: {}", key, value, width = max_key_len);
142            }
143
144            if items.is_empty() {
145                println!("  (No items to display)");
146            }
147        }
148    }
149
150    // Structured list output
151    pub fn list(&self, title: &str, items: &[String]) {
152        if !self.quiet {
153            if !title.is_empty() {
154                println!("\nšŸ“ {}", title);
155            }
156
157            for (index, item) in items.iter().enumerate() {
158                println!("  {}. {}", index + 1, item);
159            }
160
161            if items.is_empty() && !title.is_empty() {
162                println!("  (No items in list)");
163            }
164        }
165    }
166
167    // Table-like output for structured data
168    pub fn table(&self, headers: &[&str], rows: &[Vec<String>]) {
169        if !self.quiet && !headers.is_empty() {
170            // Calculate column widths
171            let mut col_widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
172
173            for row in rows {
174                for (i, cell) in row.iter().enumerate() {
175                    if i < col_widths.len() {
176                        col_widths[i] = col_widths[i].max(cell.len());
177                    }
178                }
179            }
180
181            // Print header
182            print!("  ");
183            for (i, header) in headers.iter().enumerate() {
184                print!("{:width$}", header, width = col_widths[i]);
185                if i < headers.len() - 1 {
186                    print!(" │ ");
187                }
188            }
189            println!();
190
191            // Print separator
192            print!("  ");
193            for (i, &width) in col_widths.iter().enumerate() {
194                print!("{}", "─".repeat(width));
195                if i < col_widths.len() - 1 {
196                    print!("─┼─");
197                }
198            }
199            println!();
200
201            // Print rows
202            for row in rows {
203                print!("  ");
204                for (i, cell) in row.iter().enumerate() {
205                    if i < col_widths.len() {
206                        print!("{:width$}", cell, width = col_widths[i]);
207                        if i < headers.len() - 1 {
208                            print!(" │ ");
209                        }
210                    }
211                }
212                println!();
213            }
214        }
215    }
216
217    // Enhanced progress with metrics
218    pub fn progress_with_metrics(&self, current: u64, total: u64, operation: &str) {
219        if !self.quiet {
220            let percentage = if total > 0 {
221                (current as f64 / total as f64) * 100.0
222            } else {
223                100.0
224            };
225
226            println!(
227                "šŸ“Š {}: {}/{} ({:.1}%)",
228                operation,
229                self.format_size(current),
230                self.format_size(total),
231                percentage
232            );
233        }
234    }
235
236    // Progress bar visualization
237    pub fn progress_bar(&self, current: u64, total: u64, operation: &str, width: usize) {
238        if !self.quiet {
239            let percentage = if total > 0 {
240                (current as f64 / total as f64) * 100.0
241            } else {
242                100.0
243            };
244
245            let filled = (width as f64 * (percentage / 100.0)) as usize;
246            let empty = width - filled;
247
248            let bar = format!("{}{}", "ā–ˆ".repeat(filled), "ā–‘".repeat(empty));
249
250            print!(
251                "\ršŸ“Š {}: [{}] {:.1}% ({}/{})",
252                operation,
253                bar,
254                percentage,
255                self.format_size(current),
256                self.format_size(total)
257            );
258
259            let _ = io::stdout().flush();
260
261            if current >= total {
262                println!(); // New line when complete
263            }
264        }
265    }
266
267    // Helper methods for formatting
268    pub fn format_size(&self, bytes: u64) -> String {
269        const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
270        let mut size = bytes as f64;
271        let mut unit_index = 0;
272
273        while size >= 1024.0 && unit_index < UNITS.len() - 1 {
274            size /= 1024.0;
275            unit_index += 1;
276        }
277
278        if unit_index == 0 {
279            format!("{} {}", bytes, UNITS[unit_index])
280        } else {
281            format!("{:.1} {}", size, UNITS[unit_index])
282        }
283    }
284
285    pub fn format_duration(&self, duration: Duration) -> String {
286        let total_seconds = duration.as_secs();
287        let hours = total_seconds / 3600;
288        let minutes = (total_seconds % 3600) / 60;
289        let seconds = total_seconds % 60;
290
291        if hours > 0 {
292            format!("{}h {}m {}s", hours, minutes, seconds)
293        } else if minutes > 0 {
294            format!("{}m {}s", minutes, seconds)
295        } else {
296            format!("{}s", seconds)
297        }
298    }
299
300    // Format speed (bytes per second)
301    pub fn format_speed(&self, bytes_per_second: u64) -> String {
302        format!("{}/s", self.format_size(bytes_per_second))
303    }
304
305    // Format percentage
306    pub fn format_percentage(&self, current: u64, total: u64) -> String {
307        if total == 0 {
308            "100.0%".to_string()
309        } else {
310            format!("{:.1}%", (current as f64 / total as f64) * 100.0)
311        }
312    }
313
314    // Get elapsed time since start
315    pub fn elapsed_time(&self) -> Duration {
316        self.start_time
317            .map(|start| start.elapsed())
318            .unwrap_or_else(|| Duration::from_secs(0))
319    }
320
321    // Reset start time
322    pub fn reset_timer(&mut self) {
323        self.start_time = Some(Instant::now());
324    }
325}