lunar-lib 0.4.4

Common utilities for lunar applications
Documentation
#[cfg(feature = "smarter_progress_bar")]
use std::fmt::Arguments;
use std::{
    io::{self, Write},
    sync::RwLock,
};

use crate::progress::ProgressRenderer;

fn terminal_width() -> usize {
    #[cfg(feature = "smarter_progress_bar")]
    {
        crossterm::terminal::size().map(|(w, _)| w).unwrap_or(80) as usize
    }

    #[cfg(not(feature = "smarter_progress_bar"))]
    {
        80
    }
}

pub struct CliProgressRenderer {
    bar_width: usize,
    suffix: RwLock<String>,
}

impl CliProgressRenderer {
    pub fn new(bar_width: usize) -> Self {
        Self {
            bar_width,
            suffix: RwLock::new(String::new()),
        }
    }

    fn draw(&self, value: usize, max: usize, suffix: &str) {
        let max = max.max(1);
        let value = value.min(max);

        let value_width = max.to_string().len();
        let prefix = format!("[ {value:>value_width$} / {max} ]");

        let bar_width = self.bar_width.saturating_sub(prefix.len() + 3);

        let progress = (value as f64 / max as f64 * bar_width as f64).round() as usize;

        let has_tip = progress > 0 && value < max;
        let body = progress.saturating_sub(has_tip as usize);

        let truncated_suffix = {
            let suffix_max_width = terminal_width().saturating_sub(self.bar_width + 1);
            &suffix[..suffix.len().min(suffix_max_width)]
        };

        print!(
            "\r{prefix} [{body_str}{tip_str}{none_str}] {truncated_suffix}",
            body_str = "=".repeat(body),
            tip_str = if has_tip { ">" } else { "" },
            none_str = "-".repeat(bar_width.saturating_sub(progress)) // Also print suffix, making sure it can never go past the `terminal_width()`
        );
        let _ = io::stdout().flush();
    }

    /// Prints text above the progress bar. Use this instead of [`println!()`] when a bar is running
    #[cfg(feature = "smarter_progress_bar")]
    pub fn print_above(&self, message: Arguments) {
        use crossterm::{cursor::MoveToColumn, execute, terminal::Clear};

        let mut stdout = io::stdout();

        let _ = execute!(
            stdout,
            Clear(crossterm::terminal::ClearType::CurrentLine),
            MoveToColumn(0)
        );

        let _ = stdout.write_fmt(message);
        let _ = stdout.write("\n".as_bytes());
    }

    /// Sets the suffix of the progress bar for when it next updates
    pub fn set_suffix(&self, suffix: impl AsRef<str>) {
        let mut m_suffix = self.suffix.write().unwrap();
        *m_suffix = suffix.as_ref().to_string()
    }
}

impl ProgressRenderer for CliProgressRenderer {
    fn on_start(&self, value: usize, max: usize) {
        let suffix = self.suffix.read().unwrap();
        self.draw(value, max, &suffix);
    }

    fn on_update(&self, value: usize, max: usize) {
        let suffix = self.suffix.read().unwrap();
        self.draw(value, max, &suffix);
    }

    fn on_finish(&self) {
        println!()
    }

    fn on_notify(&self, msg: &str) {
        #[cfg(feature = "smarter_progress_bar")]
        self.print_above(format_args!("{msg}"));
    }

    fn set_label(&self, msg: &str) {
        self.set_suffix(msg);
    }
}