starry 2.1.0

Current stars history tells only half the story
Documentation
use {
    std::{
        io::{
            self,
            Write,
        },
        time::Instant,
    },
    termimad::{
        crossterm::{
            cursor,
            queue,
            style::{
                style,
                Color,
                Print,
                PrintStyledContent,
                Stylize,
            },
            terminal::{
                Clear,
                ClearType,
            },
        },
        ProgressBar,
    },
};

pub fn print_progress(
    label: &str,
    done: usize,
    total: usize,
    post_label: Option<String>,
) -> anyhow::Result<()> {
    let width = 12;
    let total = total
        .max(done) // avoid overflowing the bar
        .max(1); // avoid division by zero
    let p = ProgressBar::new(done as f32 / (total as f32), width);
    let s = format!("{:width$}", p, width = width);
    let mut stderr = io::stderr();
    queue!(stderr, cursor::SavePosition)?;
    queue!(stderr, Clear(ClearType::CurrentLine))?;
    queue!(stderr, Print(format!("{:>20} ", label)))?;
    queue!(
        stderr,
        PrintStyledContent(style(s).with(Color::Yellow).on(Color::DarkBlue))
    )?;
    if let Some(post_label) = post_label {
        queue!(stderr, Print(format!(" {}", post_label)))?;
    }
    queue!(stderr, cursor::RestorePosition)?;
    stderr.flush()?;
    Ok(())
}

#[allow(dead_code)]
pub fn clear_progress() -> anyhow::Result<()> {
    let mut stderr = io::stderr();
    queue!(stderr, cursor::SavePosition)?;
    queue!(stderr, Clear(ClearType::CurrentLine))?;
    queue!(stderr, cursor::RestorePosition)?;
    stderr.flush()?;
    Ok(())
}

#[derive(Clone)]
pub struct Task {
    pub label: String,
    pub show: bool,
    pub done: usize,
    pub total: usize,
    pub start: Instant,
}
impl Task {
    pub fn new<S: Into<String>>(label: S) -> Self {
        let mut task = Self {
            label: label.into(),
            show: true,
            done: 0,
            total: 1,
            start: Instant::now(),
        };
        task.update(0, 1);
        task
    }
    pub fn with_total(
        mut self,
        total: usize,
    ) -> Self {
        self.total = total;
        self
    }
    pub fn update(
        &mut self,
        done: usize,
        total: usize,
    ) {
        if self.show {
            self.done = done;
            self.total = total;
            let _ = print_progress(&self.label, done, total, None);
        }
    }
    pub fn increment(&mut self) {
        if self.show {
            self.done += 1;
            let _ = print_progress(&self.label, self.done, self.total, None);
        }
    }
    pub fn update_with_label<S: Into<String>>(
        &self,
        done: usize,
        total: usize,
        post_label: S,
    ) {
        if self.show {
            let _ = print_progress(&self.label, done, total, Some(post_label.into()));
        }
    }
    pub fn finish<S: Into<String>>(
        &self,
        post_label: S,
    ) {
        if self.show {
            let post_label = format!(
                "{} in {:.1}s",
                post_label.into(),
                self.start.elapsed().as_secs_f32(),
            );
            let _ = print_progress(&self.label, 1, 1, Some(post_label));
            eprintln!();
        }
    }
}