use std::io::{IsTerminal, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
static QUIET: AtomicBool = AtomicBool::new(false);
pub fn set_quiet(quiet: bool) {
QUIET.store(quiet, Ordering::Relaxed);
}
pub fn is_quiet() -> bool {
QUIET.load(Ordering::Relaxed)
}
#[macro_export]
macro_rules! progress {
($($arg:tt)*) => {
if !$crate::output::is_quiet() {
eprintln!($($arg)*);
}
};
}
#[macro_export]
macro_rules! progress_inline {
($($arg:tt)*) => {
if !$crate::output::is_quiet() {
eprint!($($arg)*);
}
};
}
const SPINNER_FRAMES: &[char] = &['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
fn run_spinner_loop(stop: &AtomicBool, msg: &str) {
let mut i = 0usize;
let mut stderr = std::io::stderr();
while !stop.load(Ordering::Relaxed) {
let _ = write!(
stderr,
"\r{} {msg}",
SPINNER_FRAMES[i % SPINNER_FRAMES.len()]
);
let _ = stderr.flush();
i += 1;
std::thread::sleep(std::time::Duration::from_millis(80));
}
let _ = write!(stderr, "\r{}\r", " ".repeat(msg.len() + 3));
let _ = stderr.flush();
}
pub struct Spinner {
stop: Arc<AtomicBool>,
handle: Option<std::thread::JoinHandle<()>>,
}
impl Spinner {
pub fn new(message: &str) -> Self {
let stop = Arc::new(AtomicBool::new(false));
if is_quiet() || !std::io::stderr().is_terminal() {
return Self { stop, handle: None };
}
let stop_clone = stop.clone();
let msg = message.to_string();
let handle = std::thread::spawn(move || run_spinner_loop(&stop_clone, &msg));
Self {
stop,
handle: Some(handle),
}
}
pub fn finish(self) {
drop(self);
}
}
impl Drop for Spinner {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(h) = self.handle.take() {
let _ = h.join();
}
}
}