fermah-common 0.1.2

Fermah common utilities and types.
Documentation
use std::time::Duration;

use indicatif::ProgressBar;
use termion::color;
use tracing::Subscriber;
use tracing_subscriber::{layer::Context, registry::LookupSpan, Layer};

const TICK_STRINGS: [&str; 12] = ["π", "", "", "", "", "π", "", "", "", "", "𝜆", "𝛾"];

#[derive(Clone)]
pub enum SpinnerTemplate {
    Default,
    Progress,
}

#[derive(Debug, Clone)]
pub struct Spinner {
    spinner: ProgressBar,
    current_step: u8,
    total_steps: u8,
}

impl Spinner {
    const DEFAULT_TEMPLATE: &'static str =
        "{prefix} {spinner:.magenta} {elapsed:.yellow} {msg:.magenta}";
    const PROGRESS_TEMPLATE: &'static str =
        "{bar:.magenta/blue} {bytes}/{total_bytes} {eta:.yellow}";

    pub fn new(total_steps: u8, message: &str, template: SpinnerTemplate) -> Self {
        let tpl = match template {
            SpinnerTemplate::Default => Self::DEFAULT_TEMPLATE.to_string(),
            SpinnerTemplate::Progress => {
                format!("{} {}", Self::DEFAULT_TEMPLATE, Self::PROGRESS_TEMPLATE)
            }
        };

        let spinner = ProgressBar::new_spinner().with_prefix(format!("[{}/{}]", 1, total_steps));
        spinner.set_style(
            indicatif::ProgressStyle::with_template(tpl.as_str())
                .unwrap()
                .tick_strings(&TICK_STRINGS),
        );
        spinner.set_message(message.to_string());
        spinner.enable_steady_tick(Duration::from_millis(100));

        Spinner {
            spinner,
            current_step: 1,
            total_steps,
        }
    }

    pub fn update_step(&mut self, message: &str) {
        self.current_step += 1;

        self.spinner
            .set_prefix(format!("[{}/{}]", self.current_step, self.total_steps));
        self.spinner.set_message(message.to_string());
    }

    pub fn suspend(&self) {
        self.spinner.disable_steady_tick();
    }

    pub fn resume(&self) {
        self.spinner.enable_steady_tick(Duration::from_millis(100));
    }

    pub fn finish(&self, message: &str, success: bool) {
        match success {
            true => {
                self.spinner.finish_with_message(format!(
                    "{}{}{}",
                    color::Fg(color::Green),
                    message,
                    color::Fg(color::Reset)
                ))
            }
            false => {
                self.spinner.finish_with_message(format!(
                    "{}{}{}",
                    color::Fg(color::Red),
                    message,
                    color::Fg(color::Reset)
                ))
            }
        }
    }

    pub fn inner(&self) -> &ProgressBar {
        &self.spinner
    }
}

pub struct SpinnerLayer<S: Subscriber> {
    inner: tracing_subscriber::fmt::Layer<S>,
    spinner: Spinner,
}

impl<S: Subscriber> SpinnerLayer<S> {
    pub fn new(inner: tracing_subscriber::fmt::Layer<S>, spinner: Spinner) -> Self {
        Self { inner, spinner }
    }
}

impl<S> Layer<S> for SpinnerLayer<S>
where
    S: Subscriber + for<'a> LookupSpan<'a>,
{
    fn on_event(&self, event: &tracing::Event<'_>, context: Context<'_, S>) {
        self.spinner.inner().suspend(|| {
            self.inner.on_event(event, context);
        });
    }
}