diff --git a/Cargo.toml b/Cargo.toml index 5d2e1579..7f99de58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,6 @@ edition = "2018" exclude = ["screenshots/*"] [dependencies] -regex = { version = "1.3.1", default-features = false, features = ["std"] } -lazy_static = "1.0" number_prefix = "0.4" console = { version = "0.15.0", default-features = false, features = ["ansi-parsing"] } unicode-segmentation = { version = "1.6.0", optional = true } @@ -22,6 +20,7 @@ rayon = { version = "1.0", optional = true } tokio = { version = "1.0", optional = true, features = ["fs", "io-util"] } [dev-dependencies] +lazy_static = "1.0" rand = "0.8" structopt = "0.3" tokio = { version = "1.0", features = ["time", "rt"] } diff --git a/src/style.rs b/src/style.rs index cb97df7f..120c1950 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,9 +1,9 @@ use std::borrow::Cow; use std::collections::HashMap; use std::fmt::{self, Write}; +use std::mem; use console::{measure_text_width, Style}; -use regex::{Captures, Regex}; #[cfg(feature = "improved_unicode")] use unicode_segmentation::UnicodeSegmentation; @@ -15,7 +15,7 @@ use crate::state::ProgressState; pub struct ProgressStyle { tick_strings: Vec>, progress_chars: Vec>, - template: Box, + template: Template, on_finish: ProgressFinish, // how unicode-big each char in progress_chars is char_width: usize, @@ -70,7 +70,7 @@ impl ProgressStyle { Self::new("{spinner} {msg}") } - fn new(template: impl Into>) -> Self { + fn new(template: &str) -> Self { let progress_chars = segment("█░"); let char_width = width(&progress_chars); ProgressStyle { @@ -80,7 +80,7 @@ impl ProgressStyle { .collect(), progress_chars, char_width, - template: template.into(), + template: Template::from_str(template), on_finish: ProgressFinish::default(), format_map: FormatMap::default(), } @@ -136,7 +136,7 @@ impl ProgressStyle { /// /// Review the [list of template keys](./index.html#templates) for more information. pub fn template(mut self, s: &str) -> ProgressStyle { - self.template = s.into(); + self.template = Template::from_str(s); self } @@ -226,215 +226,356 @@ impl ProgressStyle { } pub(crate) fn format_state(&self, state: &ProgressState) -> Vec { - lazy_static::lazy_static! { - static ref VAR_RE: Regex = Regex::new(r"(\}\})|\{(\{|[^{}}]+\})").unwrap(); - static ref KEY_RE: Regex = Regex::new( - r"(?x) - ([^:]+) - (?: - : - ([<^>])? - ([0-9]+)? - (!)? - (?:\.([0-9a-z_]+(?:\.[0-9a-z_]+)*))? - (?:/([a-z_]+(?:\.[a-z_]+)*))? - )? - " - ) - .unwrap(); - } - + let mut cur = String::new(); let mut buf = String::new(); let mut rv = vec![]; - for line in self.template.lines() { - let mut wide_element = None; - let s = VAR_RE.replace_all(line, |caps: &Captures<'_>| { - if caps.get(1).is_some() { - return "}".into(); - } - - let key = &caps[2]; - if key == "{" { - return "{".into(); - } - - let mut var = TemplateVar { + let mut wide_element = None; + for part in &self.template.parts { + match part { + TemplatePart::Placeholder { key, - align: Alignment::Left, - truncate: false, - width: None, - style: None, - alt_style: None, - last_element: caps.get(0).unwrap().end() >= line.len(), - }; - - if let Some(opt_caps) = KEY_RE.captures(&key[..key.len() - 1]) { - if let Some(short_key) = opt_caps.get(1) { - var.key = short_key.as_str(); - } - var.align = match opt_caps.get(2).map(|x| x.as_str()) { - Some("<") => Alignment::Left, - Some("^") => Alignment::Center, - Some(">") => Alignment::Right, - _ => Alignment::Left, + align, + width, + truncate, + style, + alt_style, + } => { + buf.clear(); + if let Some(formatter) = self.format_map.0.get(key.as_str()) { + buf.push_str(&formatter(state)); + } else { + match key.as_str() { + "wide_bar" => { + wide_element = Some((key.as_str(), align, alt_style)); + buf.push_str("\x00"); + } + "bar" => buf + .write_fmt(format_args!( + "{}", + self.format_bar( + state.fraction(), + width.unwrap_or(20) as usize, + alt_style.as_ref(), + ) + )) + .unwrap(), + "spinner" => buf.push_str(state.current_tick_str()), + "wide_msg" => { + wide_element = Some((key.as_str(), align, alt_style)); + buf.push_str("\x00"); + } + "msg" => buf.push_str(state.message()), + "prefix" => buf.push_str(state.prefix()), + "pos" => buf.write_fmt(format_args!("{}", state.pos)).unwrap(), + "len" => buf.write_fmt(format_args!("{}", state.len)).unwrap(), + "percent" => buf + .write_fmt(format_args!("{:.*}", 0, state.fraction() * 100f32)) + .unwrap(), + "bytes" => buf + .write_fmt(format_args!("{}", HumanBytes(state.pos))) + .unwrap(), + "total_bytes" => buf + .write_fmt(format_args!("{}", HumanBytes(state.len))) + .unwrap(), + "decimal_bytes" => buf + .write_fmt(format_args!("{}", DecimalBytes(state.pos))) + .unwrap(), + "decimal_total_bytes" => buf + .write_fmt(format_args!("{}", DecimalBytes(state.len))) + .unwrap(), + "binary_bytes" => buf + .write_fmt(format_args!("{}", BinaryBytes(state.pos))) + .unwrap(), + "binary_total_bytes" => buf + .write_fmt(format_args!("{}", BinaryBytes(state.len))) + .unwrap(), + "elapsed_precise" => buf + .write_fmt(format_args!( + "{}", + FormattedDuration(state.started.elapsed()) + )) + .unwrap(), + "elapsed" => buf + .write_fmt(format_args!( + "{:#}", + HumanDuration(state.started.elapsed()) + )) + .unwrap(), + "per_sec" => buf + .write_fmt(format_args!("{:.4}/s", state.per_sec())) + .unwrap(), + "bytes_per_sec" => buf + .write_fmt(format_args!("{}/s", HumanBytes(state.per_sec() as u64))) + .unwrap(), + "binary_bytes_per_sec" => buf + .write_fmt(format_args!( + "{}/s", + BinaryBytes(state.per_sec() as u64) + )) + .unwrap(), + "eta_precise" => buf + .write_fmt(format_args!("{}", FormattedDuration(state.eta()))) + .unwrap(), + "eta" => buf + .write_fmt(format_args!("{:#}", HumanDuration(state.eta()))) + .unwrap(), + "duration_precise" => buf + .write_fmt(format_args!("{}", FormattedDuration(state.duration()))) + .unwrap(), + "duration" => buf + .write_fmt(format_args!("{:#}", HumanDuration(state.duration()))) + .unwrap(), + _ => (), + } }; - if let Some(width) = opt_caps.get(3) { - var.width = Some(width.as_str().parse().unwrap()); + + match width { + Some(width) => { + let padded = PaddedStringDisplay { + str: &buf, + width: *width as usize, + align: *align, + truncate: *truncate, + }; + match style { + Some(s) => cur + .write_fmt(format_args!("{}", s.apply_to(padded))) + .unwrap(), + None => cur.write_fmt(format_args!("{}", padded)).unwrap(), + } + } + None => match style { + Some(s) => cur.write_fmt(format_args!("{}", s.apply_to(&buf))).unwrap(), + None => cur.push_str(&buf), + }, } - if opt_caps.get(4).is_some() { - var.truncate = true; + } + TemplatePart::Literal(s) => cur.push_str(s), + TemplatePart::NewLine => { + rv.push(expand_wide( + mem::take(&mut cur), + wide_element.take(), + self, + state, + &mut buf, + )); + } + } + } + + if !cur.is_empty() { + rv.push(expand_wide( + mem::take(&mut cur), + wide_element.take(), + self, + state, + &mut buf, + )); + } + rv + } +} + +fn expand_wide( + cur: String, + wide: Option<(&str, &Alignment, &Option