1 use std::borrow::Cow;
2 use std::fmt;
3 use std::time::{Duration, Instant};
4 
5 use regex::{Captures, Regex};
6 
7 use console::{measure_text_width, Style};
8 
duration_to_secs(d: Duration) -> f649 pub fn duration_to_secs(d: Duration) -> f64 {
10     d.as_secs() as f64 + f64::from(d.subsec_nanos()) / 1_000_000_000f64
11 }
12 
secs_to_duration(s: f64) -> Duration13 pub fn secs_to_duration(s: f64) -> Duration {
14     let secs = s.trunc() as u64;
15     let nanos = (s.fract() * 1_000_000_000f64) as u32;
16     Duration::new(secs, nanos)
17 }
18 
19 /// Ring buffer with constant capacity. Used by `ProgressBar`s to display `{eta}`, `{eta_precise}`,
20 /// and `{*_per_sec}`.
21 pub struct Estimate {
22     buf: Box<[f64; 15]>,
23     /// Lower 4 bits signify the current length, meaning how many values of `buf` are actually
24     /// meaningful (and not just set to 0 by initialization).
25     ///
26     /// The upper 4 bits signify the last used index in the `buf`. The estimate is currently
27     /// implemented as a ring buffer and when recording a new step the oldest value is overwritten.
28     /// Last index is the most recently used position, and as elements are always stored with
29     /// insertion order, `last_index + 1` is the least recently used position and is the first
30     /// to be overwritten.
31     data: u8,
32     start_time: Instant,
33     start_value: u64,
34 }
35 
36 impl Estimate {
len(&self) -> u837     fn len(&self) -> u8 {
38         self.data & 0x0F
39     }
40 
set_len(&mut self, len: u8)41     fn set_len(&mut self, len: u8) {
42         // Sanity check to make sure math is correct as otherwise it could result in unexpected bugs
43         debug_assert!(len < 16);
44         self.data = (self.data & 0xF0) | len;
45     }
46 
last_idx(&self) -> u847     fn last_idx(&self) -> u8 {
48         (self.data & 0xF0) >> 4
49     }
50 
set_last_idx(&mut self, last_idx: u8)51     fn set_last_idx(&mut self, last_idx: u8) {
52         // This will wrap last_idx on overflow (setting to 16 will result in 0); this is fine
53         // because Estimate::buf is 15 elements long and this is a ring buffer, so overwriting
54         // the oldest value is correct
55         self.data = ((last_idx & 0x0F) << 4) | (self.data & 0x0F);
56     }
57 
new() -> Self58     pub fn new() -> Self {
59         let this = Self {
60             buf: Box::new([0.0; 15]),
61             data: 0,
62             start_time: Instant::now(),
63             start_value: 0,
64         };
65         // Make sure not to break anything accidentally as self.data can't handle bufs longer than
66         // 15 elements (not enough space in a u8)
67         debug_assert!(this.buf.len() < 16);
68         this
69     }
70 
reset(&mut self, start_value: u64)71     pub fn reset(&mut self, start_value: u64) {
72         self.start_time = Instant::now();
73         self.start_value = start_value;
74         self.data = 0;
75     }
76 
record_step(&mut self, value: u64)77     pub fn record_step(&mut self, value: u64) {
78         let item = {
79             let divisor = value.saturating_sub(self.start_value) as f64;
80             if divisor == 0.0 {
81                 0.0
82             } else {
83                 duration_to_secs(self.start_time.elapsed()) / divisor
84             }
85         };
86 
87         self.push(item);
88     }
89 
90     /// Adds the `value` into the buffer, overwriting the oldest one if full, or increasing length
91     /// by 1 and appending otherwise.
push(&mut self, value: f64)92     fn push(&mut self, value: f64) {
93         let len = self.len();
94         let last_idx = self.last_idx();
95 
96         if self.buf.len() > usize::from(len) {
97             // Buffer isn't yet full, increase it's size
98             self.set_len(len + 1);
99             self.buf[usize::from(last_idx)] = value;
100         } else {
101             // Buffer is full, overwrite the oldest value
102             let idx = last_idx % len;
103             self.buf[usize::from(idx)] = value;
104         }
105 
106         self.set_last_idx(last_idx + 1);
107     }
108 
time_per_step(&self) -> Duration109     pub fn time_per_step(&self) -> Duration {
110         let len = self.len();
111         secs_to_duration(self.buf[0..usize::from(len)].iter().sum::<f64>() / f64::from(len))
112     }
113 }
114 
115 impl fmt::Debug for Estimate {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result116     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117         f.debug_struct("Estimate")
118             .field("buf", &self.buf)
119             .field("len", &self.len())
120             .field("last_idx", &self.last_idx())
121             .field("start_time", &self.start_time)
122             .field("start_value", &self.start_value)
123             .finish()
124     }
125 }
126 
127 #[derive(PartialEq, Eq, Debug, Copy, Clone)]
128 pub enum Alignment {
129     Left,
130     Center,
131     Right,
132 }
133 
134 #[derive(Debug)]
135 pub struct TemplateVar<'a> {
136     pub key: &'a str,
137     pub align: Alignment,
138     pub truncate: bool,
139     pub width: Option<usize>,
140     pub style: Option<Style>,
141     pub alt_style: Option<Style>,
142     pub last_element: bool,
143 }
144 
145 impl<'a> TemplateVar<'a> {
duplicate_for_key<'b>(&self, key: &'b str) -> TemplateVar<'b>146     pub fn duplicate_for_key<'b>(&self, key: &'b str) -> TemplateVar<'b> {
147         TemplateVar {
148             key,
149             align: self.align,
150             truncate: self.truncate,
151             width: self.width,
152             style: self.style.clone(),
153             alt_style: self.alt_style.clone(),
154             last_element: self.last_element,
155         }
156     }
157 }
158 
expand_template<F: FnMut(&TemplateVar<'_>) -> String>(s: &str, mut f: F) -> Cow<'_, str>159 pub fn expand_template<F: FnMut(&TemplateVar<'_>) -> String>(s: &str, mut f: F) -> Cow<'_, str> {
160     lazy_static::lazy_static! {
161         static ref VAR_RE: Regex = Regex::new(r"(\}\})|\{(\{|[^{}}]+\})").unwrap();
162         static ref KEY_RE: Regex = Regex::new(
163             r"(?x)
164                 ([^:]+)
165                 (?:
166                     :
167                     ([<^>])?
168                     ([0-9]+)?
169                     (!)?
170                     (?:\.([0-9a-z_]+(?:\.[0-9a-z_]+)*))?
171                     (?:/([a-z_]+(?:\.[a-z_]+)*))?
172                 )?
173             "
174         )
175         .unwrap();
176     }
177     VAR_RE.replace_all(s, |caps: &Captures<'_>| {
178         if caps.get(1).is_some() {
179             return "}".into();
180         }
181         let key = &caps[2];
182         if key == "{" {
183             return "{".into();
184         }
185         let mut var = TemplateVar {
186             key,
187             align: Alignment::Left,
188             truncate: false,
189             width: None,
190             style: None,
191             alt_style: None,
192             last_element: caps.get(0).unwrap().end() >= s.len(),
193         };
194         if let Some(opt_caps) = KEY_RE.captures(&key[..key.len() - 1]) {
195             if let Some(short_key) = opt_caps.get(1) {
196                 var.key = short_key.as_str();
197             }
198             var.align = match opt_caps.get(2).map(|x| x.as_str()) {
199                 Some("<") => Alignment::Left,
200                 Some("^") => Alignment::Center,
201                 Some(">") => Alignment::Right,
202                 _ => Alignment::Left,
203             };
204             if let Some(width) = opt_caps.get(3) {
205                 var.width = Some(width.as_str().parse().unwrap());
206             }
207             if opt_caps.get(4).is_some() {
208                 var.truncate = true;
209             }
210             if let Some(style) = opt_caps.get(5) {
211                 var.style = Some(Style::from_dotted_str(style.as_str()));
212             }
213             if let Some(alt_style) = opt_caps.get(6) {
214                 var.alt_style = Some(Style::from_dotted_str(alt_style.as_str()));
215             }
216         }
217         let mut rv = f(&var);
218         if let Some(width) = var.width {
219             rv = pad_str(&rv, width, var.align, var.truncate).to_string()
220         }
221         if let Some(s) = var.style {
222             rv = s.apply_to(rv).to_string();
223         }
224         rv
225     })
226 }
227 
pad_str(s: &str, width: usize, align: Alignment, truncate: bool) -> Cow<'_, str>228 pub fn pad_str(s: &str, width: usize, align: Alignment, truncate: bool) -> Cow<'_, str> {
229     let cols = measure_text_width(s);
230 
231     if cols >= width {
232         return if truncate {
233             Cow::Borrowed(s.get(..width).unwrap_or(s))
234         } else {
235             Cow::Borrowed(s)
236         };
237     }
238 
239     let diff = width.saturating_sub(cols);
240 
241     let (left_pad, right_pad) = match align {
242         Alignment::Left => (0, diff),
243         Alignment::Right => (diff, 0),
244         Alignment::Center => (diff / 2, diff.saturating_sub(diff / 2)),
245     };
246 
247     let mut rv = String::new();
248     for _ in 0..left_pad {
249         rv.push(' ');
250     }
251     rv.push_str(s);
252     for _ in 0..right_pad {
253         rv.push(' ');
254     }
255     Cow::Owned(rv)
256 }
257 
258 #[test]
test_expand_template()259 fn test_expand_template() {
260     let rv = expand_template("{{ {foo} {bar} }}", |var| var.key.to_uppercase());
261     assert_eq!(&rv, "{ FOO BAR }");
262     let rv = expand_template(r#"{ "foo": "{foo}", "bar": {bar} }"#, |var| {
263         var.key.to_uppercase()
264     });
265     assert_eq!(&rv, r#"{ "foo": "FOO", "bar": BAR }"#);
266 }
267 
268 #[test]
test_expand_template_flags()269 fn test_expand_template_flags() {
270     use console::set_colors_enabled;
271     set_colors_enabled(true);
272 
273     let rv = expand_template("{foo:5}", |var| {
274         assert_eq!(var.key, "foo");
275         assert_eq!(var.width, Some(5));
276         "XXX".into()
277     });
278     assert_eq!(&rv, "XXX  ");
279 
280     let rv = expand_template("{foo:.red.on_blue}", |var| {
281         assert_eq!(var.key, "foo");
282         assert_eq!(var.width, None);
283         assert_eq!(var.align, Alignment::Left);
284         assert_eq!(var.style, Some(Style::new().red().on_blue()));
285         "XXX".into()
286     });
287     assert_eq!(&rv, "\u{1b}[31m\u{1b}[44mXXX\u{1b}[0m");
288 
289     let rv = expand_template("{foo:^5.red.on_blue}", |var| {
290         assert_eq!(var.key, "foo");
291         assert_eq!(var.width, Some(5));
292         assert_eq!(var.align, Alignment::Center);
293         assert_eq!(var.style, Some(Style::new().red().on_blue()));
294         "XXX".into()
295     });
296     assert_eq!(&rv, "\u{1b}[31m\u{1b}[44m XXX \u{1b}[0m");
297 
298     let rv = expand_template("{foo:^5.red.on_blue/green.on_cyan}", |var| {
299         assert_eq!(var.key, "foo");
300         assert_eq!(var.width, Some(5));
301         assert_eq!(var.align, Alignment::Center);
302         assert_eq!(var.style, Some(Style::new().red().on_blue()));
303         assert_eq!(var.alt_style, Some(Style::new().green().on_cyan()));
304         "XXX".into()
305     });
306     assert_eq!(&rv, "\u{1b}[31m\u{1b}[44m XXX \u{1b}[0m");
307 }
308 
309 #[test]
test_duration_stuff()310 fn test_duration_stuff() {
311     let duration = Duration::new(42, 100_000_000);
312     let secs = duration_to_secs(duration);
313     assert_eq!(secs_to_duration(secs), duration);
314 }
315