progress_string/
lib.rs

1//! This library is primarily concerned with generating strings that can be used by your favorite
2//! terminal stream manipulation system to display a progress bar.
3//!
4//! #### Example
5//!
6//! ```
7//! # #[cfg(unix)]
8//! use std::thread::sleep;
9//! # #[cfg(unix)]
10//! use std::time::Duration;
11//!
12//! # #[cfg(unix)]
13//! const TOTAL: usize = 1000;
14//! # #[cfg(unix)]
15//! fn main() {
16//!     let mut bar = progress_string::BarBuilder::new()
17//!         .total(TOTAL)
18//!         .include_percent()
19//!         .build();
20//!
21//!     println!("starting the progress");
22//!     for i in 0..TOTAL {
23//!         bar.replace(i);
24//!         print!(
25//!             "{}{}",
26//!             termion::cursor::Left(bar.get_last_width() as u16),
27//!             bar.to_string()
28//!         );
29//!         sleep(Duration::from_millis(10));
30//!     }
31//!     println!("\ndone with progress");
32//! }
33//!
34//! # #[cfg(not(unix))]
35//! # fn main() {}
36//!```
37
38/// Represents a progress bar which can be used to get your progress string.
39pub struct Bar {
40    pub current_partial: usize,
41    pub total: usize,
42    width: usize,
43    empty_char: char,
44    full_char: char,
45    leading_char: char,
46    include_percent: bool,
47    include_numbers: bool,
48    previous_text_width: usize,
49}
50
51/// Helper struct for building a progress bar.
52///
53/// #### Examples
54/// ```
55/// use progress_string::BarBuilder;
56///
57/// let bar = BarBuilder::new()
58///                     .total(1000000)
59///                     .width(20)
60///                     .empty_char('0')
61///                     .full_char('X')
62///                     .include_percent()
63///                     .build();
64/// ```
65/// the above would look something like this
66/// `[XXXXXXXXXX0000000000] 50.00%`
67pub struct BarBuilder {
68    bar: Bar,
69}
70
71impl Default for BarBuilder {
72    fn default() -> Self {
73        BarBuilder::new()
74    }
75}
76
77impl BarBuilder {
78    /// Create a new `BarBuilder`.
79    pub fn new() -> Self {
80        BarBuilder {
81            bar: Bar::default(),
82        }
83    }
84    /// Update the total (default 100).
85    ///
86    /// #### Examples
87    /// ```
88    /// use progress_string::BarBuilder;
89    ///
90    /// let thousand = BarBuilder::new().total(1000).build();
91    /// // yields [█                                                 ]
92    /// ```
93    pub fn total(mut self, total: usize) -> BarBuilder {
94        self.bar.total = total;
95        self
96    }
97    /// Update the progress section's width (default 50).
98    /// ```
99    /// use progress_string::BarBuilder;
100    ///
101    /// let bar = BarBuilder::new().width(10);
102    /// // yields [          ]
103    /// ```
104    pub fn width(mut self, width: usize) -> BarBuilder {
105        self.bar.width = width;
106        self
107    }
108    /// Update the character you want to use as an empty section of the progress bar (default ' ').
109    ///
110    /// #### Examples
111    /// ```
112    /// use progress_string::BarBuilder;
113    ///
114    /// let zero_emp = BarBuilder::new().empty_char('0').build();
115    /// // yields
116    /// // [██████████00000000000]
117    /// ```
118    pub fn empty_char(mut self, character: char) -> BarBuilder {
119        self.bar.empty_char = character;
120        self
121    }
122    /// Update the character you want to use as a full section of the bar (default '█').
123    ///
124    /// #### Examples
125    /// ```
126    /// use progress_string::BarBuilder;
127    ///
128    /// let x_bar = BarBuilder::new().full_char('X').build();
129    /// // yields [XXXXXX      ]
130    /// let y_bar = BarBuilder::new().full_char('Y').build();
131    /// // yields [YYYYYY      ]
132    /// ```
133    pub fn full_char(mut self, character: char) -> BarBuilder {
134        self.bar.full_char = character;
135        self
136    }
137    /// Update the character you want to use to lead the full section of the bar
138    /// (defaults to the value of `full_char` if not provided).
139    ///
140    /// #### Examples
141    /// ```
142    /// use progress_string::BarBuilder;
143    ///
144    /// let x_bar = BarBuilder::new()
145    ///                 .full_char('X')
146    ///                 .leading_char('}')
147    ///                 .build();
148    /// // yields [XXXXXX}     ]
149    /// let y_bar = BarBuilder::new()
150    ///                 .full_char('Y')
151    ///                 .leading_char(')')
152    ///                 .build();
153    /// // yields [YYYYYY)     ]
154    /// ```
155    pub fn leading_char(mut self, character: impl Into<Option<char>>) -> BarBuilder {
156        if let Some(char) = character.into() {
157            self.bar.leading_char = char;
158        } else {
159            self.bar.leading_char = self.bar.full_char;
160        }
161        self
162    }
163
164    /// Update the bar to include the percent after the bar representation (default `false`).
165    ///
166    /// #### Examples
167    /// ```
168    /// use progress_string::BarBuilder;
169    ///
170    /// let no_p = BarBuilder::new().include_percent().build();
171    /// // yields [██████████          ] 50.00%
172    /// let with_p = BarBuilder::new();
173    /// // yields [██████████          ]
174    /// ```
175    pub fn include_percent(mut self) -> BarBuilder {
176        self.bar.include_percent = true;
177        self
178    }
179    /// Update the bar to include the divison after the bar representation.
180    ///
181    /// #### Examples
182    /// ```
183    /// use progress_string::BarBuilder;
184    ///
185    /// let mut no_n = BarBuilder::new().build();
186    /// no_n.replace(50);
187    /// // yields [██████████          ]
188    /// let mut with_n = BarBuilder::new().include_numbers().build();
189    /// with_n.replace(50)
190    /// // yields [██████████          ] 50/100
191    /// ```
192    pub fn include_numbers(mut self) -> BarBuilder {
193        self.bar.include_numbers = true;
194        self
195    }
196    /// deprecated please use `build`
197    #[deprecated]
198    pub fn get_bar(self) -> Bar {
199        self.bar
200    }
201
202    /// Complete building your bar and return the updated struct.
203    ///
204    /// #### Examples
205    /// ```
206    /// use progress_string::BarBuilder;
207    ///
208    /// let bar = BarBuilder::new().build();
209    /// // yields a default bar instance
210    /// ```
211    pub fn build(self) -> Bar {
212        self.bar
213    }
214}
215
216impl Default for Bar {
217    /// Bar constructor with default values.
218    /// ```text
219    /// Bar {
220    ///     current_partial: 0,
221    ///     total: 100,
222    ///     width: 50,
223    ///     full_char:  '█',
224    ///     empty_char: ' ',
225    ///     leading_char: '█',
226    ///     include_percent: false,
227    ///     include_numbers: false,
228    ///     previous_text_width: 0
229    /// }
230    /// ```
231    fn default() -> Self {
232        Self {
233            current_partial: 0,
234            total: 100,
235            width: 50,
236            full_char: '█',
237            empty_char: ' ',
238            leading_char: '█',
239            include_percent: false,
240            include_numbers: false,
241            previous_text_width: 0,
242        }
243    }
244}
245
246impl Bar {
247    /// Update the `current_partial` value by adding the `to_add` parameter.
248    ///
249    /// #### Examples
250    /// ```
251    /// use progress_string::Bar;
252    ///
253    /// let mut bar = Bar::default();
254    /// bar.update(10);
255    /// assert_eq!(bar.current_partial, 10);
256    /// ```
257    pub fn update(&mut self, to_add: usize) {
258        self.previous_text_width = self.get_width();
259        self.current_partial += to_add;
260    }
261    /// Update the current partial by replacing the current value.
262    ///
263    /// #### Examples
264    /// ```
265    /// use progress_string::Bar;
266    ///
267    /// let mut bar = Bar::default();
268    /// bar.replace(10);
269    /// assert_eq!(bar.current_partial, 10);
270    /// ```
271    pub fn replace(&mut self, new_progress: usize) {
272        self.previous_text_width = self.get_width();
273        self.current_partial = new_progress;
274    }
275    /// Get the current width of characters in the bar.
276    ///
277    /// This includes the brackets, spaces and percent if set.
278    ///
279    /// #### Examples
280    /// ```
281    /// use progress_string::{Bar, BarBuilder};
282    ///
283    /// let bar = Bar::default();
284    /// assert_eq!(bar.get_width(), 52);
285    ///
286    /// let mut with_percent = BarBuilder::new().include_percent().build();
287    /// assert_eq!(with_percent.get_width(), 58);
288    ///
289    /// with_percent.update(10);
290    /// assert_eq!(with_percent.get_width(), 59);
291    ///
292    /// with_percent.replace(100);
293    /// assert_eq!(with_percent.get_width(), 60);
294    /// ```
295    pub fn get_width(&self) -> usize {
296        let mut width: usize = 52;
297        if self.include_numbers {
298            let total_string = format!("{}", self.total);
299            let partial_string = format!("{}", self.current_partial);
300            width += total_string.len() + partial_string.len() + 2;
301        }
302        if self.include_percent {
303            let current_percent = self.calculate_percent();
304            if current_percent >= 0.95 {
305                width += 8;
306            } else if current_percent > 0.095 {
307                width += 7;
308            } else {
309                width += 6;
310            }
311        }
312        width
313    }
314    /// Similar to `get_width` but gets the value before the last `update` or `replace` call.
315    ///
316    /// This is useful for when you are trying to clear the terminal.
317    pub fn get_last_width(&self) -> usize {
318        self.previous_text_width
319    }
320
321    fn calculate_percent(&self) -> f32 {
322        self.current_partial as f32 / self.total as f32
323    }
324}
325
326impl std::fmt::Display for Bar {
327    /// Get the string representation of the progress bar.
328    ///
329    /// This string will include brackets ([]) around the empty/full characters. The width is
330    /// determined by the width property. If `bar.include_percent == true`, the resulting string
331    /// will include a space and the percent with 2 decimal places followed by %.
332    ///
333    /// #### Examples
334    /// ```
335    /// use progress_string::BarBuilder;
336    ///
337    /// let mut with_percent = BarBuilder::new().include_percent().build();
338    /// with_percent.update(50);
339    /// println!("{}", with_percent.to_string());
340    /// // prints [█████████████████████████                         ] 50.00%
341    /// let mut no_percent = BarBuilder::new().build();
342    /// no_percent.update(50);
343    /// // prints [█████████████████████████                         ]
344    /// ```
345    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346        let percent = self.calculate_percent();
347        f.write_str("[")?;
348        for i in 0..self.width {
349            if (i as f32) < ((self.width as f32 * percent) - 1.0) {
350                f.write_fmt(format_args!("{}", self.full_char))?;
351            } else if (i as f32) < (self.width as f32 * percent) {
352                f.write_fmt(format_args!("{}", self.leading_char))?;
353            } else {
354                f.write_fmt(format_args!("{}", self.empty_char))?;
355            }
356        }
357        f.write_str("]")?;
358        if self.include_percent {
359            f.write_fmt(format_args!(" {:.2}%", percent * 100.0))?;
360        }
361        if self.include_numbers {
362            f.write_fmt(format_args!(" {:?}/{:?}", self.current_partial, self.total))?;
363        }
364        Ok(())
365    }
366}
367
368#[cfg(test)]
369mod tests {
370    use super::*;
371
372    #[test]
373    fn include_percent_test() {
374        let mut bar = BarBuilder::new().include_percent().build();
375        // single digit percent
376        assert_eq!(bar.get_width(), 58);
377        assert_eq!(
378            format!("{}", bar),
379            "[                                                  ] 0.00%"
380        );
381        bar.update(50);
382        // double digit percent
383        assert_eq!(bar.get_width(), 59);
384        assert_eq!(
385            format!("{}", bar),
386            "[█████████████████████████                         ] 50.00%"
387        );
388        bar.update(50);
389        // triple digit percent
390        assert_eq!(bar.get_width(), 60);
391        assert_eq!(
392            format!("{}", bar),
393            "[██████████████████████████████████████████████████] 100.00%"
394        );
395    }
396
397    #[test]
398    fn include_numbers_test() {
399        let mut bar = BarBuilder::new().include_numbers().build();
400        // 0/100
401        assert_eq!(bar.get_width(), 58);
402        assert_eq!(
403            format!("{}", bar),
404            "[                                                  ] 0/100"
405        );
406        bar.update(50);
407        // 50/100
408        assert_eq!(bar.get_width(), 59);
409        assert_eq!(
410            format!("{}", bar),
411            "[█████████████████████████                         ] 50/100"
412        );
413        bar.update(50);
414        // 100/100
415        assert_eq!(bar.get_width(), 60);
416        assert_eq!(
417            format!("{}", bar),
418            "[██████████████████████████████████████████████████] 100/100"
419        );
420    }
421
422    #[test]
423    fn update_test() {
424        let mut bar = Bar::default();
425        bar.update(50);
426        assert_eq!(bar.current_partial, 50);
427        assert_eq!(
428            format!("{}", bar),
429            "[█████████████████████████                         ]"
430        );
431    }
432
433    #[test]
434    fn replace_test() {
435        let mut bar = Bar::default();
436        bar.update(50);
437        assert_eq!(bar.current_partial, 50);
438        assert_eq!(
439            format!("{}", bar),
440            "[█████████████████████████                         ]"
441        );
442        bar.replace(10);
443        assert_eq!(bar.current_partial, 10);
444        assert_eq!(
445            format!("{}", bar),
446            "[█████                                             ]"
447        );
448    }
449
450    #[test]
451    fn to_string_test() {
452        let mut bar = Bar::default();
453        assert_eq!(
454            bar.to_string(),
455            "[                                                  ]"
456        );
457        bar.update(50);
458        assert_eq!(
459            bar.to_string(),
460            "[█████████████████████████                         ]"
461        )
462    }
463    #[test]
464    fn leading_char() {
465        let mut bar = BarBuilder::new().leading_char('>').build();
466        assert_eq!(
467            bar.to_string(),
468            "[                                                  ]"
469        );
470        bar.update(50);
471        assert_eq!(
472            bar.to_string(),
473            "[████████████████████████>                         ]"
474        )
475    }
476    #[test]
477    fn display() {
478        let mut bar = BarBuilder::new().build();
479        assert_eq!(
480            format!("{}", bar),
481            "[                                                  ]"
482        );
483        bar.update(50);
484        assert_eq!(
485            format!("{}", bar),
486            "[█████████████████████████                         ]"
487        )
488    }
489}