progress/
progress.rs

1use std::iter::repeat;
2use std::io::{stdout, Write};
3use std::ops::{AddAssign, SubAssign};
4use terminal_size::{terminal_size, Height, Width};
5
6/// Tracks progress of a task.
7///
8/// There should be not more than a single instance started at a given point in
9/// time, it will mangle your terminal output.
10pub struct Progress {
11    current: usize,
12    total: usize,
13
14    caption: String,
15
16    width: Option<u16>,
17
18    started: bool,
19}
20
21impl Default for Progress {
22    fn default() -> Progress {
23        Progress::new("Progress", 0, 100)
24    }
25}
26
27impl Progress {
28    pub fn new<S: Into<String>>(caption: S, start: usize, end: usize) -> Self {
29        Progress {
30            current: start,
31            total: end,
32            caption: caption.into(),
33            width: None,
34            started: false,
35        }
36    }
37
38    pub fn new_with_width<S>(caption: S, start: usize, end: usize, width: u16) -> Self
39    where
40        S: Into<String>,
41    {
42        Progress {
43            current: start,
44            total: end,
45            caption: caption.into(),
46            width: Some(width),
47            started: false,
48        }
49    }
50
51    /// Returns the current progress absolute value.
52    ///
53    /// # Example
54    ///
55    /// ```
56    /// use progress::Progress;
57    /// let p = Progress::default();
58    /// assert_eq!(0, p.current());
59    /// ```
60    pub fn current(&self) -> usize {
61        self.current
62    }
63
64    /// Returns the total value.
65    ///
66    /// # Example
67    ///
68    /// ```
69    /// use progress::Progress;
70    /// let p = Progress::default();
71    /// assert_eq!(100, p.total());
72    /// ```
73    pub fn total(&self) -> usize {
74        self.total
75    }
76
77    /// Advances the Progress by a certain amount.
78    ///
79    /// # Examplw
80    ///
81    /// ```
82    /// use progress::Progress;
83    /// let mut p = Progress::default();
84    /// assert_eq!(0, p.current());
85    /// p.forward(10);
86    /// assert_eq!(10, p.current());
87    /// p += 20;
88    /// assert_eq!(30, p.current());
89    /// ```
90    pub fn forward(&mut self, step: usize) -> &Self {
91        *self += step;
92        self
93    }
94
95    /// Advances the Progress by a certain amount.
96    ///
97    /// # Examplw
98    ///
99    /// ```
100    /// use progress::Builder;
101    /// let mut p = Builder::new().set_start(30).build();
102    /// assert_eq!(30, p.current());
103    /// p.backward(10);
104    /// assert_eq!(20, p.current());
105    /// p -= 20;
106    /// assert_eq!(0, p.current());
107    /// ```
108    pub fn backward(&mut self, step: usize) -> &Self {
109        *self -= step;
110        self
111    }
112
113    /// Advances the Progress by exactly one.
114    pub fn increment(&mut self) -> &Self {
115        self.forward(1)
116    }
117
118    /// Does a step backwards at the Progress.
119    pub fn decrement(&mut self) -> &Self {
120        self.backward(1)
121    }
122
123    /// Determines wheter a Progress has finished (reached the total) or not.
124    pub fn finished(&self) -> bool {
125        self.current >= self.total
126    }
127
128    /// Returns the relative value of the Progress.
129    pub fn process(&self) -> u8 {
130        (self.current * 100 / self.total) as u8
131    }
132
133    /// Activates the Progress.
134    pub fn start(&mut self) -> &Self {
135        self.started = true;
136        self
137    }
138
139    /// Returns the current caption.
140    pub fn caption(&self) -> &String {
141        &self.caption
142    }
143}
144
145impl AddAssign<usize> for Progress {
146    fn add_assign(&mut self, step: usize) {
147        let new = self.current + step;
148        if new > self.total {
149            self.current = self.total;
150        } else {
151            self.current += step;
152        }
153        print_bar(self);
154    }
155}
156
157impl SubAssign<usize> for Progress {
158    fn sub_assign(&mut self, step: usize) {
159        self.current = self.current.saturating_sub(step);
160        print_bar(self);
161    }
162}
163
164fn print_bar(p: &Progress) {
165    let p_info = format!(
166        "{current} / {total} ({process})",
167        current = p.current(),
168        total = p.total(),
169        process = p.process()
170    );
171    let caption = p.caption();
172
173    let terminal_width = p.width
174        .unwrap_or_else(|| terminal_size().unwrap_or((Width(79), Height(0))).0 .0); // terminal_size().unwrap_or((Width(79), Height(0)));
175
176    let bar_width = terminal_width as usize // Width of terminal
177        - p_info.len()  // Width of right summary
178        - caption.len() // Width of caption
179        - 3  // Colon and spaces
180        - 2; // vertical bars in the progress meter
181    let done_width = (bar_width * p.process() as usize) / 100;
182    let todo_width = bar_width - done_width;
183    let done_bar = repeat("#").take(done_width).collect::<String>();
184    let todo_bar = repeat("-").take(todo_width).collect::<String>();
185    let bar = format!("|{}{}|", done_bar, todo_bar);
186
187    print!(
188        "{caption}: {bar} {info}\r",
189        caption = caption,
190        bar = bar,
191        info = p_info
192    );
193    stdout().flush().unwrap();
194}