From 5ab0521e61e445b04eea5f66249e0f67042dd576 Mon Sep 17 00:00:00 2001 From: Redzic Date: Mon, 5 Jul 2021 21:53:38 -0400 Subject: [PATCH] Allow custom template keys in `ProgressStyle` Tweak custom-template example Adjust visibility of `ProgressState` Make `Estimate` struct have pub(crate) visibility again Tweak visibility Rename variable Add doc comment back in --- examples/custom-template.rs | 26 ++++++++++ src/progress_bar.rs | 4 +- src/state.rs | 29 ++++++----- src/style.rs | 99 ++++++++++++++++++++++++++++--------- 4 files changed, 120 insertions(+), 38 deletions(-) create mode 100644 examples/custom-template.rs diff --git a/examples/custom-template.rs b/examples/custom-template.rs new file mode 100644 index 00000000..defa3e41 --- /dev/null +++ b/examples/custom-template.rs @@ -0,0 +1,26 @@ +use std::thread; +use std::time::Duration; + +use indicatif::{ProgressBar, ProgressStyle}; + +fn main() { + const SIZE: u64 = 1 << 12; + + let pb = ProgressBar::new(SIZE); + + pb.set_style( + ProgressStyle::default_bar() + .template("{per_sec_precise} {wide_bar} {percent}% ({pos}/{len})") + .key("per_sec_precise", |s| format!("{:>5.1}/s", s.per_sec())), + ); + + pb.set_position(0); + thread::sleep(Duration::from_secs(1)); + + for i in 1..=SIZE { + pb.inc(1); + thread::sleep(Duration::from_secs_f64(2.0 / ((2 * i) as f64))); + } + + pb.finish(); +} diff --git a/src/progress_bar.rs b/src/progress_bar.rs index 933de01a..fdd8facd 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -187,7 +187,7 @@ impl ProgressBar { pub fn set_draw_rate(&self, n: u64) { let mut state = self.state.lock().unwrap(); state.draw_rate = n; - state.draw_next = state.pos.saturating_add(state.per_sec() / n); + state.draw_next = state.pos.saturating_add(state.per_sec() as u64 / n); } /// Manually ticks the spinner or progress bar @@ -478,7 +478,7 @@ impl ProgressBar { /// Returns the current rate of progress pub fn per_sec(&self) -> u64 { - self.state.lock().unwrap().per_sec() + self.state.lock().unwrap().per_sec() as u64 } /// Returns the current expected duration diff --git a/src/state.rs b/src/state.rs index 01518b43..64480c75 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,10 +8,10 @@ use crate::draw_target::{ProgressDrawState, ProgressDrawTarget}; use crate::style::{ProgressFinish, ProgressStyle}; /// The state of a progress bar at a moment in time. -pub(crate) struct ProgressState { +pub struct ProgressState { pub(crate) style: ProgressStyle, - pub(crate) pos: u64, - pub(crate) len: u64, + pub pos: u64, + pub len: u64, pub(crate) tick: u64, pub(crate) started: Instant, pub(crate) draw_target: ProgressDrawTarget, @@ -82,9 +82,14 @@ impl ProgressState { pct.max(0.0).min(1.0) } - /// Returns the position of the status bar as `(pos, len)` tuple. - pub fn position(&self) -> (u64, u64) { - (self.pos, self.len) + /// Returns the position of the status bar. + pub fn position(&self) -> u64 { + self.pos + } + + /// Returns the length of the status bar. + pub fn length(&self) -> u64 { + self.len } /// Returns the current message of the progress bar. @@ -120,12 +125,12 @@ impl ProgressState { } /// The number of steps per second - pub fn per_sec(&self) -> u64 { - let avg_time = self.est.seconds_per_step(); - if avg_time == 0.0 { - 0 + pub fn per_sec(&self) -> f64 { + let per_sec = 1.0 / self.est.seconds_per_step(); + if per_sec.is_nan() { + 0.0 } else { - (1.0 / avg_time) as u64 + per_sec } } @@ -157,7 +162,7 @@ impl ProgressState { } if new_pos >= self.draw_next { self.draw_next = new_pos.saturating_add(if self.draw_rate != 0 { - self.per_sec() / self.draw_rate + self.per_sec() as u64 / self.draw_rate } else { self.draw_delta }); diff --git a/src/style.rs b/src/style.rs index e3db4983..720f566e 100644 --- a/src/style.rs +++ b/src/style.rs @@ -4,12 +4,73 @@ use crate::format::{BinaryBytes, DecimalBytes, FormattedDuration, HumanBytes, Hu use crate::state::ProgressState; use crate::utils::{expand_template, pad_str}; use std::borrow::Cow; +use std::collections::HashMap; #[cfg(feature = "improved_unicode")] use unicode_segmentation::UnicodeSegmentation; +pub type Format = fn(&ProgressState) -> String; +type FormatMapInner = HashMap<&'static str, Format>; + +#[derive(Clone)] +pub(crate) struct FormatMap(FormatMapInner); + +impl Default for FormatMap { + fn default() -> Self { + let mut map: FormatMapInner = HashMap::with_capacity(32); + + map.insert("spinner", |state| state.current_tick_str().to_string()); + map.insert("msg", |state| state.message().to_string()); + map.insert("prefix", |state| state.prefix().to_string()); + map.insert("pos", |state| state.position().to_string()); + map.insert("len", |state| state.length().to_string()); + map.insert("percent", |state| { + format!("{:.*}", 0, state.fraction() * 100f32) + }); + map.insert("bytes", |state| format!("{}", HumanBytes(state.pos))); + map.insert("total_bytes", |state| format!("{}", HumanBytes(state.len))); + map.insert("decimal_bytes", |state| { + format!("{}", DecimalBytes(state.pos)) + }); + map.insert("decimal_total_bytes", |state| { + format!("{}", DecimalBytes(state.len)) + }); + map.insert("binary_bytes", |state| { + format!("{}", BinaryBytes(state.pos)) + }); + map.insert("binary_total_bytes", |state| { + format!("{}", BinaryBytes(state.len)) + }); + map.insert("elapsed_precise", |state| { + format!("{}", FormattedDuration(state.started.elapsed())) + }); + map.insert("elapsed", |state| { + format!("{:#}", HumanDuration(state.started.elapsed())) + }); + map.insert("per_sec", |state| format!("{:.0}/s", state.per_sec())); + map.insert("bytes_per_sec", |state| { + format!("{}/s", HumanBytes(state.per_sec() as u64)) + }); + map.insert("binary_bytes_per_sec", |state| { + format!("{}/s", BinaryBytes(state.per_sec() as u64)) + }); + map.insert("eta_precise", |state| { + format!("{}", FormattedDuration(state.eta())) + }); + map.insert("eta", |state| format!("{:#}", HumanDuration(state.eta()))); + map.insert("duration_precise", |state| { + format!("{}", FormattedDuration(state.duration())) + }); + map.insert("duration", |state| { + format!("{:#}", HumanDuration(state.duration())) + }); + + FormatMap(map) + } +} + /// Controls the rendering style of progress bars -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct ProgressStyle { tick_strings: Vec>, progress_chars: Vec>, @@ -17,6 +78,7 @@ pub struct ProgressStyle { on_finish: ProgressFinish, // how unicode-big each char in progress_chars is char_width: usize, + format_map: FormatMap, } #[cfg(feature = "improved_unicode")] @@ -70,6 +132,7 @@ impl ProgressStyle { char_width, template: "{wide_bar} {pos}/{len}".into(), on_finish: ProgressFinish::default(), + format_map: FormatMap::default(), } } @@ -86,6 +149,7 @@ impl ProgressStyle { char_width, template: "{spinner} {msg}".into(), on_finish: ProgressFinish::default(), + format_map: FormatMap::default(), } } @@ -129,6 +193,12 @@ impl ProgressStyle { self } + /// Adds a custom key parser that references a `&ProgressState` to the template. + pub fn key(mut self, key: &'static str, f: Format) -> ProgressStyle { + self.format_map.0.insert(key, f); + self + } + /// Sets the template string for the progress bar /// /// Review the [list of template keys](./index.html#templates) for more information. @@ -226,7 +296,6 @@ impl ProgressStyle { } pub(crate) fn format_state(&self, state: &ProgressState) -> Vec { - let (pos, len) = state.position(); let mut rv = vec![]; for line in self.template.lines() { @@ -242,32 +311,14 @@ impl ProgressStyle { var.width.unwrap_or(20), var.alt_style.as_ref(), ), - "spinner" => state.current_tick_str().to_string(), "wide_msg" => { wide_element = Some(var.duplicate_for_key("msg")); "\x00".into() } - "msg" => state.message().to_string(), - "prefix" => state.prefix().to_string(), - "pos" => pos.to_string(), - "len" => len.to_string(), - "percent" => format!("{:.*}", 0, state.fraction() * 100f32), - "bytes" => format!("{}", HumanBytes(state.pos)), - "total_bytes" => format!("{}", HumanBytes(state.len)), - "decimal_bytes" => format!("{}", DecimalBytes(state.pos)), - "decimal_total_bytes" => format!("{}", DecimalBytes(state.len)), - "binary_bytes" => format!("{}", BinaryBytes(state.pos)), - "binary_total_bytes" => format!("{}", BinaryBytes(state.len)), - "elapsed_precise" => format!("{}", FormattedDuration(state.started.elapsed())), - "elapsed" => format!("{:#}", HumanDuration(state.started.elapsed())), - "per_sec" => format!("{}/s", state.per_sec()), - "bytes_per_sec" => format!("{}/s", HumanBytes(state.per_sec())), - "binary_bytes_per_sec" => format!("{}/s", BinaryBytes(state.per_sec())), - "eta_precise" => format!("{}", FormattedDuration(state.eta())), - "eta" => format!("{:#}", HumanDuration(state.eta())), - "duration_precise" => format!("{}", FormattedDuration(state.duration())), - "duration" => format!("{:#}", HumanDuration(state.duration())), - _ => "".into(), + key => match self.format_map.0.get(key) { + Some(func) => func(state), + None => "".into(), + }, }); rv.push(if let Some(ref var) = wide_element {