use std::io::IsTerminal;
use std::sync::OnceLock;
use std::time::Duration;
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
fn candy_frames() -> &'static [String] {
static FRAMES: OnceLock<Vec<String>> = OnceLock::new();
FRAMES.get_or_init(|| {
const WIDTH: usize = 10;
(0..WIDTH)
.map(|i| {
(0..WIDTH)
.map(|j| if j < i { ' ' } else if j == i { 'C' } else { '.' })
.collect()
})
.collect()
})
}
pub struct Spinner {
bar: ProgressBar,
}
impl Spinner {
pub fn start(label: impl Into<String>) -> Self {
let target = if std::io::stderr().is_terminal() {
ProgressDrawTarget::stderr()
} else {
ProgressDrawTarget::hidden()
};
let bar = ProgressBar::with_draw_target(None, target);
let frames: Vec<&str> = candy_frames().iter().map(String::as_str).collect();
bar.set_style(
ProgressStyle::with_template("{spinner:.yellow} {msg}")
.unwrap()
.tick_strings(&frames),
);
bar.set_message(label.into());
bar.enable_steady_tick(Duration::from_millis(80));
Self { bar }
}
pub fn success(self, msg: impl AsRef<str>) {
self.bar.finish_and_clear();
println!("{} {}", crate::color::green("✓"), msg.as_ref());
}
pub fn fail(self, msg: impl AsRef<str>) {
self.bar.finish_and_clear();
println!("{} {}", crate::color::red("✗"), msg.as_ref());
}
}