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
//! prgrs is a progress bar for rust, that aims to work like the python package [tqdm](https://github.com/tqdm/tqdm).
use std::io::{self, Error, ErrorKind, Stdout, Write};
use terminal::{error, Action, Clear, Retrieved, Terminal, Value};
use terminal_size::{terminal_size, Height, Width};

pub struct Prgrs<T: Iterator> {
    iter: T,
    size: usize,
    curr: usize,
    len: Length,
    term: Terminal<Stdout>,
}

/// Use this struct to set the length of the progress debug_assert!
/// # Proportional (better use this when possible)
/// When using Proportional values below 0. are rounded to 0. and above 1. are rounded to 1.
///
/// A value of 0. means the progress bar will have a single step
///
/// A value of 1. will fill make the progress bar fit the entire width of the screen
/// # Absolute (use carful)
/// When using Absolute you specify the total length of the bar including the percentage count and parentheses
///
/// **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 steps
pub enum Length {
    Absolute(usize),
    Proportional(f32),
}

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),
            term: terminal::stdout(),
        }
    }

    /// Set the length of the progress bar, either as an absolute value or proportional to the size of the terminal
    ///
    /// By default the length is set to Length::Proportional(0.33)
    ///
    /// To set an absolute value please use Length::Absolute(val) and to set a proportional value use Length::Proportional(val)
    /// # Example
    /// ```
    /// 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
    ///}
    /// ```
    pub fn set_length(&mut self, len: Length) {
        self.len = len;
    }

    /// Same as 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 Ok(Retrieved::TerminalSize(x, _y)) = self.term.get(Value::TerminalSize) {
                    if p > 1. {
                        p = 1.;
                    }
                    if p < 0. {
                        p = 0.;
                    }
                    (x as f32 * p) as usize
                } else {
                    30
                }
            }
        }
    }

    fn create_bar(&self) -> String {
        let len = self.get_absolute_length();
        let mut steps = 1;
        if len > 10 {
            steps = len - 9; // 9 is length of all the other characters in the progress bar
        }
        let symbol = "#";
        let mut buf = String::new();
        buf.push_str("[");
        let ratio = self.curr as f32 / self.size as f32;
        let num_symbols = (ratio * steps as f32) as usize;
        for _ in 0..num_symbols {
            buf.push_str(symbol);
        }
        for _ in 0..steps - num_symbols {
            buf.push_str(" ");
        }
        buf.push_str("]");
        buf
    }

    fn print_bar(&mut self) -> error::Result<()> {
        if let Retrieved::CursorPosition(_x, y) = self.term.get(Value::CursorPosition)? {
            self.curr += 1;
            self.term.batch(Action::MoveCursorTo(0, y))?;
            self.term.act(Action::ClearTerminal(Clear::CurrentLine))?;
            let mut percentage = (self.curr as f32 / self.size as f32) * 100.;
            if percentage > 100. {
                percentage = 100.;
            }
            self.term
                .write(format!("{} ({:3.0}%)", self.create_bar(), percentage).as_bytes())?;
            self.term.flush_batch()?;
        }
        Ok(())
    }
}

impl<T: Iterator> Iterator for Prgrs<T> {
    type Item = T::Item;

    fn next(&mut self) -> std::option::Option<Self::Item> {
        let next = self.iter.next();
        match self.print_bar() {
            Err(_e) => {
                let mut percentage = (self.curr as f32 / self.size as f32) * 100.;
                if percentage > 100. {
                    percentage = 100.;
                }
                print!("{} ({:3.0}%)\r", self.create_bar(), percentage);
                match io::stdout().flush() {
                    Err(_) => (),
                    Ok(_) => (),
                }
            }
            Ok(_) => {}
        }

        match next {
            Some(n) => Some(n),
            None => {
                println!("");
                None
            }
        }
    }
}

/// Use this function to write to the terminal, while displaying a progress bar
///
/// # Example
/// ```
/// use prgrs::{Prgrs, writeln};
/// for i in Prgrs::new(0..100, 100){
///     match writeln("test") {
///         Ok(_)=>(),
///         Err(_) =>  println!("test")
///     }
///}
/// ```

fn get_n_whitespaces(n: usize) -> String {
    let mut buf = String::new();
    for _ in 0..n {
        buf.push_str(" ");
    }
    buf
}

pub fn writeln(text: &str) -> Result<(), Error> {
    let size = terminal_size();
    if let Some((Width(w), Height(_h))) = size {
        println!("\r{}{}", text, get_n_whitespaces(w as usize - text.len()));
        return Ok(());
    } else {
        return Err(Error::new(
            ErrorKind::Other,
            "Issue determining size of your terminal",
        ));
    }
}

#[cfg(test)]
mod tests {
    use crate::Prgrs;
    #[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());
    }
}