use indicatif::{ProgressBar, ProgressStyle};
use std::env;
use std::future::Future;
use std::io::IsTerminal;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::LazyLock;
use std::time::Duration;
static QUIET: AtomicBool = AtomicBool::new(false);
static VERBOSE: AtomicBool = AtomicBool::new(false);
static STDERR_IS_TERMINAL: LazyLock<bool> = LazyLock::new(|| std::io::stderr().is_terminal());
static USE_COLOR: LazyLock<bool> = LazyLock::new(|| {
if env::var_os("CLICOLOR_FORCE")
.map(|v| v != "0")
.unwrap_or(false)
{
return true;
}
if env::var_os("NO_COLOR").is_some() {
return false;
}
if let Some(val) = env::var_os("CLICOLOR")
&& val == "0"
{
return false;
}
*STDERR_IS_TERMINAL
});
static SPINNER_STYLE: LazyLock<ProgressStyle> = LazyLock::new(|| {
ProgressStyle::default_spinner()
.tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
.template("{spinner:.green} {msg}")
.expect("Valid spinner template")
});
pub struct Ui;
impl Ui {
pub fn set_quiet(quiet: bool) {
QUIET.store(quiet, Ordering::Relaxed);
}
pub fn is_quiet() -> bool {
QUIET.load(Ordering::Relaxed)
}
pub fn set_verbose(verbose: bool) {
VERBOSE.store(verbose, Ordering::Relaxed);
}
pub fn is_verbose() -> bool {
VERBOSE.load(Ordering::Relaxed)
}
pub fn use_color() -> bool {
*USE_COLOR
}
pub fn spinner(message: impl Into<std::borrow::Cow<'static, str>>) -> ProgressBar {
if Self::is_quiet() || !*STDERR_IS_TERMINAL {
return ProgressBar::hidden();
}
let pb = ProgressBar::new_spinner();
pb.set_style(SPINNER_STYLE.clone());
pb.set_message(message);
pb.enable_steady_tick(Duration::from_millis(80));
pb
}
pub fn finish_spinner(pb: &ProgressBar, message: &str) {
pb.finish_and_clear();
Self::success(message);
}
pub async fn with_spinner<T, E, F>(
start_message: &'static str,
done_message: &str,
fut: F,
) -> Result<T, E>
where
F: Future<Output = Result<T, E>>,
{
let pb = Self::spinner(start_message);
match fut.await {
Ok(val) => {
Self::finish_spinner(&pb, done_message);
Ok(val)
}
Err(err) => {
pb.finish_and_clear();
Err(err)
}
}
}
pub fn header(title: &str) {
if Self::is_quiet() {
return;
}
if Self::use_color() {
eprintln!("\n\x1b[1;36m=== {} ===\x1b[0m\n", title);
} else {
eprintln!("\n=== {} ===\n", title);
}
}
pub fn info(message: &str) {
if Self::is_quiet() {
return;
}
if Self::use_color() {
eprintln!("\x1b[1;34mℹ\x1b[0m {}", message);
} else {
eprintln!("ℹ {}", message);
}
}
pub fn success(message: &str) {
if Self::is_quiet() {
return;
}
if Self::use_color() {
eprintln!("\x1b[1;32m✔\x1b[0m {}", message);
} else {
eprintln!("✔ {}", message);
}
}
pub fn warn(message: &str) {
if Self::is_quiet() {
return;
}
if Self::use_color() {
eprintln!("\x1b[1;33m⚠\x1b[0m {}", message);
} else {
eprintln!("⚠ {}", message);
}
}
pub fn error(message: &str) {
if Self::use_color() {
eprintln!("\x1b[1;31m✖\x1b[0m {}", message);
} else {
eprintln!("✖ {}", message);
}
}
pub fn verbose(message: &str) {
if !Self::is_verbose() {
return;
}
if Self::use_color() {
eprintln!("\x1b[1;35m[debug]\x1b[0m {}", message);
} else {
eprintln!("[debug] {}", message);
}
}
}