tqdm_rs/
lib.rs

1//! [![ci-badge][]][ci] [![docs-badge][]][docs] [![crate-version]][crate-link]
2//!
3//! A simple progress bar library inspired by Python's `tqdm`.
4//!
5//! ```rust
6//! for _ in tqdm_rs::Tqdm::new(0..10) {
7//!     tqdm_rs::write("Doing some work...\nOn multiple lines!");
8//!     std::thread::sleep(std::time::Duration::from_millis(100));
9//!     continue
10//! }
11//!
12//! // It is possible to use print, but it looks more clumsy!
13//! for _ in tqdm_rs::Tqdm::new(0..10) {
14//!     println!("Doing some work...\nOn multiple lines!");
15//!     std::thread::sleep(std::time::Duration::from_millis(100));
16//!     continue
17//! }
18//!
19//! let mut tq = tqdm_rs::Tqdm::manual(100);
20//! for _ in 0..10 {
21//!     println!("I am updated manually!");
22//!     tq.update(10);
23//! }
24//! ```
25//!
26//! [ci]: https://github.com/Elinvynia/tqdm-rs/actions?query=workflow%3ARust
27//! [ci-badge]: https://img.shields.io/github/workflow/status/Elinvynia/tqdm-rs/Rust/master?style=flat-square
28//! [docs]: https://docs.rs/tqdm-rs
29//! [docs-badge]: https://img.shields.io/badge/docs-online-5023dd.svg?style=flat-square
30//! [crate-link]: https://crates.io/crates/tqdm-rs
31//! [crate-version]: https://img.shields.io/crates/v/tqdm-rs.svg?style=flat-square
32
33#![forbid(unsafe_code)]
34#![warn(missing_debug_implementations)]
35#![warn(missing_docs)]
36
37use std::sync::Mutex;
38
39#[macro_use]
40extern crate lazy_static;
41
42lazy_static! {
43    static ref TEXT: Mutex<String> = Mutex::new(String::new());
44}
45
46/// Empty struct used for creating one of the two tqdm types.
47#[derive(Debug)]
48pub struct Tqdm;
49
50impl Tqdm {
51    /// Creates a new `tqdm` instance which handles progress automatically.
52    #[allow(clippy::new_ret_no_self)]
53    pub fn new<I: Iterator>(iter: I) -> TqdmAuto<I> {
54        let total = iter.size_hint().0;
55        TqdmAuto {
56            iter,
57            current: 0,
58            total,
59        }
60    }
61
62    /// Creates a new manual `tqdm` instance, providing control over the progress.
63    pub fn manual(total: usize) -> TqdmManual {
64        TqdmManual { current: 0, total }
65    }
66}
67
68/// Properly handles writing text along with the progress bar.
69/// `println!` works, but looks a little clumsy if the loop sleeps.
70pub fn write(text: &str) {
71    let mut msg = TEXT.lock().unwrap();
72    *msg = String::from(text);
73}
74
75fn clear_previous_line() {
76    print!("\x1b[1A");
77    print!("\r");
78    let size = terminal_size::terminal_size()
79        .unwrap_or((terminal_size::Width(120), terminal_size::Height(120)));
80    let width = size.0 .0 as usize;
81    print!("{}", " ".repeat(width));
82    print!("\r");
83}
84
85trait WriteCon {
86    fn get_current_amount(&self) -> usize {
87        0
88    }
89
90    fn get_total_amount(&self) -> usize {
91        0
92    }
93
94    fn get_percentage(&self) -> usize {
95        let fraction = self.get_current_amount() as f32 / self.get_total_amount() as f32;
96        (fraction * 100.0).round() as usize
97    }
98
99    fn create_bar(&self) -> String {
100        let percents = self.get_percentage();
101        let current = self.get_current_amount();
102        let total = self.get_total_amount();
103        let length = total.to_string().len();
104        let mut bar = String::new();
105
106        bar += "[";
107        for x in 1..=20 {
108            if x * 5 <= percents {
109                bar += "#"
110            } else {
111                bar += " "
112            }
113        }
114        bar += "]";
115
116        format!(
117            "{:>3}% {} {:>length$}/{}",
118            percents,
119            bar,
120            current,
121            total,
122            length = length
123        )
124    }
125
126    fn display(&self) {
127        let bar = self.create_bar();
128        let mut text = TEXT.lock().unwrap();
129
130        if self.get_current_amount() == 0 {
131            return;
132        }
133
134        let lines = text.matches('\n').count();
135        if self.get_current_amount() == 1 && lines > 0 {
136            for _ in 0..=lines + 1 {
137                println!();
138            }
139        }
140        for _ in 0..lines {
141            clear_previous_line();
142        }
143        if lines > 0 {
144            clear_previous_line();
145            clear_previous_line();
146        }
147
148        if !text.is_empty() {
149            println!("{}", text);
150            *text = String::new();
151        }
152
153        println!("{}", bar);
154    }
155}
156
157/// Struct that handles progress automatically.
158#[derive(Debug)]
159pub struct TqdmAuto<I: Iterator> {
160    iter: I,
161    current: usize,
162    total: usize,
163}
164
165impl<I: Iterator> Iterator for TqdmAuto<I> {
166    type Item = I::Item;
167    fn next(&mut self) -> Option<Self::Item> {
168        let next = self.iter.next();
169
170        #[allow(clippy::branches_sharing_code)]
171        if next.is_some() {
172            self.display();
173            self.current += 1;
174            next
175        } else {
176            self.display();
177            None
178        }
179    }
180}
181
182impl<I: Iterator> WriteCon for TqdmAuto<I> {
183    fn get_current_amount(&self) -> usize {
184        self.current
185    }
186    fn get_total_amount(&self) -> usize {
187        self.total
188    }
189}
190
191/// For manually updating the progress.
192#[derive(Debug, Copy, Clone)]
193pub struct TqdmManual {
194    current: usize,
195    total: usize,
196}
197
198impl TqdmManual {
199    /// Updates the progress manually and displays the progress bar.
200    pub fn update(mut self, num: usize) {
201        self.current = std::cmp::min(self.total, self.current + num);
202        self.display();
203    }
204}
205
206impl WriteCon for TqdmManual {
207    fn get_current_amount(&self) -> usize {
208        self.current
209    }
210
211    fn get_total_amount(&self) -> usize {
212        self.total
213    }
214}