contributor_graphs/progress.rs
1//! Progress bars for the slow, countable phases — cloning many repositories
2//! and the parallel GitHub enrichment passes — built on `indicatif`. The
3//! styling mirrors Seqera's RustQC (cyan braille spinner, a filled bar, and a
4//! dim elapsed time) so the two tools feel consistent.
5//!
6//! Bars draw to stderr. `indicatif` hides them automatically when stderr is not
7//! a terminal, so piped and CI output stays clean; callers additionally pass
8//! `show` (the verbose flag) to opt out entirely. When hidden, the returned bar
9//! is a no-op, so `inc`/`finish_and_clear` are safe to call unconditionally.
10
11use indicatif::{ProgressBar, ProgressStyle};
12use std::time::Duration;
13
14/// Braille spinner frames, matching RustQC's progress style.
15const TICKS: &[&str] = &["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷", "⣾"];
16
17/// A determinate progress bar over `total` items, rendered as
18/// `⣾ label [████░░░░] 12/57 3s`. Returns a hidden (no-op) bar when `show` is
19/// false or there is nothing to count, so callers don't need to branch.
20pub fn bar(label: &str, total: usize, show: bool) -> ProgressBar {
21 if !show || total == 0 {
22 return ProgressBar::hidden();
23 }
24 let pb = ProgressBar::new(total as u64);
25 pb.set_style(
26 ProgressStyle::with_template(
27 " {spinner:.cyan} {msg} [{bar:24.cyan/dim}] {pos}/{len} {elapsed:.dim}",
28 )
29 .expect("valid progress template")
30 .progress_chars("█▉▊▋▌▍▎▏ ")
31 .tick_strings(TICKS),
32 );
33 pb.set_message(label.to_string());
34 // Animate the spinner even while a single long item (e.g. a big clone) is in
35 // flight and the count isn't moving.
36 pb.enable_steady_tick(Duration::from_millis(100));
37 pb
38}