1use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
4
5#[derive(Clone)]
7pub struct Progress {
8 current: Arc<AtomicUsize>,
9 total: usize,
10 name: String,
11}
12
13impl Progress {
14 pub fn new(name: String, total: usize) -> Self {
16 Self {
17 current: Arc::new(AtomicUsize::new(0)),
18 total,
19 name,
20 }
21 }
22
23 pub fn increment(&self, amount: usize) {
25 self.current.fetch_add(amount, Ordering::Relaxed);
26 }
27
28 pub fn set(&self, value: usize) {
30 self.current.store(value, Ordering::Relaxed);
31 }
32
33 pub fn current(&self) -> usize {
35 self.current.load(Ordering::Relaxed)
36 }
37
38 pub fn total(&self) -> usize {
40 self.total
41 }
42
43 pub fn percentage(&self) -> f64 {
45 if self.total == 0 {
46 return 100.0;
47 }
48 let current = self.current();
49 (current as f64 / self.total as f64 * 100.0).min(100.0)
50 }
51
52 pub fn name(&self) -> &str {
54 &self.name
55 }
56
57 pub fn print(&self) {
59 eprint!("\r{}: {:.0}% ({}/{})",
60 self.name,
61 self.percentage(),
62 self.current(),
63 self.total
64 );
65 }
66
67 pub fn print_with_message(&self, message: &str) {
69 eprint!("\r{}: {:.0}% - {}",
70 self.name,
71 self.percentage(),
72 message
73 );
74 }
75
76 pub fn finish(&self) {
78 eprintln!("\r{}: Complete! (100%)", self.name);
79 }
80
81 pub fn finish_with_message(&self, message: &str) {
83 eprintln!("\r{}: {}", self.name, message);
84 }
85}
86
87pub type ProgressCallback = Box<dyn Fn(usize, usize) + Send + Sync>;
89
90pub struct ProgressHandler {
92 callback: Option<ProgressCallback>,
93}
94
95impl ProgressHandler {
96 pub fn new() -> Self {
98 Self { callback: None }
99 }
100
101 pub fn with_callback(callback: ProgressCallback) -> Self {
103 Self { callback: Some(callback) }
104 }
105
106 pub fn report(&self, current: usize, total: usize) {
108 if let Some(ref callback) = self.callback {
109 callback(current, total);
110 }
111 }
112
113 pub fn has_callback(&self) -> bool {
115 self.callback.is_some()
116 }
117}
118
119impl Default for ProgressHandler {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125pub fn console_progress_callback(name: String) -> ProgressCallback {
127 Box::new(move |current: usize, total: usize| {
128 let percentage = if total > 0 {
129 (current as f64 / total as f64 * 100.0).min(100.0)
130 } else {
131 100.0
132 };
133 eprint!("\r{name}: {percentage:.0}% ({current}/{total})");
134 })
135}
136
137pub fn silent_progress_callback() -> ProgressCallback {
139 Box::new(|_current: usize, _total: usize| {})
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_progress_creation() {
148 let progress = Progress::new("Test".to_string(), 100);
149 assert_eq!(progress.current(), 0);
150 assert_eq!(progress.total(), 100);
151 assert_eq!(progress.name(), "Test");
152 }
153
154 #[test]
155 fn test_progress_increment() {
156 let progress = Progress::new("Test".to_string(), 100);
157 progress.increment(10);
158 assert_eq!(progress.current(), 10);
159 progress.increment(20);
160 assert_eq!(progress.current(), 30);
161 }
162
163 #[test]
164 fn test_progress_set() {
165 let progress = Progress::new("Test".to_string(), 100);
166 progress.set(50);
167 assert_eq!(progress.current(), 50);
168 }
169
170 #[test]
171 fn test_progress_percentage() {
172 let progress = Progress::new("Test".to_string(), 100);
173 assert_eq!(progress.percentage(), 0.0);
174 progress.set(50);
175 assert_eq!(progress.percentage(), 50.0);
176 progress.set(100);
177 assert_eq!(progress.percentage(), 100.0);
178 progress.set(150); assert_eq!(progress.percentage(), 100.0);
180 }
181
182 #[test]
183 fn test_progress_zero_total() {
184 let progress = Progress::new("Test".to_string(), 0);
185 assert_eq!(progress.percentage(), 100.0);
186 }
187
188 #[test]
189 fn test_progress_handler() {
190 let handler = ProgressHandler::new();
191 assert!(!handler.has_callback());
192 handler.report(10, 100); let callback = silent_progress_callback();
195 let handler_with_cb = ProgressHandler::with_callback(callback);
196 assert!(handler_with_cb.has_callback());
197 handler_with_cb.report(50, 100); }
199}