progress/
lib.rs

1#![crate_name = "progress"]
2#![crate_type = "rlib"]
3#![crate_type = "dylib"]
4
5//! **progress** is meant to be a set of useful tools for showing program running
6//! progress (as its name) and steps.
7//!
8//! Installation
9//! ============
10//!
11//! Add the following lines to your `Cargo.toml` dependencies section, if you
12//! use [Cargo](https://crates.io):
13//!
14//! ```
15//! [dependencies]
16//! progress = "0.1.0"
17//! ```
18//!
19//! Usage
20//! =====
21//!
22//! Please check documentations for each structs. Life is easy here :)
23//!
24//! Who create this
25//! ===============
26//!
27//! - [Ying-Ruei Liang (KK)](https://github.com/TheKK)
28//!
29//! Contribution
30//! ============
31//!
32//! I can't believe you would say that, but if you have any great idea or any
33//! bug report, don't be hesitate! It would be more wonderful if someone wants
34//! to write some code for this project!
35//!
36//! TODO list
37//! =========
38//!
39//! - BarBuilder, so we can do some customization, e.g. change the symbols used
40//! - Add more type of indicators, e.g. spinning symbol or nayn cat :3
41//! - Color/styled text support (print!("{:<50}") will count unprintable text as
42//! well, I have to solve it first)
43//! - Make output format customizable, despite I have no idea how to achieve this
44//! for now.
45//!
46//! License
47//! =======
48//!
49//! MIT
50
51use std::io::{self, Write};
52
53extern crate terminal_size;
54use terminal_size::{terminal_size, Width};
55
56/// A builder that used for creating customize progress bar.
57///
58/// # Examples
59///
60/// ```
61/// use std::thread;
62///
63/// extern crate progress;
64///
65/// fn main() {
66///     let mut bar = progress::BarBuilder::new()
67///         .left_cap("<")
68///         .right_cap(">")
69///         .empty_symbol("-")
70///         .filled_symbol("/")
71///         .build();
72///
73///     bar.set_job_title("Meow...");
74///
75///     for i in 0..11 {
76///         thread::sleep_ms(500);
77///         bar.reach_percent(i * 10);
78///     }
79/// }
80pub struct BarBuilder {
81    _left_cap: Option<String>,
82    _right_cap: Option<String>,
83    _filled_symbol: Option<String>,
84    _empty_symbol: Option<String>,
85}
86
87impl BarBuilder {
88    /// Create a new progress bar builder.
89    pub fn new() -> BarBuilder {
90        BarBuilder {
91            _left_cap: None,
92            _right_cap: None,
93            _filled_symbol: None,
94            _empty_symbol: None,
95        }
96    }
97
98    /// Set desired symbol used as left cap
99    ///
100    /// ```shell
101    /// [=========-] 90%
102    /// ^
103    pub fn left_cap(&mut self, symbol: &str) -> &mut BarBuilder {
104        self._left_cap = Some(symbol.to_string());
105
106        self
107    }
108
109    /// Set desired symbol used as right cap
110    ///
111    /// ```shell
112    /// [=========-] 90%
113    ///            ^
114    pub fn right_cap(&mut self, symbol: &str) -> &mut BarBuilder {
115        self._right_cap = Some(symbol.to_string());
116
117        self
118    }
119
120    /// Set desired symbol used as filled bar
121    ///
122    /// ```shell
123    /// [=========-] 90%
124    ///  ^^^^^^^^^
125    pub fn filled_symbol(&mut self, symbol: &str) -> &mut BarBuilder {
126        self._filled_symbol = Some(symbol.to_string());
127
128        self
129    }
130
131    /// Set desired symbol used as empty bar
132    ///
133    /// ```shell
134    /// [=========-] 90%
135    ///           ^
136    ///  ```
137    pub fn empty_symbol(&mut self, symbol: &str) -> &mut BarBuilder {
138        self._empty_symbol = Some(symbol.to_string());
139
140        self
141    }
142
143    /// Build progress bar according to previous configurations.
144    pub fn build(&mut self) -> Bar {
145        // XXX Does `take()` appropriate way?
146        Bar {
147            _job_title: String::new(),
148            _progress_percentage: 0,
149            _left_cap: self._left_cap.take().unwrap_or(String::from("[")),
150            _right_cap: self._right_cap.take().unwrap_or(String::from("]")),
151            _filled_symbol: self._filled_symbol.take().unwrap_or(String::from("=")),
152            _empty_symbol: self._empty_symbol.take().unwrap_or(String::from("-")),
153        }
154    }
155}
156
157/// Struct that used for presenting progress bar with plain texts.
158///
159/// It looks like:
160///
161/// ```shell
162/// Doing something            [===-------] 70%
163/// ```
164///
165/// # Examples
166///
167/// ```
168/// use std::thread;
169///
170/// extern crate progress;
171///
172/// fn main() {
173///     let bar = progress::Bar::new();
174///
175///     bar.set_job_title("Working...");
176///
177///     for i in 0..11 {
178///         thread::sleep_ms(100);
179///         bar.reach_percent(i * 10);
180///     }
181/// }
182pub struct Bar {
183    _job_title: String,
184    _progress_percentage: i32,
185    _left_cap: String,
186    _right_cap: String,
187    _filled_symbol: String,
188    _empty_symbol: String,
189}
190
191impl Bar {
192    /// Create a new progress bar.
193    pub fn new() -> Bar {
194        Bar {
195            _job_title: String::new(),
196            _progress_percentage: 0,
197            _left_cap: String::from("["),
198            _right_cap: String::from("]"),
199            _filled_symbol: String::from("="),
200            _empty_symbol: String::from("-"),
201        }
202    }
203
204    /// Reset progress percentage to zero and job title to empty string. Also
205    /// prints "\n".
206    pub fn jobs_done(&mut self) {
207        self._job_title.clear();
208        self._progress_percentage = 0;
209
210        print!("\n");
211    }
212
213    /// Set text shown in progress bar.
214    pub fn set_job_title(&mut self, new_title: &str) {
215        self._job_title.clear();
216        self._job_title.push_str(new_title);
217        self._show_progress();
218    }
219
220    /// Put progress to given percentage.
221    pub fn reach_percent(&mut self, percent: i32) {
222        self._progress_percentage = percent;
223        self._show_progress();
224    }
225
226    /// Increase progress with given percentage.
227    pub fn add_percent(&mut self, progress: i32) {
228        self._progress_percentage += progress;
229        self._show_progress();
230    }
231}
232
233impl Bar {
234    fn _show_progress(&self) {
235        let width = if let Some((Width(w), _)) = terminal_size() {
236            w as i32
237        } else {
238            81 as i32
239        };
240        let overhead = self._progress_percentage / 100;
241        let left_percentage = self._progress_percentage - overhead * 100;
242        let bar_len = width - (50 + 5) - 2;
243        let bar_finished_len = ((bar_len as f32) *
244                                (left_percentage as f32 / 100.0)) as i32;
245        let filled_symbol = if overhead & 0b1 == 0 {
246            &self._filled_symbol
247        } else {
248            &self._empty_symbol
249        };
250        let empty_symbol = if overhead & 0b1 == 0 {
251            &self._empty_symbol
252        } else {
253            &self._filled_symbol
254        };
255
256        io::stdout().flush().unwrap();
257        print!("\r");
258
259        print!("{:<50}", self._job_title);
260        print!("{}", self._left_cap);
261        for _ in 0..bar_finished_len {
262            print!("{}", filled_symbol);
263        }
264        for _ in bar_finished_len..bar_len {
265            print!("{}", empty_symbol);
266        }
267        print!("{}", self._right_cap);
268        print!("{:>4}%", self._progress_percentage);
269    }
270}
271
272/// Struct that used for presenting progress with plain texts.
273///
274/// It looks like:
275///
276/// ```shell
277/// Doing something
278/// ```
279///
280/// # Examples
281///
282/// ```
283/// use std::thread;
284///
285/// extern crate progress;
286///
287/// fn main() {
288///     let mut text = progress::Text::new();
289///
290///     text.set_job_title("Drawing...");
291///     thread::sleep_ms(1000);
292///
293///     text.set_job_title("Painting...");
294///     thread::sleep_ms(1000);
295///
296///     text.set_job_title("Sleeping zzz");
297///     thread::sleep_ms(1000);
298///
299///     text.set_job_title("Wait! Is that a nyan cat?");
300///     thread::sleep_ms(1000);
301///
302///     text.set_job_title("This must be my dream zzzzzz");
303///     thread::sleep_ms(1000);
304///
305///     text.jobs_done();
306/// }
307pub struct Text {
308    _job_title: String,
309}
310
311impl Text {
312    /// Create a new progress text.
313    pub fn new() -> Text {
314        Text {
315            _job_title: String::new(),
316        }
317    }
318
319    /// Set text shown in progress text.
320    pub fn set_job_title(&mut self, new_title: &str) {
321        self._job_title.clear();
322        self._job_title.push_str(new_title);
323        self._show_progress();
324    }
325
326    /// Tell progress::Text everything has been done. Also prints "\n".
327    pub fn jobs_done(&self) {
328        print!("\n");
329    }
330}
331
332impl Text {
333    fn _show_progress(& self) {
334        io::stdout().flush().unwrap();
335        print!("\r");
336        // TODO How to handle extra text?
337        print!("{:<81}", self._job_title);
338    }
339}
340
341/// Struct that used for presenting progress with plain texts.
342///
343/// It looks like:
344///
345/// ```shell
346/// * Doing something
347/// / Doing another thing
348/// ```
349///
350/// # Examples
351///
352/// ```
353/// use std::thread;
354///
355/// extern crate progress;
356///
357/// fn main() {
358///     let mut spinningCircle = progress::SpinningCircle::new();
359///
360///     spinningCircle.set_job_title("Writing boring and stupid homeworks");
361///     for _ in 0..50 {
362///         thread::sleep_ms(50);
363///         spinningCircle.tick();
364///     }
365///     spinningCircle.jobs_done();
366///
367///     spinningCircle.set_job_title("Previewing boring and stupid subjects");
368///     for _ in 0..50 {
369///         thread::sleep_ms(50);
370///         spinningCircle.tick();
371///     }
372///     spinningCircle.jobs_done();
373///
374///     spinningCircle.set_job_title("Learning and creating interesting programs");
375///     for _ in 0..50 {
376///         thread::sleep_ms(50);
377///         spinningCircle.tick();
378///     }
379///     spinningCircle.jobs_done();
380/// }
381pub struct SpinningCircle {
382    _job_title: String,
383    _circle_symbols: Vec<char>,
384    _finished_symbol: char,
385    _tick_count: usize,
386}
387
388impl SpinningCircle {
389    /// Create a new progress spinning circle.
390    pub fn new() -> SpinningCircle {
391        SpinningCircle {
392            _job_title: String::new(),
393            _circle_symbols: vec!['|', '/', '-', '\\'],
394            _finished_symbol: '*',
395            _tick_count: 0,
396        }
397    }
398
399    /// Set text shown in progress spinning circle.
400    pub fn set_job_title(&mut self, new_title: &str) {
401        self._job_title.clear();
402        self._job_title.push_str(new_title);
403        self._show_progress();
404    }
405
406    /// Tell spinning circle to spin a bit.
407    pub fn tick(&mut self) {
408        self._tick_count += 1;
409        self._show_progress();
410    }
411
412    /// Print finished symbol at the position spinning circle symbol used to be.
413    /// And print "\n" to jump to next line.
414    ///
415    /// e.g.
416    /// * Collection kitties
417    pub fn jobs_done(& self) {
418        self._show_finished();
419    }
420}
421
422impl SpinningCircle {
423    fn _print_symbol_and_texts(& self, symbol: &char) {
424        io::stdout().flush().unwrap();
425        print!("\r");
426        print!("{}", symbol);
427        // TODO How to handle extra text?
428        print!(" {:<81}", self._job_title);
429    }
430
431    fn _show_progress(& self) {
432        let circle_symbol: &char = self._circle_symbols.get(
433            self._tick_count % self._circle_symbols.len()).unwrap();
434
435        self._print_symbol_and_texts(circle_symbol);
436    }
437
438    fn _show_finished(& self) {
439        self._print_symbol_and_texts(&self._finished_symbol);
440        print!("\n");
441    }
442}