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}