From 88f403b914a63adedd4720cc5487813897af9c38 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 28 Oct 2021 13:57:20 +0200 Subject: [PATCH] Replace regex with hand-written parser --- Cargo.toml | 3 +- src/style.rs | 567 +++++++++++++++++++++++++++++++-------------------- 2 files changed, 342 insertions(+), 228 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a03c4d24..a7aea7c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,6 @@ edition = "2018" exclude = ["screenshots/*"] [dependencies] -regex = { version = "1.3.1", default-features = false, features = ["std"] } -once_cell = "1.8.0" number_prefix = "0.4" console = { version = "0.15.0", default-features = false, features = ["ansi-parsing"] } unicode-segmentation = { version = "1.6.0", optional = true } @@ -23,6 +21,7 @@ rayon = { version = "1.0", optional = true } tokio = { version = "1.0", optional = true, features = ["fs", "io-util"] } [dev-dependencies] +once_cell = "1.8.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 6442854c..f9358319 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,10 +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 once_cell::sync::Lazy; -use regex::{Captures, Regex}; #[cfg(feature = "improved_unicode")] use unicode_segmentation::UnicodeSegmentation; @@ -16,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, @@ -71,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 { @@ -81,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(), } @@ -137,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 } @@ -227,215 +226,356 @@ impl ProgressStyle { } pub(crate) fn format_state(&self, state: &ProgressState) -> Vec { - static VAR_RE: Lazy = Lazy::new(|| Regex::new(r"(\}\})|\{(\{|[^{}}]+\})").unwrap()); - static KEY_RE: Lazy = Lazy::new(|| { - 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('\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('\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