biov 0.1.12

A uv-style tool manager for bioinformatics: reproducible Docker-backed tools with digest-pinned lockfiles (installs as `bv`)
use std::io::Write;
use std::time::Duration;

use indicatif::{MultiProgress, ProgressBar, ProgressStyle};

use bv_runtime::{PauseGuard, ProgressReporter};

pub struct CliProgressReporter {
    bar: ProgressBar,
    mp: MultiProgress,
}

impl CliProgressReporter {
    fn styled(bar: ProgressBar, mp: MultiProgress) -> Self {
        bar.set_style(
            // 2-space indent matches the surrounding `Pulling` / `Added` status lines.
            ProgressStyle::with_template("  {spinner:.cyan} {msg}")
                .unwrap()
                .tick_strings(&["", "", "", "", "", "", "", "", "", ""]),
        );
        bar.enable_steady_tick(Duration::from_millis(80));
        Self { bar, mp }
    }

    /// Spinner added to a `MultiProgress` (multi-tool path).
    pub fn for_multi(mp: &MultiProgress) -> Self {
        Self::styled(mp.add(ProgressBar::new_spinner()), mp.clone())
    }

    /// Print a status line above the spinner without tearing it. Use this
    /// for top-level "Pulling X" / "Added X" lines so they interleave cleanly
    /// with the running spinner.
    pub fn println(&self, line: &str) {
        let _ = self.mp.println(line);
    }
}

impl ProgressReporter for CliProgressReporter {
    fn update(&self, message: &str, _current: Option<u64>, _total: Option<u64>) {
        if !message.is_empty() {
            self.bar.set_message(message.to_string());
        }
    }

    fn finish(&self, message: &str) {
        self.bar.finish_and_clear();
        if !message.is_empty() {
            eprintln!("  {message}");
        }
    }

    fn pause(&self) -> Box<dyn PauseGuard + '_> {
        // One-shot pause: caller is expected to call `finish()` afterwards.
        // We clear the spinner row and never restore it, which avoids the brief
        // flicker (and stale glyph artifact) you'd otherwise see between
        // "rolling tail done" and "finish_and_clear".
        self.bar.disable_steady_tick();
        self.bar.set_message(String::new());
        eprint!("\r\x1b[2K");
        let _ = std::io::stderr().flush();
        Box::new(SpinnerPauseGuard)
    }
}

struct SpinnerPauseGuard;
impl PauseGuard for SpinnerPauseGuard {}