use std::{
borrow::Cow,
io::IsTerminal,
};
use indicatif::{
HumanDuration,
MultiProgress,
ProgressBar,
ProgressDrawTarget,
ProgressStyle,
};
use tracing::{
Level,
Span,
};
#[derive(Clone)]
pub struct ProgressReporter {
bar: ProgressBar,
target: ReportMethod,
}
impl Default for ProgressReporter {
fn default() -> Self {
Self::new(ReportMethod::Logs(tracing::info_span!("default")), None)
}
}
#[derive(Clone)]
pub enum ReportMethod {
VisualBar(String),
Logs(Span),
}
impl ProgressReporter {
pub fn new(target: ReportMethod, max: Option<usize>) -> Self {
let max = max.map(|max| u64::try_from(max).unwrap_or(u64::MAX));
let bar = ProgressBar::with_draw_target(max, ProgressDrawTarget::hidden());
if let ReportMethod::VisualBar(message) = &target {
bar.set_message(message.clone());
bar.set_style(Self::style(max.is_some()));
}
ProgressReporter { bar, target }
}
fn style(length_known: bool) -> ProgressStyle {
let template = if length_known {
"[{elapsed_precise}] {bar:.64.on_black} {pos:>7}/{len:7} {msg} {eta}"
} else {
"[{elapsed_precise}] {pos:>7} {msg}"
};
ProgressStyle::with_template(template).expect("hard coded templates to be valid")
}
pub fn set_index(&self, index: usize) {
let display_index = u64::try_from(index).unwrap_or(u64::MAX).saturating_add(1);
self.bar.set_position(display_index);
if let ReportMethod::Logs(span) = &self.target {
span.in_scope(|| match self.bar.length() {
Some(len) => {
let human_eta = HumanDuration(self.bar.eta());
tracing::info!("Processing: {display_index}/{len}. ({human_eta})");
}
_ => {
tracing::info!("Processing: {}", display_index);
}
})
}
}
}
pub struct MultipleProgressReporter {
multi_progress: MultiProgress,
span: Span,
}
impl MultipleProgressReporter {
fn should_display_bars() -> bool {
std::io::stderr().is_terminal() && !cfg!(test)
}
pub fn new(span: Span) -> MultipleProgressReporter {
if Self::should_display_bars() {
Self::new_target(ProgressDrawTarget::stderr(), span)
} else {
Self::new_target(ProgressDrawTarget::hidden(), span)
}
}
pub fn table_reporter(
&self,
num_groups: Option<usize>,
desc: impl Into<Cow<'static, str>>,
) -> ProgressReporter {
let target = if Self::should_display_bars() {
ReportMethod::VisualBar(desc.into().into_owned())
} else {
let span = tracing::span!(
parent: &self.span,
Level::INFO,
"task",
migration = desc.into().as_ref()
);
ReportMethod::Logs(span)
};
self.register(ProgressReporter::new(target, num_groups))
}
fn new_target(target: ProgressDrawTarget, span: Span) -> Self {
Self {
multi_progress: MultiProgress::with_draw_target(target),
span,
}
}
fn register(&self, reporter: ProgressReporter) -> ProgressReporter {
let bar = self.multi_progress.add(reporter.bar);
ProgressReporter {
bar,
target: reporter.target,
}
}
}