pub fn spinner_frame(tick: u64) -> char {
static FRAMES: [char; 10] = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
FRAMES[(tick as usize) % FRAMES.len()]
}
pub fn ballot_bar(total: u64, successes: u64, errors: u64) -> String {
if total == 0 {
return String::new();
}
let total = (total as usize).min(10);
let errors = (errors as usize).min(total);
let successes = (successes as usize).min(total - errors);
let pending = total - errors - successes;
let mut s = String::with_capacity(total * 3);
for _ in 0..errors {
s.push('\u{2612}');
} for _ in 0..successes {
s.push('\u{2611}');
} for _ in 0..pending {
s.push('\u{2610}');
} s
}
pub fn braille_bar(pct: f64, width: usize) -> String {
static FILL: [char; 9] = [
'\u{2800}', '\u{2840}', '\u{28C0}', '\u{28C4}', '\u{28E4}', '\u{28E6}', '\u{28F6}', '\u{28F7}', '\u{28FF}', ];
if width == 0 {
return String::new();
}
let bounded = pct.clamp(0.0, 100.0);
let total = (bounded / 100.0 * (width as f64) * 8.0).round() as usize;
let total = total.min(width * 8);
let full = total / 8;
let part = total % 8;
let mut s = String::with_capacity(width * 3);
for _ in 0..full {
s.push(FILL[8]);
}
if full < width {
s.push(FILL[part]);
for _ in (full + 1)..width {
s.push(FILL[0]);
}
}
s
}
pub fn format_eta(remaining_secs: f64) -> String {
if !remaining_secs.is_finite() || remaining_secs < 0.0 {
return "—".to_string();
}
let secs = remaining_secs.round() as u64;
if secs < 60 {
format!("{secs}s")
} else if secs < 3600 {
format!("{}m{:02}s", secs / 60, secs % 60)
} else {
format!("{}h{:02}m", secs / 3600, (secs % 3600) / 60)
}
}
pub fn format_compact_session_elapsed(secs: f64) -> String {
let s = secs.max(0.0);
if s < 60.0 * 60.0 * 100.0 {
if s < 10.0 {
format!("{s:.5}s")
} else if s < 100.0 {
format!("{s:.4}s")
} else if s < 1000.0 {
format!("{s:.3}s")
} else {
let m = s / 60.0;
if m < 10.0 {
format!("{m:.5}m")
} else if m < 100.0 {
format!("{m:.4}m")
} else if m < 1000.0 {
format!("{m:.3}m")
} else {
let h = s / 3600.0;
if h < 10.0 {
format!("{h:.5}h")
} else {
format!("{h:.4}h")
}
}
}
} else {
let h = s / 3600.0;
format!("{h:.3}h")
}
}
pub fn session_elapsed_color(secs: f64, color: bool) -> (&'static str, &'static str) {
if !color {
return ("", "");
}
if secs < 60.0 {
("\x1b[2m", "\x1b[0m") } else if secs < 3600.0 {
("", "") } else {
("\x1b[1m", "\x1b[0m") }
}
pub fn format_rate(rate: f64) -> String {
if rate >= 1_000_000.0 {
format!("{:.1}M/s", rate / 1_000_000.0)
} else if rate >= 1_000.0 {
format!("{:.1}K/s", rate / 1_000.0)
} else {
format!("{:.0}/s", rate)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ballot_bar_empty_total_returns_empty_string() {
assert_eq!(ballot_bar(0, 0, 0), "");
}
#[test]
fn compact_session_elapsed_seconds_band() {
assert_eq!(format_compact_session_elapsed(0.0), "0.00000s");
assert_eq!(format_compact_session_elapsed(1.23456), "1.23456s");
assert_eq!(format_compact_session_elapsed(9.99999), "9.99999s");
assert_eq!(format_compact_session_elapsed(10.0), "10.0000s");
assert_eq!(format_compact_session_elapsed(99.9999), "99.9999s");
assert_eq!(format_compact_session_elapsed(100.0), "100.000s");
assert_eq!(format_compact_session_elapsed(999.999), "999.999s");
}
#[test]
fn compact_session_elapsed_minutes_band() {
assert_eq!(format_compact_session_elapsed(1000.0), "16.6667m");
assert_eq!(format_compact_session_elapsed(3599.0), "59.9833m");
assert_eq!(format_compact_session_elapsed(3600.0), "60.0000m");
assert_eq!(format_compact_session_elapsed(59940.0), "999.000m");
}
#[test]
fn compact_session_elapsed_hours_band() {
assert_eq!(format_compact_session_elapsed(60000.0), "16.6667h");
assert_eq!(format_compact_session_elapsed(360000.0), "100.000h");
}
#[test]
fn compact_session_elapsed_fixed_width_under_a_thousand_hours() {
let samples = [
0.0, 0.5, 1.0, 9.999, 10.0, 99.99, 100.0, 999.999, 1000.0, 5000.0, 59940.0, 60000.0,
360000.0, 3500000.0,
];
for s in samples {
let out = format_compact_session_elapsed(s);
assert_eq!(
out.chars().count(),
8,
"elapsed={s} produced {out:?} (width != 8)"
);
}
}
#[test]
fn session_elapsed_color_buckets() {
assert_eq!(session_elapsed_color(0.5, false), ("", ""));
assert_eq!(session_elapsed_color(3600.0, false), ("", ""));
let (open_sub, _) = session_elapsed_color(0.5, true);
assert_eq!(open_sub, "\x1b[2m");
let (open_mid, _) = session_elapsed_color(120.0, true);
assert_eq!(open_mid, "");
let (open_long, _) = session_elapsed_color(7200.0, true);
assert_eq!(open_long, "\x1b[1m");
}
#[test]
fn ballot_bar_groups_errors_first_then_successes_then_pending() {
assert_eq!(ballot_bar(10, 5, 2), "☒☒☑☑☑☑☑☐☐☐");
}
#[test]
fn ballot_bar_all_successful_degenerates_to_check_only_then_pending() {
assert_eq!(ballot_bar(5, 3, 0), "☑☑☑☐☐");
}
#[test]
fn ballot_bar_clamps_oversize_total_to_ten() {
assert_eq!(ballot_bar(15, 0, 0).chars().count(), 10);
}
#[test]
fn ballot_bar_clamps_overflowing_counters() {
assert_eq!(ballot_bar(3, 99, 1), "☒☑☑");
}
}