use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::time::Duration;
#[derive(Debug, Clone)]
pub enum ProgressIndicator {
Spinner(Spinner),
Bar { current: u64, total: u64 },
}
#[derive(Debug, Clone)]
pub struct Spinner {
pb: ProgressBar,
}
impl Spinner {
pub fn new(message: impl Into<String>) -> Self {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"])
.template("{spinner:.blue} {msg}")
.unwrap(),
);
pb.set_message(message.into());
pb.enable_steady_tick(Duration::from_millis(80));
Self { pb }
}
pub fn set_message(&self, message: impl Into<String>) {
self.pb.set_message(message.into());
}
pub fn finish_with_message(&self, message: impl Into<String>) {
self.pb.finish_with_message(message.into());
}
pub fn finish_and_clear(&self) {
self.pb.finish_and_clear();
}
pub fn progress_bar(&self) -> &ProgressBar {
&self.pb
}
}
pub struct ProgressBarBuilder {
total: u64,
message: String,
template: Option<String>,
}
impl ProgressBarBuilder {
pub fn new(total: u64) -> Self {
Self {
total,
message: String::new(),
template: None,
}
}
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = message.into();
self
}
pub fn with_template(mut self, template: impl Into<String>) -> Self {
self.template = Some(template.into());
self
}
pub fn build(self) -> ProgressBar {
let pb = ProgressBar::new(self.total);
let template = self
.template
.unwrap_or_else(|| "{msg} [{bar:40.cyan/blue}] {pos}/{len} ({percent}%)".to_string());
pb.set_style(
ProgressStyle::default_bar()
.template(&template)
.unwrap()
.progress_chars("█▓░"),
);
if !self.message.is_empty() {
pb.set_message(self.message);
}
pb
}
}
pub struct MultiProgressManager {
mp: MultiProgress,
}
impl MultiProgressManager {
pub fn new() -> Self {
Self {
mp: MultiProgress::new(),
}
}
pub fn add_spinner(&self, message: impl Into<String>) -> Spinner {
let pb = self.mp.add(ProgressBar::new_spinner());
pb.set_style(
ProgressStyle::default_spinner()
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"])
.template("{spinner:.blue} {msg}")
.unwrap(),
);
pb.set_message(message.into());
pb.enable_steady_tick(Duration::from_millis(80));
Spinner { pb }
}
pub fn add_progress_bar(&self, total: u64, message: impl Into<String>) -> ProgressBar {
let pb = self.mp.add(ProgressBar::new(total));
pb.set_style(
ProgressStyle::default_bar()
.template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({percent}%)")
.unwrap()
.progress_chars("█▓░"),
);
pb.set_message(message.into());
pb
}
}
impl Default for MultiProgressManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spinner_creation() {
let spinner = Spinner::new("Testing...");
spinner.finish_and_clear();
}
#[test]
fn test_spinner_update_message() {
let spinner = Spinner::new("Initial message");
spinner.set_message("Updated message");
spinner.finish_with_message("Done!");
}
#[test]
fn test_progress_bar_builder() {
let pb = ProgressBarBuilder::new(100)
.with_message("Processing")
.build();
pb.inc(50);
assert_eq!(pb.position(), 50);
pb.finish_and_clear();
}
#[test]
fn test_multi_progress() {
let manager = MultiProgressManager::new();
let spinner = manager.add_spinner("Task 1");
let pb = manager.add_progress_bar(100, "Task 2");
spinner.finish_and_clear();
pb.finish_and_clear();
}
}