vault_audit_tools/utils/
progress.rs1use std::io::{self, Write};
2use std::time::{Duration, Instant};
3
4pub struct ProgressBar {
6 total: Option<usize>,
7 current: usize,
8 last_update: Instant,
9 update_interval: Duration,
10 label: String,
11 started: Instant,
12 render_count: usize,
13}
14
15impl ProgressBar {
16 pub fn new(total: usize, label: &str) -> Self {
18 let mut pb = Self {
19 total: Some(total),
20 current: 0,
21 last_update: Instant::now(),
22 update_interval: Duration::from_millis(200), label: label.to_string(),
24 started: Instant::now(),
25 render_count: 0,
26 };
27 pb.render(); pb
29 }
30
31 pub fn new_spinner(label: &str) -> Self {
33 let mut pb = Self {
34 total: None,
35 current: 0,
36 last_update: Instant::now(),
37 update_interval: Duration::from_millis(200),
38 label: label.to_string(),
39 started: Instant::now(),
40 render_count: 0,
41 };
42 pb.render(); pb
44 }
45
46 pub fn update(&mut self, current: usize) {
48 self.current = current;
49
50 if self.last_update.elapsed() >= self.update_interval {
51 self.render();
52 self.last_update = Instant::now();
53 }
54 }
55
56 #[allow(dead_code)]
58 pub fn inc(&mut self) {
59 self.update(self.current + 1);
60 }
61
62 pub fn render(&mut self) {
64 self.render_count += 1;
65
66 if let Some(total) = self.total {
67 let percentage = if total > 0 {
68 (self.current as f64 / total as f64 * 100.0).min(100.0)
69 } else {
70 0.0
71 };
72
73 let bar_width = 40;
74 let filled = (bar_width as f64 * percentage / 100.0) as usize;
75 let empty = bar_width - filled;
76
77 let bar = format!("[{}{}]", "=".repeat(filled), " ".repeat(empty));
78
79 eprint!(
80 "\r{} {} {:>6.1}% ({}/{})",
81 self.label,
82 bar,
83 percentage,
84 format_number(self.current),
85 format_number(total)
86 );
87 } else {
88 let spinner = ['|', '/', '-', '\\'];
90 let idx = self.render_count % spinner.len();
91
92 eprint!(
93 "\r{} {} {}",
94 self.label,
95 spinner[idx],
96 format_number(self.current)
97 );
98 }
99
100 let _ = io::stderr().flush();
101 }
102
103 pub fn finish(&mut self) {
105 self.render();
106 let elapsed = self.started.elapsed();
107 let rate = if elapsed.as_secs() > 0 {
108 format!(
109 "{}/s",
110 format_number(self.current / elapsed.as_secs() as usize)
111 )
112 } else {
113 "".to_string()
114 };
115
116 eprintln!(
117 " Done in {:.1}s {}",
118 elapsed.as_secs_f64(),
119 if rate.is_empty() {
120 "".to_string()
121 } else {
122 format!("({})", rate)
123 }
124 );
125 }
126
127 pub fn finish_with_message(&mut self, message: &str) {
129 self.render();
130 eprintln!(" {}", message);
131 }
132}
133
134fn format_number(n: usize) -> String {
136 let s = n.to_string();
137 let mut result = String::new();
138 for (i, c) in s.chars().rev().enumerate() {
139 if i > 0 && i % 3 == 0 {
140 result.push(',');
141 }
142 result.push(c);
143 }
144 result.chars().rev().collect()
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_format_number() {
153 assert_eq!(format_number(0), "0");
154 assert_eq!(format_number(999), "999");
155 assert_eq!(format_number(1000), "1,000");
156 assert_eq!(format_number(1234567), "1,234,567");
157 }
158}