Skip to main content

pbr/
pb.rs

1use crate::tty::{terminal_size, Width};
2use std::io::Stdout;
3use std::io::{self, Write};
4use std::time::{Duration, Instant};
5
6macro_rules! kb_fmt {
7    ($n: ident) => {{
8        let kb = 1024f64;
9        match $n {
10            $n if $n >= kb.powf(4_f64) => format!("{:.*} TB", 2, $n / kb.powf(4_f64)),
11            $n if $n >= kb.powf(3_f64) => format!("{:.*} GB", 2, $n / kb.powf(3_f64)),
12            $n if $n >= kb.powf(2_f64) => format!("{:.*} MB", 2, $n / kb.powf(2_f64)),
13            $n if $n >= kb => format!("{:.*} KB", 2, $n / kb),
14            _ => format!("{:.*} B", 0, $n),
15        }
16    }};
17}
18
19const FORMAT: &str = "[=>-]";
20const TICK_FORMAT: &str = "\\|/-";
21
22// Output type format, indicate which format wil be used in
23// the speed box.
24#[derive(Debug)]
25pub enum Units {
26    Default,
27    Bytes,
28}
29
30pub struct ProgressBar<T: Write> {
31    start_time: Instant,
32    units: Units,
33    pub total: u64,
34    current: u64,
35    bar_start: String,
36    bar_current: String,
37    bar_current_n: String,
38    bar_remain: String,
39    bar_end: String,
40    tick: Vec<String>,
41    tick_state: usize,
42    width: Option<usize>,
43    message: String,
44    last_refresh_time: Instant,
45    max_refresh_rate: Option<Duration>,
46    pub is_finish: bool,
47    pub is_multibar: bool,
48    pub show_bar: bool,
49    pub show_speed: bool,
50    pub show_percent: bool,
51    pub show_counter: bool,
52    pub show_time_left: bool,
53    pub show_tick: bool,
54    pub show_message: bool,
55    handle: T,
56}
57
58impl ProgressBar<Stdout> {
59    /// Create a new ProgressBar with default configuration.
60    ///
61    /// # Examples
62    ///
63    /// ```no_run
64    /// use std::thread;
65    /// use pbr::{ProgressBar, Units};
66    ///
67    /// let count = 1000;
68    /// let mut pb = ProgressBar::new(count);
69    /// pb.set_units(Units::Bytes);
70    ///
71    /// for _ in 0..count {
72    ///    pb.inc();
73    ///    thread::sleep_ms(100);
74    /// }
75    /// ```
76    pub fn new(total: u64) -> ProgressBar<Stdout> {
77        let handle = ::std::io::stdout();
78        ProgressBar::on(handle, total)
79    }
80}
81
82impl<T: Write> ProgressBar<T> {
83    /// Create a new ProgressBar with default configuration but
84    /// pass an arbitrary writer.
85    ///
86    /// # Examples
87    ///
88    /// ```no_run
89    /// use std::thread;
90    /// use std::io::stderr;
91    /// use pbr::{ProgressBar, Units};
92    ///
93    /// let count = 1000;
94    /// let mut pb = ProgressBar::on(stderr(), count);
95    /// pb.set_units(Units::Bytes);
96    ///
97    /// for _ in 0..count {
98    ///    pb.inc();
99    ///    thread::sleep_ms(100);
100    /// }
101    /// ```
102    pub fn on(handle: T, total: u64) -> ProgressBar<T> {
103        let mut pb = ProgressBar {
104            total,
105            current: 0,
106            start_time: Instant::now(),
107            units: Units::Default,
108            is_finish: false,
109            is_multibar: false,
110            show_bar: true,
111            show_speed: true,
112            show_percent: true,
113            show_counter: true,
114            show_time_left: true,
115            show_tick: false,
116            show_message: true,
117            bar_start: String::new(),
118            bar_current: String::new(),
119            bar_current_n: String::new(),
120            bar_remain: String::new(),
121            bar_end: String::new(),
122            tick: Vec::new(),
123            tick_state: 0,
124            width: None,
125            message: String::new(),
126            last_refresh_time: Instant::now(),
127            max_refresh_rate: None,
128            handle,
129        };
130        pb.format(FORMAT);
131        pb.tick_format(TICK_FORMAT);
132        pb
133    }
134
135    /// Set units, default is simple numbers
136    ///
137    /// # Examples
138    ///
139    /// ```no_run
140    /// use pbr::{ProgressBar, Units};
141    ///
142    /// let n_bytes = 100;
143    /// let mut pb = ProgressBar::new(n_bytes);
144    /// pb.set_units(Units::Bytes);
145    /// ```
146    pub fn set_units(&mut self, u: Units) {
147        self.units = u;
148    }
149
150    /// Set custom format to the drawing bar, default is `[=>-]`
151    ///
152    /// # Examples
153    ///
154    /// ```ignore
155    /// let mut pb = ProgressBar::new(...);
156    /// pb.format("[=>_]");
157    /// ```
158    pub fn format(&mut self, fmt: &str) {
159        if fmt.len() >= 5 {
160            let v: Vec<&str> = fmt.split("").collect();
161            self.bar_start = v[1].to_owned();
162            self.bar_current = v[2].to_owned();
163            self.bar_current_n = v[3].to_owned();
164            self.bar_remain = v[4].to_owned();
165            self.bar_end = v[5].to_owned();
166        }
167    }
168
169    /// Set message to display in the prefix, call with "" to stop printing a message.
170    ///
171    /// All newlines are replaced with spaces.
172    ///
173    /// # Examples
174    /// ```ignore
175    /// let mut pb = ProgressBar::new(20);
176    ///
177    /// for x in 0..20 {
178    ///    match x {
179    ///       0 => pb.message("Doing 1st Quarter"),
180    ///       5 => pb.message("Doing 2nd Quarter"),
181    ///       10 => pb.message("Doing 3rd Quarter"),
182    ///       15 => pb.message("Doing 4th Quarter"),
183    ///    }
184    ///    pb.inc().
185    /// }
186    ///
187    /// ```
188    pub fn message(&mut self, message: &str) {
189        self.message = message.replace(['\n', '\r'], " ")
190    }
191
192    /// Set tick format for the progressBar, default is \\|/-
193    ///
194    /// Format is not limited to 4 characters, any string can
195    /// be used as a tick format (the tick will successively
196    /// take the value of each char but won't loop backwards).
197    ///
198    ///
199    /// # Examples
200    /// ```ignore
201    /// let mut pb = ProgressBar::new(...);
202    /// pb.tick_format("▀▐▄▌")
203    /// ```
204    pub fn tick_format(&mut self, tick_fmt: &str) {
205        if tick_fmt != TICK_FORMAT {
206            self.show_tick = true;
207        }
208        self.tick = tick_fmt
209            .split("")
210            .map(|x| x.to_owned())
211            .filter(|x| !x.is_empty())
212            .collect();
213    }
214
215    /// Set width, or `None` for default.
216    ///
217    /// # Examples
218    ///
219    /// ```ignore
220    /// let mut pb = ProgressBar::new(...);
221    /// pb.set_width(Some(80));
222    /// ```
223    pub fn set_width(&mut self, w: Option<usize>) {
224        self.width = w;
225    }
226
227    /// Set max refresh rate, above which the progress bar will not redraw, or `None` for none.
228    ///
229    /// # Examples
230    ///
231    /// ```ignore
232    /// let mut pb = ProgressBar::new(...);
233    /// pb.set_max_refresh_rate(Some(Duration::from_millis(100)));
234    /// ```
235    pub fn set_max_refresh_rate(&mut self, w: Option<Duration>) {
236        self.max_refresh_rate = w;
237        if let Some(dur) = self.max_refresh_rate {
238            self.last_refresh_time = self.last_refresh_time - dur;
239        }
240    }
241
242    /// Update progress bar even though no progress are made
243    /// Useful to see if a program is bricked or just
244    /// not doing any progress.
245    ///
246    /// tick is not needed with add or inc
247    /// as performed operation take place
248    /// in draw function.
249    ///
250    /// # Examples
251    /// ```ignore
252    /// let mut pb = ProgressBar::new(...);
253    /// pb.inc();
254    /// for _ in ... {
255    ///    ...do something
256    ///    pb.tick();
257    /// }
258    /// pb.finish();
259    /// ```
260    pub fn tick(&mut self) {
261        self.tick_state = (self.tick_state + 1) % self.tick.len();
262        if self.current <= self.total {
263            self.draw()
264        }
265    }
266
267    /// Add to current value
268    ///
269    /// # Examples
270    ///
271    /// ```no_run
272    /// use pbr::ProgressBar;
273    ///
274    /// let mut pb = ProgressBar::new(10);
275    /// pb.add(5);
276    /// pb.finish();
277    /// ```
278    pub fn add(&mut self, i: u64) -> u64 {
279        self.current += i;
280        self.tick();
281        self.current
282    }
283
284    /// Manually set the current value of the bar
285    ///
286    /// # Examples
287    /// ```no_run
288    /// use pbr::ProgressBar;
289    ///
290    /// let mut pb = ProgressBar::new(10);
291    /// pb.set(8);
292    /// pb.finish();
293    pub fn set(&mut self, i: u64) -> u64 {
294        self.current = i;
295        self.tick();
296        self.current
297    }
298
299    /// Increment current value
300    pub fn inc(&mut self) -> u64 {
301        self.add(1)
302    }
303
304    /// Resets the start time to now
305    pub fn reset_start_time(&mut self) {
306        self.start_time = Instant::now();
307    }
308
309    fn draw(&mut self) {
310        let now = Instant::now();
311        if let Some(mrr) = self.max_refresh_rate {
312            if now - self.last_refresh_time < mrr && self.current < self.total {
313                return;
314            }
315        }
316
317        let mut time_elapsed = now - self.start_time;
318        if time_elapsed.is_zero() {
319            time_elapsed = Duration::from_nanos(1);
320        }
321        let speed = self.current as f64 / time_elapsed.as_secs_f64();
322        let width = self.width();
323
324        let mut out;
325        let mut parts = Vec::new();
326        let mut base = String::new();
327        let mut prefix = String::new();
328        let mut suffix = String::from(" ");
329
330        // precent box
331        if self.show_percent {
332            let percent = self.current as f64 / (self.total as f64 / 100f64);
333            parts.push(format!(
334                "{:.*} %",
335                2,
336                if percent.is_nan() { 0.0 } else { percent }
337            ));
338        }
339        // speed box
340        if self.show_speed {
341            match self.units {
342                Units::Default => parts.push(format!("{:.*}/s", 2, speed)),
343                Units::Bytes => parts.push(format!("{}/s", kb_fmt!(speed))),
344            };
345        }
346        // time left box
347        if self.show_time_left && self.current > 0 && self.total > self.current {
348            let left = 1. / speed * (self.total - self.current) as f64;
349            if left < 60. {
350                parts.push(format!("{:.0}s", left));
351            } else {
352                parts.push(format!("{:.0}m", left / 60.));
353            };
354        }
355        suffix += &parts.join(" ");
356        // message box
357        if self.show_message {
358            prefix = prefix + &self.message;
359        }
360        // counter box
361        if self.show_counter {
362            let (c, t) = (self.current as f64, self.total as f64);
363            prefix = prefix
364                + &match self.units {
365                    Units::Default => format!("{} / {} ", c, t),
366                    Units::Bytes => format!("{} / {} ", kb_fmt!(c), kb_fmt!(t)),
367                };
368        }
369        // tick box
370        if self.show_tick {
371            prefix = prefix + &format!("{} ", self.tick[self.tick_state]);
372        }
373        // bar box
374        if self.show_bar {
375            let p = prefix.chars().count() + suffix.chars().count() + 3;
376            if p < width {
377                let size = width - p;
378                let curr_count =
379                    ((self.current as f64 / self.total as f64) * size as f64).ceil() as usize;
380                if size >= curr_count {
381                    let rema_count = size - curr_count;
382                    base = self.bar_start.clone();
383                    if rema_count > 0 && curr_count > 0 {
384                        base =
385                            base + &self.bar_current.repeat(curr_count - 1) + &self.bar_current_n;
386                    } else {
387                        base = base + &self.bar_current.repeat(curr_count);
388                    }
389                    base = base + &self.bar_remain.repeat(rema_count) + &self.bar_end;
390                }
391            }
392        }
393        out = prefix + &base + &suffix;
394        // pad
395        if out.len() < width {
396            let gap = width - out.len();
397            out = out + &" ".repeat(gap);
398        }
399        // print
400        printfl!(self.handle, "\r{}", out);
401
402        self.last_refresh_time = Instant::now();
403    }
404
405    // finish_draw ensure that the progress bar is reached to its end, and do the
406    // last drawing if needed.
407    fn finish_draw(&mut self) {
408        let mut redraw = false;
409
410        if let Some(mrr) = self.max_refresh_rate {
411            if Instant::now() - self.last_refresh_time < mrr {
412                self.max_refresh_rate = None;
413                redraw = true;
414            }
415        }
416
417        if self.current < self.total {
418            self.current = self.total;
419            redraw = true;
420        }
421
422        if redraw {
423            self.draw();
424        }
425        self.is_finish = true;
426    }
427
428    /// Calling finish manually will set current to total and draw
429    /// the last time
430    pub fn finish(&mut self) {
431        self.finish_draw();
432        self.handle.write(b"").expect("write() failed");
433    }
434
435    /// Call finish and write string `s` that will replace the progress bar.
436    pub fn finish_print(&mut self, s: &str) {
437        self.finish_draw();
438        let width = self.width();
439        let mut out = s.to_owned();
440        if s.len() < width {
441            out += &" ".repeat(width - s.len());
442        };
443        printfl!(self.handle, "\r{}", out);
444        self.finish();
445    }
446
447    /// Call finish and write string `s` below the progress bar.
448    ///
449    /// If the ProgressBar is part of MultiBar instance, you should use
450    /// `finish_print` to print message.
451    pub fn finish_println(&mut self, s: &str) {
452        // `finish_println` does not allow in MultiBar mode, because printing
453        // new line will break the multiBar output.
454        if self.is_multibar {
455            return self.finish_print(s);
456        }
457        self.finish_draw();
458        printfl!(self.handle, "\n{}", s);
459    }
460
461    /// Get terminal width, from configuration, terminal size, or default(80)
462    fn width(&mut self) -> usize {
463        if let Some(w) = self.width {
464            w
465        } else if let Some((Width(w), _)) = terminal_size() {
466            w as usize
467        } else {
468            80
469        }
470    }
471}
472
473// Implement io::Writer
474impl<T: Write> Write for ProgressBar<T> {
475    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
476        let n = buf.len();
477        self.add(n as u64);
478        Ok(n)
479    }
480    fn flush(&mut self) -> io::Result<()> {
481        Ok(())
482    }
483}
484
485#[cfg(test)]
486mod test {
487    use crate::{ProgressBar, Units};
488    use std::time::Duration;
489
490    #[test]
491    fn add() {
492        let mut pb = ProgressBar::new(10);
493        pb.add(2);
494        assert!(pb.current == 2, "should add the given `n` to current");
495        assert!(
496            pb.add(2) == pb.current,
497            "add should return the current value"
498        );
499    }
500
501    #[test]
502    fn inc() {
503        let mut pb = ProgressBar::new(10);
504        pb.inc();
505        assert!(pb.current == 1, "should increment current by 1");
506    }
507
508    #[test]
509    fn format() {
510        let fmt = "[~> ]";
511        let mut pb = ProgressBar::new(1);
512        pb.format(fmt);
513        assert!(
514            pb.bar_start + &pb.bar_current + &pb.bar_current_n + &pb.bar_remain + &pb.bar_end
515                == fmt
516        );
517    }
518
519    #[test]
520    fn finish() {
521        let mut pb = ProgressBar::new(10);
522        pb.finish();
523        assert!(pb.current == pb.total, "should set current to total");
524        assert!(pb.is_finish, "should set is_finish to true");
525    }
526
527    #[test]
528    fn kb_fmt() {
529        let kb = 1024f64;
530        let mb = kb.powf(2f64);
531        let gb = kb.powf(3f64);
532        let tb = kb.powf(4f64);
533        assert_eq!(kb_fmt!(kb), "1.00 KB");
534        assert_eq!(kb_fmt!(mb), "1.00 MB");
535        assert_eq!(kb_fmt!(gb), "1.00 GB");
536        assert_eq!(kb_fmt!(tb), "1.00 TB");
537    }
538
539    #[test]
540    fn disable_speed_percent() {
541        let mut out = Vec::new();
542        let mut pb = ProgressBar::on(&mut out, 10);
543        pb.show_speed = false;
544        pb.show_percent = false;
545        pb.set_width(Some(80));
546        pb.add(2);
547        assert_eq!(
548            std::str::from_utf8(&out).unwrap(),
549            "\r2 / 10 [=============>-----------------------------------------------------] 0s ",
550        );
551    }
552
553    #[test]
554    fn disable_speed_time_left() {
555        let mut out = Vec::new();
556        let mut pb = ProgressBar::on(&mut out, 10);
557        pb.show_speed = false;
558        pb.show_time_left = false;
559        pb.set_width(Some(65));
560        pb.add(1);
561        assert_eq!(
562            std::str::from_utf8(&out).unwrap(),
563            "\r1 / 10 [====>------------------------------------------] 10.00 % ",
564        );
565    }
566
567    #[test]
568    fn disable_percent_time_left() {
569        let mut out = Vec::new();
570        let mut pb = ProgressBar::on(&mut out, 10);
571        pb.show_percent = false;
572        pb.show_time_left = false;
573        pb.set_units(Units::Bytes);
574        pb.set_width(Some(65));
575        pb.draw();
576        assert_eq!(
577            std::str::from_utf8(&out).unwrap(),
578            "\r0 B / 10 B [---------------------------------------------] 0 B/s ",
579        );
580    }
581
582    #[test]
583    fn disable_suffix() {
584        let mut out = Vec::new();
585        let mut pb = ProgressBar::on(&mut out, 10);
586        pb.show_speed = false;
587        pb.show_percent = false;
588        pb.show_time_left = false;
589        pb.set_units(Units::Bytes);
590        pb.set_width(Some(65));
591        pb.draw();
592        assert_eq!(
593            std::str::from_utf8(&out).unwrap(),
594            "\r0 B / 10 B [--------------------------------------------------]  ",
595        );
596    }
597
598    #[test]
599    fn max_refresh_rate_finish() {
600        let count = 500;
601        let mut out = Vec::new();
602        let mut pb = ProgressBar::on(&mut out, count);
603        pb.format("╢▌▌░╟");
604        pb.set_width(Some(80));
605        pb.set_max_refresh_rate(Some(Duration::from_millis(100)));
606        pb.show_speed = false;
607        pb.show_time_left = false;
608        pb.add(count / 2);
609        pb.add(count / 2);
610        let mut split = std::str::from_utf8(&out)
611            .unwrap()
612            .trim_start_matches('\r')
613            .split('\r');
614        assert_eq!(
615            split.next(),
616            Some("250 / 500 ╢▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌░░░░░░░░░░░░░░░░░░░░░░░░░░░░░╟ 50.00 %")
617        );
618        assert_eq!(
619            split.next(),
620            Some("500 / 500 ╢▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌╟ 100.00 %")
621        );
622    }
623}