diff --git a/src/progress_bar.rs b/src/progress_bar.rs index 933de01a..d219f4be 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -187,7 +187,9 @@ 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() / n as f64) as u64); } /// Manually ticks the spinner or progress bar @@ -478,7 +480,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..cc547c24 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,11 +82,6 @@ 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 current message of the progress bar. pub fn message(&self) -> &str { &self.message @@ -120,12 +115,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 +152,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() / self.draw_rate as f64) as u64 } else { self.draw_delta }); diff --git a/src/style.rs b/src/style.rs index e3db4983..176e5b98 100644 --- a/src/style.rs +++ b/src/style.rs @@ -4,12 +4,24 @@ 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; + +#[derive(Clone)] +pub(crate) struct FormatMap(HashMap<&'static str, Format>); + +impl Default for FormatMap { + fn default() -> Self { + FormatMap(HashMap::new()) + } +} + /// Controls the rendering style of progress bars -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct ProgressStyle { tick_strings: Vec>, progress_chars: Vec>, @@ -17,6 +29,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 +83,7 @@ impl ProgressStyle { char_width, template: "{wide_bar} {pos}/{len}".into(), on_finish: ProgressFinish::default(), + format_map: FormatMap::default(), } } @@ -86,6 +100,7 @@ impl ProgressStyle { char_width, template: "{spinner} {msg}".into(), on_finish: ProgressFinish::default(), + format_map: FormatMap::default(), } } @@ -129,6 +144,14 @@ impl ProgressStyle { self } + /// Adds a custom key that references a `&ProgressState` to the template + /// + /// Note that custom keys cannot override built-in template keys. + pub fn with_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 +249,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() { @@ -249,8 +271,8 @@ impl ProgressStyle { } "msg" => state.message().to_string(), "prefix" => state.prefix().to_string(), - "pos" => pos.to_string(), - "len" => len.to_string(), + "pos" => state.pos.to_string(), + "len" => state.len.to_string(), "percent" => format!("{:.*}", 0, state.fraction() * 100f32), "bytes" => format!("{}", HumanBytes(state.pos)), "total_bytes" => format!("{}", HumanBytes(state.len)), @@ -261,13 +283,24 @@ impl ProgressStyle { "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())), + "bytes_per_sec" => format!("{}/s", HumanBytes(state.per_sec() as u64)), + "binary_bytes_per_sec" => format!("{}/s", BinaryBytes(state.per_sec() as u64)), "eta_precise" => format!("{}", FormattedDuration(state.eta())), "eta" => format!("{:#}", HumanDuration(state.eta())), "duration_precise" => format!("{}", FormattedDuration(state.duration())), "duration" => format!("{:#}", HumanDuration(state.duration())), - _ => "".into(), + key => { + // Avoid hash table lookup if the format map is empty, + // which is the common case. + if self.format_map.0.is_empty() { + "".into() + } else { + match self.format_map.0.get(key) { + Some(func) => func(state), + None => "".into(), + } + } + } }); rv.push(if let Some(ref var) = wide_element {