1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
//! prgrs is a simple progress bar for rust, that aims to work like the python package [tqdm](https://github.com/tqdm/tqdm). //! # Example //! ``` //! use prgrs::{Prgrs, writeln, Length}; //! use std::{thread, time}; //! //! fn main() { //! for i in Prgrs::new(0..1000, 1000).set_length_move(Length::Proportional(0.5)){ //! thread::sleep(time::Duration::from_millis(10)); //! if i % 10 == 0{ //! let str = format!("{}", i); //! writeln(&str).ok(); //! } //! } //! } //! ``` //! //! The output will look something like this: //! //! `[############## ] ( 42%)` //! use std::io::{self, Error, ErrorKind, Write}; use terminal_size::{terminal_size, Height, Width}; pub struct Prgrs<T: Iterator> { iter: T, size: usize, curr: usize, len: Length, } /// Use this struct to [set the length](struct.Prgrs.html#method.set_length) of the progress bar. /// The lengths include the percentage count and parentheses at the end of the bar. /// # Proportional (better use this whenever possible) /// When using the Proportional variant values below 0. are rounded to 0. and values above 1. are rounded to 1. /// /// A value of 0. means the progress bar will have a single step. /// /// A value of 1. will make the progress bar fit the entire width of the screen. /// /// The proportions are calculated for each iteration, so you could change the size of the terminal while displaying a progress bar, but this still may lead to smaller glitches. /// # Absolute (use with care) /// **Careful:** Values that are larger than the terminal will NOT be handled in a special manner, which will probably lead to glitches. /// /// Values, that would make make the bar smaller than a single step however like negative values or for example 2 are ignored and the bar will have a single step. pub enum Length { /// Used to set the absolute length of the progress bar in characters Absolute(usize), /// Used to set the length proportional to the width of your terminal /// /// The supplied value should be between 0 and 1 Proportional(f64), } impl<T: Iterator> Prgrs<T> { /// Creates a new Prgrs struct. /// /// You have to specify the number of elements in the Iterator as the second argument /// # Example /// ``` /// use prgrs::Prgrs; /// for _ in Prgrs::new(0..100, 100){ /// // do something here ///} /// ``` pub fn new(it: T, size: usize) -> Self { Prgrs::<T> { iter: it, size, curr: 0, len: Length::Proportional(0.33), } } /// Set the length of the progress bar. The default is `Length::Proportional(0.33)` /// /// To set an absolute value use [`Length::Absolute(val)`](enum.Length.html#variant.Absolute) and to set a proportional value use [`Length::Proportional(val)`](enum.Length.html#variant.Proportional) /// # Examples /// ``` /// use prgrs::{Prgrs, Length}; /// let mut p = Prgrs::new(0..100, 100); /// p.set_length(Length::Proportional(0.5)); /// for _ in p{ /// // do something here ///} /// ``` /// ``` /// use prgrs::{Prgrs, Length}; /// let mut p = Prgrs::new(0..100, 100); /// p.set_length(Length::Absolute(40)); /// for _ in p{ /// // do something here ///} /// ``` pub fn set_length(&mut self, len: Length) { self.len = len; } /// Same as [set_length()](struct.Prgrs.html#method.set_length), but the Instance of Prgrs, on which it is called is moved out and returned afterwards, which is useful for a oneliner /// # Example /// ``` /// use prgrs::{Prgrs, Length}; /// for _ in Prgrs::new(0..100, 100).set_length_move(Length::Proportional(0.5)){ /// // do something here ///} /// ``` pub fn set_length_move(mut self, len: Length) -> Self { self.len = len; self } fn get_absolute_length(&self) -> usize { match self.len { Length::Absolute(l) => l, Length::Proportional(mut p) => { if let Some((Width(x), Height(_y))) = terminal_size() { if p > 1. { p = 1.; } else if p < 0. { p = 0.; } (x as f64 * p) as usize } else { 50 } } } } fn get_ratio(&self) -> f64 { self.curr as f64 / self.size as f64 } fn create_bar(&self) -> String { let symbol = "#"; let len = self.get_absolute_length(); let mut steps = 1; let additional_chars = "[] (100%)".len(); if len > additional_chars + 1 { steps = len - additional_chars; } let mut buf = String::from("["); if self.size == 0 { for _ in 0..steps { buf.push_str(symbol); } } else { let num_symbols = (self.get_ratio() * steps as f64) as usize; for _ in 0..num_symbols { buf.push_str(symbol); } for _ in 0..steps - num_symbols { buf.push_str(" "); } } buf.push_str("]"); buf } } impl<T: Iterator> Iterator for Prgrs<T> { type Item = T::Item; fn next(&mut self) -> Option<Self::Item> { let next = self.iter.next(); let mut percentage = self.get_ratio() * 100.; if percentage > 100. || percentage.is_nan() { percentage = 100.; } if let Some((Width(w), Height(_h))) = terminal_size() { let whitespaces = std::iter::repeat(" ").take(w as usize).collect::<String>(); print!( "\r{}\r{} ({:3.0}%)\r", whitespaces, self.create_bar(), percentage ); } else { print!("{} ({:3.0}%)\r", self.create_bar(), percentage); } io::stdout().flush().ok(); if let None = next { println!(""); } self.curr += 1; next } } /// Use this function to write to the terminal, while displaying a progress bar. /// /// It may return an error, when the size of the terminal couldn't be determined. /// ///In this case the supplied text will **NOT** be printed, so you may want to print it with `println!()` instead. /// # Example /// ``` /// use prgrs::{Prgrs, writeln}; /// for i in Prgrs::new(0..100, 100){ /// if let Err(_) = writeln("test") { /// println!("test") /// } /// } /// ``` pub fn writeln(text: &str) -> Result<(), Error> { if let Some((Width(w), Height(_h))) = terminal_size() { // The whitespaces override the rest of the line, because \r doesn't delete characters already printed let whitespaces = (w as usize).checked_sub(text.len()).unwrap_or(0); let whitespaces = std::iter::repeat(" ").take(whitespaces).collect::<String>(); println!("\r{}{}", text, whitespaces); Ok(()) } else { Err(Error::new( ErrorKind::Other, "Issue determining size of your terminal", )) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_prgrs() { assert_eq!(Prgrs::new(1..100, 100).next(), (1..100).next()); assert_eq!(Prgrs::new(1..100, 100).last(), (1..100).last()); assert_eq!(Prgrs::new(0..0, 0).next(), None); } }