xbp 10.17.2

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
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]
}