use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle};
use std::io::{IsTerminal, stderr};
use std::time::Duration;
#[must_use]
pub fn should_show_progress() -> bool {
stderr().is_terminal()
}
#[must_use]
pub fn create_progress_bar(total: u64, message: &str, show: bool) -> ProgressBar {
let pb = ProgressBar::new(total);
if show {
let style = ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} {msg}")
.unwrap_or_else(|_| ProgressStyle::default_bar())
.progress_chars("=>-");
pb.set_style(style);
pb.set_message(message.to_string());
} else {
pb.set_draw_target(ProgressDrawTarget::hidden());
}
pb
}
#[must_use]
pub fn create_spinner(message: &str, show: bool) -> ProgressBar {
let pb = ProgressBar::new_spinner();
if show {
let style = ProgressStyle::default_spinner()
.template("{spinner:.green} {msg}")
.unwrap_or_else(|_| ProgressStyle::default_spinner());
pb.set_style(style);
pb.set_message(message.to_string());
pb.enable_steady_tick(Duration::from_millis(100));
} else {
pb.set_draw_target(ProgressDrawTarget::hidden());
}
pb
}
#[must_use]
pub fn create_multi_progress(show: bool) -> MultiProgress {
let multi = MultiProgress::new();
if !show {
multi.set_draw_target(ProgressDrawTarget::hidden());
}
multi
}
pub struct ProgressTracker {
bar: ProgressBar,
showing: bool,
}
impl ProgressTracker {
#[must_use]
pub fn new(total: u64, message: &str) -> Self {
let showing = should_show_progress();
Self {
bar: create_progress_bar(total, message, showing),
showing,
}
}
#[must_use]
pub fn new_spinner(message: &str) -> Self {
let showing = should_show_progress();
Self {
bar: create_spinner(message, showing),
showing,
}
}
pub fn inc(&self, delta: u64) {
self.bar.inc(delta);
}
pub fn set_position(&self, pos: u64) {
self.bar.set_position(pos);
}
pub fn set_message(&self, message: impl Into<String>) {
self.bar.set_message(message.into());
}
pub fn finish_with_message(&self, message: impl Into<String>) {
self.bar.finish_with_message(message.into());
}
pub fn finish_and_clear(&self) {
self.bar.finish_and_clear();
}
#[must_use]
pub const fn is_showing(&self) -> bool {
self.showing
}
#[must_use]
pub const fn bar(&self) -> &ProgressBar {
&self.bar
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_bar_hidden_when_not_terminal() {
let pb = create_progress_bar(100, "Test", false);
assert_eq!(pb.length(), Some(100));
assert_eq!(pb.position(), 0);
pb.inc(50);
assert_eq!(pb.position(), 50);
pb.finish();
assert!(pb.is_finished());
}
#[test]
fn test_spinner_hidden_when_not_terminal() {
let spinner = create_spinner("Testing...", false);
assert_eq!(spinner.length(), None);
assert_eq!(spinner.position(), 0);
spinner.finish();
assert!(spinner.is_finished());
}
#[test]
fn test_progress_tracker_determinate() {
let tracker = ProgressTracker::new(10, "Processing");
assert_eq!(tracker.bar().length(), Some(10));
assert_eq!(tracker.bar().position(), 0);
assert_eq!(tracker.is_showing(), should_show_progress());
for _ in 0..10 {
tracker.inc(1);
}
assert_eq!(tracker.bar().position(), 10);
tracker.finish_with_message("Done");
assert!(tracker.bar().is_finished());
assert_eq!(tracker.bar().message(), "Done");
}
#[test]
fn test_progress_tracker_spinner() {
let tracker = ProgressTracker::new_spinner("Loading...");
assert_eq!(tracker.bar().length(), None);
assert_eq!(tracker.is_showing(), should_show_progress());
let expected_initial_message = if tracker.is_showing() {
"Loading..."
} else {
""
};
assert_eq!(tracker.bar().message(), expected_initial_message);
tracker.set_message("Still loading...");
assert_eq!(tracker.bar().message(), "Still loading...");
tracker.finish_and_clear();
assert!(tracker.bar().is_finished());
}
#[test]
fn test_multi_progress_hidden() {
let multi = create_multi_progress(false);
let pb = multi.add(create_progress_bar(10, "Test", false));
assert_eq!(pb.length(), Some(10));
assert_eq!(pb.position(), 0);
pb.inc(5);
assert_eq!(pb.position(), 5);
pb.finish();
assert!(pb.is_finished());
}
}