use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use std::future::Future;
use std::time::Duration;
const SPINNER_SETS: &[&[&str]] = &[
&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
&["◐", "◓", "◑", "◒"],
&["▖", "▘", "▝", "▗"],
&["∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙●∙"],
];
pub struct Loader {
pb: ProgressBar,
label: String,
}
impl Loader {
pub fn start(label: &str) -> Self {
let spinner_frames = select_spinner_set(label);
let pb = ProgressBar::new_spinner();
let style = ProgressStyle::with_template("{spinner:.cyan} {msg}")
.unwrap_or_else(|_| ProgressStyle::default_spinner())
.tick_strings(spinner_frames);
pb.set_style(style);
pb.set_message(format!("{}", label.bright_cyan()));
pb.enable_steady_tick(Duration::from_millis(95));
Self {
pb,
label: label.to_string(),
}
}
pub fn success(&self) {
self.pb
.finish_with_message(format!("{} {}", "OK".bright_green().bold(), self.label));
}
pub fn fail(&self, details: &str) {
self.pb.finish_with_message(format!(
"{} {} {}",
"ERR".bright_red().bold(),
self.label,
format!("({})", details).bright_black()
));
}
}
pub async fn with_loader<T, E, F>(label: &str, op: F) -> Result<T, E>
where
E: std::fmt::Display,
F: Future<Output = Result<T, E>>,
{
let loader = Loader::start(label);
let result = op.await;
match &result {
Ok(_) => loader.success(),
Err(err) => loader.fail(&err.to_string()),
}
result
}
pub fn print_cli_header(command: &str, debug: bool) {
let mode = if debug {
"DEBUG".bright_yellow().bold().to_string()
} else {
"NORMAL".bright_blue().bold().to_string()
};
println!(
"{} {} {} {}",
"xbp".bright_magenta().bold(),
"→".bright_black(),
command.bright_white().bold(),
format!("[{}]", mode).bright_black()
);
}
pub fn section(title: &str) {
println!(
"\n{} {}",
"◆".bright_blue().bold(),
title.bright_blue().bold()
);
}
pub fn divider(width: usize) {
println!("{}", "─".repeat(width).bright_black());
}
pub fn status_line(label: &str, status: &str, ok: bool) {
let icon = if ok {
"✓".bright_green().bold()
} else {
"✗".bright_red().bold()
};
let status = if ok {
status.bright_green().to_string()
} else {
status.bright_red().to_string()
};
println!(" {} {} {}", icon, label.bright_white(), status);
}
pub fn tip(message: &str) {
println!("{} {}", "Hint:".bright_yellow().bold(), message);
}
fn select_spinner_set(label: &str) -> &'static [&'static str] {
let hash = label
.bytes()
.fold(0_u64, |acc, b| acc.wrapping_mul(16777619) ^ u64::from(b));
let idx = (hash as usize) % SPINNER_SETS.len();
SPINNER_SETS[idx]
}