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