mirror of
https://github.com/vhaudiquet/ecrou.git
synced 2026-11-12 22:22:44 +00:00
A run blocked on the cache-entry flock was silent: the wait went to the log sink, which renders nowhere by default, so the CLI read as a hang. The Progress trait gains message() - a status line outside the counted-operation protocol, defaulting to a no-op so existing sinks keep compiling - and the flock wait reports what it is waiting for. The CLI renders messages on the active bar and spins one up when no operation is active.
112 lines
3.3 KiB
Rust
112 lines
3.3 KiB
Rust
//! Terminal rendering of library progress events (indicatif-backed).
|
|
//!
|
|
//! This is CLI-owned UI: the library only emits [`Progress`] events,
|
|
//! and everything drawn on the terminal lives here. indicatif hides
|
|
//! all output when stderr is not a terminal.
|
|
|
|
use ecrou::progress::{Progress, Unit};
|
|
use indicatif::{ProgressBar, ProgressStyle};
|
|
use std::sync::Mutex;
|
|
|
|
/// Renders sequential library operations as bars and spinners on stderr.
|
|
pub struct TermProgress {
|
|
bar: Mutex<Option<ProgressBar>>,
|
|
}
|
|
|
|
impl TermProgress {
|
|
pub fn new() -> Self {
|
|
TermProgress {
|
|
bar: Mutex::new(None),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for TermProgress {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl Drop for TermProgress {
|
|
fn drop(&mut self) {
|
|
self.finish();
|
|
}
|
|
}
|
|
|
|
impl Progress for TermProgress {
|
|
fn begin(&self, label: &str, unit: Unit, total: Option<u64>) {
|
|
let mut slot = self.bar.lock().unwrap();
|
|
// The library's `begin` implicitly ends the previous operation.
|
|
if let Some(prev) = slot.take() {
|
|
prev.finish_and_clear();
|
|
}
|
|
|
|
let bar = match (unit, total) {
|
|
(Unit::Bytes, Some(total)) => {
|
|
let pb = ProgressBar::new(total);
|
|
pb.set_style(
|
|
ProgressStyle::default_bar()
|
|
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {msg} {bytes}/{total_bytes} ({eta})")
|
|
.unwrap()
|
|
.progress_chars("#>-"),
|
|
);
|
|
pb.set_message(label.to_string());
|
|
pb
|
|
}
|
|
(Unit::Bytes, None) => {
|
|
let pb = ProgressBar::new_spinner();
|
|
pb.set_style(
|
|
ProgressStyle::default_spinner()
|
|
.template("{spinner:.green} {msg} ({bytes})")
|
|
.unwrap(),
|
|
);
|
|
pb.set_message(label.to_string());
|
|
pb
|
|
}
|
|
(Unit::Files, _) => {
|
|
let pb = ProgressBar::new_spinner();
|
|
pb.set_style(
|
|
ProgressStyle::default_spinner()
|
|
.template("{spinner:.green} {msg} ({pos} files)")
|
|
.unwrap(),
|
|
);
|
|
pb.set_message(label.to_string());
|
|
pb
|
|
}
|
|
};
|
|
*slot = Some(bar);
|
|
}
|
|
|
|
fn advance(&self, n: u64) {
|
|
if let Some(bar) = &*self.bar.lock().unwrap() {
|
|
bar.inc(n);
|
|
}
|
|
}
|
|
|
|
fn finish(&self) {
|
|
if let Some(bar) = self.bar.lock().unwrap().take() {
|
|
bar.finish_and_clear();
|
|
}
|
|
}
|
|
|
|
fn message(&self, text: &str) {
|
|
let mut slot = self.bar.lock().unwrap();
|
|
if slot.is_none() {
|
|
// No operation is active: the message becomes a spinner
|
|
// until the next `begin` replaces it.
|
|
let pb = ProgressBar::new_spinner();
|
|
pb.set_style(
|
|
ProgressStyle::default_spinner()
|
|
.template("{spinner:.green} {msg}")
|
|
.unwrap(),
|
|
);
|
|
pb.set_message(text.to_string());
|
|
*slot = Some(pb);
|
|
return;
|
|
}
|
|
if let Some(bar) = &*slot {
|
|
bar.set_message(text.to_string());
|
|
}
|
|
}
|
|
}
|