pub struct ProgressReporter {
#[allow(clippy::type_complexity)]
callback: Option<Box<dyn Fn(u32, &str)>>,
#[allow(clippy::type_complexity)]
output_callback: Option<Box<dyn Fn(&str)>>,
}
impl ProgressReporter {
pub fn new(callback: impl Fn(u32, &str) + 'static) -> Self {
Self {
callback: Some(Box::new(callback)),
output_callback: None,
}
}
pub fn new_noop() -> Self {
Self {
callback: None,
output_callback: None,
}
}
pub fn with_output(
callback: impl Fn(u32, &str) + 'static,
output_callback: impl Fn(&str) + 'static,
) -> Self {
Self {
callback: Some(Box::new(callback)),
output_callback: Some(Box::new(output_callback)),
}
}
pub fn report(&self, percent: u32, message: &str) {
if let Some(cb) = &self.callback {
cb(percent, message);
}
}
pub fn report_output(&self, line: &str) {
if let Some(cb) = &self.output_callback {
cb(line);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
#[test]
fn test_noop_reporter_doesnt_panic() {
let reporter = ProgressReporter::new_noop();
reporter.report(0, "Starting...");
reporter.report(50, "Halfway there...");
reporter.report(100, "Done!");
}
#[test]
fn test_noop_reporter_callback_is_none() {
let reporter = ProgressReporter::new_noop();
assert!(reporter.callback.is_none());
}
#[test]
fn test_reporter_calls_callback() {
let calls: Arc<Mutex<Vec<(u32, String)>>> = Arc::new(Mutex::new(Vec::new()));
let calls_clone = Arc::clone(&calls);
let reporter = ProgressReporter::new(move |percent, message| {
calls_clone
.lock()
.unwrap()
.push((percent, message.to_string()));
});
reporter.report(0, "Starting...");
reporter.report(50, "Halfway there...");
reporter.report(100, "Done!");
let recorded = calls.lock().unwrap();
assert_eq!(recorded.len(), 3, "Should have recorded 3 calls");
assert_eq!(recorded[0], (0, "Starting...".to_string()));
assert_eq!(recorded[1], (50, "Halfway there...".to_string()));
assert_eq!(recorded[2], (100, "Done!".to_string()));
}
#[test]
fn report_output_calls_callback() {
let received: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let received_clone = Arc::clone(&received);
let reporter = ProgressReporter::with_output(
|_, _| {},
move |line| received_clone.lock().unwrap().push(line.to_string()),
);
reporter.report_output("downloading 50%");
reporter.report_output("downloading 100%");
let lines = received.lock().unwrap();
assert_eq!(lines.len(), 2);
assert_eq!(lines[0], "downloading 50%");
assert_eq!(lines[1], "downloading 100%");
}
#[test]
fn report_output_noop_doesnt_panic() {
let reporter = ProgressReporter::new_noop();
reporter.report_output("ignored");
}
#[test]
fn test_reporter_with_callback_has_some() {
let reporter = ProgressReporter::new(|_percent, _message| {
});
assert!(reporter.callback.is_some());
}
}