use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
#[derive(Clone)]
pub struct Progress {
current: Arc<AtomicUsize>,
total: usize,
name: String,
}
impl Progress {
pub fn new(name: String, total: usize) -> Self {
Self {
current: Arc::new(AtomicUsize::new(0)),
total,
name,
}
}
pub fn increment(&self, amount: usize) {
self.current.fetch_add(amount, Ordering::Relaxed);
}
pub fn set(&self, value: usize) {
self.current.store(value, Ordering::Relaxed);
}
pub fn current(&self) -> usize {
self.current.load(Ordering::Relaxed)
}
pub fn total(&self) -> usize {
self.total
}
pub fn percentage(&self) -> f64 {
if self.total == 0 {
return 100.0;
}
let current = self.current();
(current as f64 / self.total as f64 * 100.0).min(100.0)
}
pub fn name(&self) -> &str {
&self.name
}
pub fn print(&self) {
eprint!("\r{}: {:.0}% ({}/{})",
self.name,
self.percentage(),
self.current(),
self.total
);
}
pub fn print_with_message(&self, message: &str) {
eprint!("\r{}: {:.0}% - {}",
self.name,
self.percentage(),
message
);
}
pub fn finish(&self) {
eprintln!("\r{}: Complete! (100%)", self.name);
}
pub fn finish_with_message(&self, message: &str) {
eprintln!("\r{}: {}", self.name, message);
}
}
pub type ProgressCallback = Box<dyn Fn(usize, usize) + Send + Sync>;
pub struct ProgressHandler {
callback: Option<ProgressCallback>,
}
impl ProgressHandler {
pub fn new() -> Self {
Self { callback: None }
}
pub fn with_callback(callback: ProgressCallback) -> Self {
Self { callback: Some(callback) }
}
pub fn report(&self, current: usize, total: usize) {
if let Some(ref callback) = self.callback {
callback(current, total);
}
}
pub fn has_callback(&self) -> bool {
self.callback.is_some()
}
}
impl Default for ProgressHandler {
fn default() -> Self {
Self::new()
}
}
pub fn console_progress_callback(name: String) -> ProgressCallback {
Box::new(move |current: usize, total: usize| {
let percentage = if total > 0 {
(current as f64 / total as f64 * 100.0).min(100.0)
} else {
100.0
};
eprint!("\r{name}: {percentage:.0}% ({current}/{total})");
})
}
pub fn silent_progress_callback() -> ProgressCallback {
Box::new(|_current: usize, _total: usize| {})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_creation() {
let progress = Progress::new("Test".to_string(), 100);
assert_eq!(progress.current(), 0);
assert_eq!(progress.total(), 100);
assert_eq!(progress.name(), "Test");
}
#[test]
fn test_progress_increment() {
let progress = Progress::new("Test".to_string(), 100);
progress.increment(10);
assert_eq!(progress.current(), 10);
progress.increment(20);
assert_eq!(progress.current(), 30);
}
#[test]
fn test_progress_set() {
let progress = Progress::new("Test".to_string(), 100);
progress.set(50);
assert_eq!(progress.current(), 50);
}
#[test]
fn test_progress_percentage() {
let progress = Progress::new("Test".to_string(), 100);
assert_eq!(progress.percentage(), 0.0);
progress.set(50);
assert_eq!(progress.percentage(), 50.0);
progress.set(100);
assert_eq!(progress.percentage(), 100.0);
progress.set(150); assert_eq!(progress.percentage(), 100.0);
}
#[test]
fn test_progress_zero_total() {
let progress = Progress::new("Test".to_string(), 0);
assert_eq!(progress.percentage(), 100.0);
}
#[test]
fn test_progress_handler() {
let handler = ProgressHandler::new();
assert!(!handler.has_callback());
handler.report(10, 100);
let callback = silent_progress_callback();
let handler_with_cb = ProgressHandler::with_callback(callback);
assert!(handler_with_cb.has_callback());
handler_with_cb.report(50, 100); }
}