use crate::bar::progress::ProgressBar;
use crate::style::style::ProgressStyle;
use std::fmt;
use std::time::Duration;
#[derive(Clone)]
pub struct Spinner {
bar: ProgressBar,
interval: Duration,
}
impl Spinner {
pub fn new(frames: &'static [&'static str]) -> Self {
Self::new_with_interval(frames, Duration::from_millis(80))
}
pub fn new_with_interval(frames: &'static [&'static str], interval: Duration) -> Self {
let style = ProgressStyle::default_spinner().tick_strings(frames);
let bar = ProgressBar::builder().style(style).build();
Self { bar, interval }
}
pub fn with_message(frames: &'static [&'static str], message: impl Into<String>) -> Self {
let spinner = Self::new(frames);
spinner.set_message(message);
spinner
}
pub fn start(&self) {
self.bar.enable_steady_tick(self.interval);
}
pub fn start_with_message(&self, msg: impl Into<String>) {
self.set_message(msg);
self.start();
}
pub fn stop(&self) {
self.bar.disable_steady_tick();
self.bar.finish();
}
pub fn stop_with_message(&self, msg: impl Into<String>) {
self.bar.disable_steady_tick();
self.bar.finish_with_message(msg);
}
pub fn stop_with_symbol(&self, symbol: &str, msg: impl Into<String>) {
self.bar.disable_steady_tick();
let style = ProgressStyle::default_spinner().tick_strings(&[symbol]);
self.bar.set_style(style);
self.bar.finish_with_message(msg);
}
pub fn success(&self, msg: impl Into<String>) {
self.stop_with_symbol("ok", msg);
}
pub fn failure(&self, msg: impl Into<String>) {
self.stop_with_symbol("x", msg);
}
pub fn warning(&self, msg: impl Into<String>) {
self.stop_with_symbol("!", msg);
}
pub fn info(&self, msg: impl Into<String>) {
self.stop_with_symbol("i", msg);
}
pub fn stop_and_persist(&self, symbol: &str, msg: impl Into<String>) {
self.stop_with_symbol(symbol, msg);
}
pub fn set_message(&self, msg: impl Into<String>) {
self.bar.set_message(msg);
}
pub fn set_prefix(&self, prefix: impl Into<String>) {
self.bar.set_prefix(prefix);
}
pub fn set_style(&self, style: ProgressStyle) {
self.bar.set_style(style);
}
pub fn interval(&self) -> Duration {
self.interval
}
pub fn tick(&self) {
self.bar.tick();
}
pub fn is_finished(&self) -> bool {
self.bar.is_finished()
}
pub fn bar(&self) -> &ProgressBar {
&self.bar
}
}
impl fmt::Debug for Spinner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Spinner")
.field("bar", &self.bar)
.field("interval", &self.interval)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::terminal::writer::DrawTarget;
fn hidden_spinner() -> Spinner {
let bar = ProgressBar::builder()
.target(DrawTarget::hidden())
.style(ProgressStyle::default_spinner())
.build();
Spinner {
bar,
interval: Duration::from_millis(1),
}
}
#[test]
fn test_spinner_starts_and_stops() {
let spinner = hidden_spinner();
spinner.start();
spinner.stop();
assert!(spinner.is_finished());
}
#[test]
fn test_spinner_message_update() {
let spinner = hidden_spinner();
spinner.set_message("hello");
assert_eq!(spinner.bar().message(), "hello");
}
#[test]
fn test_spinner_stop_with_symbol() {
let spinner = hidden_spinner();
spinner.stop_with_symbol("ok", "done");
assert_eq!(spinner.bar().message(), "done");
}
#[test]
fn test_spinner_status_helpers_finish() {
let success = hidden_spinner();
success.success("done");
assert!(success.is_finished());
let failure = hidden_spinner();
failure.failure("failed");
assert!(failure.is_finished());
let warning = hidden_spinner();
warning.warning("skipped");
assert!(warning.is_finished());
let info = hidden_spinner();
info.info("noted");
assert!(info.is_finished());
}
#[test]
fn test_spinner_interval_and_set_style() {
let spinner = hidden_spinner();
spinner.set_style(ProgressStyle::default_spinner().tick_strings(&["a", "b"]));
assert_eq!(spinner.interval(), Duration::from_millis(1));
}
#[test]
fn test_spinner_is_finished_after_stop() {
let spinner = hidden_spinner();
spinner.stop();
assert!(spinner.is_finished());
}
}