docker_image_pusher/output/
mod.rs1use 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 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 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 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 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 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 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 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 pub fn table(&self, headers: &[&str], rows: &[Vec<String>]) {
169 if !self.quiet && !headers.is_empty() {
170 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!(" ");
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!(" ");
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 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 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 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!(); }
264 }
265 }
266
267 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 pub fn format_speed(&self, bytes_per_second: u64) -> String {
302 format!("{}/s", self.format_size(bytes_per_second))
303 }
304
305 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 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 pub fn reset_timer(&mut self) {
323 self.start_time = Some(Instant::now());
324 }
325}