use indicatif::{ProgressBar, ProgressStyle};
use std::time::Duration;
pub struct Spinner {
pb: ProgressBar,
}
#[derive(Debug, Clone)]
pub struct SpinnerPrinter {
pb: ProgressBar,
}
impl Spinner {
const TICK_RATE: Duration = Duration::from_millis(80);
const TEMPLATE: &'static str = "{spinner:.green} {msg}";
fn new_internal(message: String) -> Self {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::with_template(Self::TEMPLATE)
.unwrap_or_else(|_| ProgressStyle::default_spinner()),
);
pb.set_message(message);
pb.enable_steady_tick(Self::TICK_RATE);
std::thread::sleep(Duration::from_millis(20));
Spinner { pb }
}
pub fn new(message: String) -> Self {
Self::new_internal(message)
}
pub fn new_with_output_below(message: String) -> Self {
Self::new_internal(message)
}
pub fn println<T: AsRef<str>>(&self, message: T) {
self.pb.println(message.as_ref());
}
pub fn printer(&self) -> SpinnerPrinter {
SpinnerPrinter {
pb: self.pb.clone(),
}
}
pub fn suspend<F: FnOnce()>(&self, f: F) {
self.pb.suspend(f);
}
pub fn stop(&self) {
self.pb.finish_and_clear();
}
pub fn stop_with_message(&self, message: &str) {
self.pb.finish_with_message(message.to_string());
}
pub fn update_message(&self, new_message: String) {
self.pb.set_message(new_message);
}
}
impl Drop for Spinner {
fn drop(&mut self) {
if !self.pb.is_finished() {
self.pb.finish_and_clear();
}
}
}
impl SpinnerPrinter {
pub fn println<T: AsRef<str>>(&self, message: T) {
self.pb.println(message.as_ref());
}
pub fn suspend<F: FnOnce()>(&self, f: F) {
self.pb.suspend(f);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_spinner_creation_and_stop() {
let spinner = Spinner::new("Testing".to_string());
thread::sleep(Duration::from_millis(200));
spinner.stop();
}
#[test]
fn test_spinner_with_message() {
let spinner = Spinner::new("Loading".to_string());
thread::sleep(Duration::from_millis(200));
spinner.stop_with_message("✓ Done");
}
}