feat(cli): render library progress with indicatif
The CLI owns the terminal now: TermProgress turns the library's Progress events into bars and spinners on stderr (hidden by indicatif when stderr is not a terminal). Named transfers (OCI layers, kernel, tarball) render as byte bars, extraction and initramfs as file-count spinners.
This commit is contained in:
Generated
+1
@@ -329,6 +329,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"ecr",
|
"ecr",
|
||||||
|
"indicatif",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -21,3 +21,6 @@ clap = { version = "4", features = ["derive", "env"] }
|
|||||||
|
|
||||||
# Error handling
|
# Error handling
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
|
||||||
|
# Progress bar rendering of library progress events
|
||||||
|
indicatif = "0.18"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
mod cli;
|
mod cli;
|
||||||
|
mod progress;
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ use ecr::config::Config;
|
|||||||
use ecr::exec::ExecOptions;
|
use ecr::exec::ExecOptions;
|
||||||
use ecr::mount::BindTarget;
|
use ecr::mount::BindTarget;
|
||||||
use ecr::{kernel, qemu, qemu_vm, utils, veprintln, verbose, PrepareRequest, RootfsCache};
|
use ecr::{kernel, qemu, qemu_vm, utils, veprintln, verbose, PrepareRequest, RootfsCache};
|
||||||
|
use progress::TermProgress;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
@@ -18,6 +20,9 @@ fn main() -> Result<()> {
|
|||||||
// Initialise verbosity before anything else so all downstream code can use veprintln!.
|
// Initialise verbosity before anything else so all downstream code can use veprintln!.
|
||||||
verbose::set(args.verbose);
|
verbose::set(args.verbose);
|
||||||
|
|
||||||
|
// Render library progress (bars/spinners) on the terminal
|
||||||
|
let progress = TermProgress::new();
|
||||||
|
|
||||||
// Load config file
|
// Load config file
|
||||||
let config = Config::load()?;
|
let config = Config::load()?;
|
||||||
|
|
||||||
@@ -38,7 +43,7 @@ fn main() -> Result<()> {
|
|||||||
arch: args.arch.clone().unwrap_or_default(),
|
arch: args.arch.clone().unwrap_or_default(),
|
||||||
no_cache: args.no_cache,
|
no_cache: args.no_cache,
|
||||||
};
|
};
|
||||||
let rootfs = cache.prepare(&request, &ecr::NoProgress)?;
|
let rootfs = cache.prepare(&request, &progress)?;
|
||||||
|
|
||||||
// Branch based on --kernel flag
|
// Branch based on --kernel flag
|
||||||
// Option<Option<PathBuf>> (require_equals: the value must use =PATH syntax
|
// Option<Option<PathBuf>> (require_equals: the value must use =PATH syntax
|
||||||
@@ -63,7 +68,7 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
veprintln!("QEMU mode: downloading default kernel...");
|
veprintln!("QEMU mode: downloading default kernel...");
|
||||||
kernel::get_default_kernel(cache.dir(), &arch, &ecr::NoProgress)?
|
kernel::get_default_kernel(cache.dir(), &arch, &progress)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -81,7 +86,7 @@ fn main() -> Result<()> {
|
|||||||
arch: arch.clone(),
|
arch: arch.clone(),
|
||||||
command,
|
command,
|
||||||
},
|
},
|
||||||
&ecr::NoProgress,
|
&progress,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Cleanup happens automatically via tempfile
|
// Cleanup happens automatically via tempfile
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
//! 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 ecr::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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user