use indicatif::{ProgressBar, ProgressStyle};
const PROGRESS_BYTES_TEMPLATE: &str =
"{spinner:.green} [{elapsed_precise}] [{bar:.cyan/blue}] {bytes}/{total_bytes} {bytes_per_sec} ({eta})";
const PROGRESS_ITEMS_TEMPLATE: &str =
"{spinner:.green} [{elapsed_precise}] [{bar:.cyan/blue}] {pos}/{len} items ({eta})";
pub struct Progress {
bar: Option<ProgressBar>,
verbose: bool,
}
impl Progress {
fn new_with_template(enabled: bool, total: u64, template: &str, verbose: bool) -> Self {
let bar = if enabled {
let pb = ProgressBar::new(total);
pb.set_style(
ProgressStyle::default_bar()
.template(template)
.expect("invalid progress template")
.progress_chars("█▉▊▋▌▍▎▏ "),
);
Some(pb)
} else {
None
};
Self { bar, verbose }
}
pub fn new(enabled: bool, total_bytes: u64, verbose: bool) -> Self {
Self::new_with_template(enabled, total_bytes, PROGRESS_BYTES_TEMPLATE, verbose)
}
pub fn new_items(enabled: bool, total_items: u64, verbose: bool) -> Self {
Self::new_with_template(enabled, total_items, PROGRESS_ITEMS_TEMPLATE, verbose)
}
pub fn update(&self, processed_bytes: u64) {
if let Some(bar) = &self.bar {
bar.set_position(processed_bytes);
}
}
pub fn set_position(&self, pos: u64) {
if let Some(bar) = &self.bar {
bar.set_position(pos);
}
}
pub fn set_length(&self, len: u64) {
if let Some(bar) = &self.bar {
bar.set_length(len);
}
}
pub fn finish_with_message(&self, msg: &str) {
if let Some(bar) = &self.bar {
bar.finish_with_message(msg.to_string());
}
}
pub fn finish(&self) {
if let Some(bar) = &self.bar {
bar.finish_and_clear();
}
}
pub fn is_verbose(&self) -> bool {
self.verbose
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_disabled() {
let progress = Progress::new(false, 1000, false);
progress.update(500);
progress.finish_with_message("test");
progress.finish();
assert!(progress.bar.is_none());
}
#[test]
fn test_progress_enabled() {
let progress = Progress::new(true, 1000, false);
assert!(progress.bar.is_some());
progress.update(500);
progress.update(750);
progress.finish_with_message("completed");
}
#[test]
fn test_progress_zero_total() {
let progress = Progress::new(true, 0, false);
progress.update(0);
progress.finish();
}
#[test]
fn test_progress_update_beyond_total() {
let progress = Progress::new(true, 100, false);
progress.update(150);
progress.finish();
}
#[test]
fn test_progress_is_verbose() {
let progress_enabled = Progress::new(true, 1000, true);
assert!(progress_enabled.is_verbose());
let progress_disabled = Progress::new(false, 1000, false);
assert!(!progress_disabled.is_verbose());
}
#[test]
fn test_progress_verbose_without_bar() {
let progress = Progress::new(false, 1000, true);
assert!(progress.is_verbose());
assert!(progress.bar.is_none());
}
}