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
#![feature(drain_filter)]

extern crate termion;

use core::cell::RefCell;
use std::{rc::Rc, io::Write};


pub(crate) fn term_width() -> std::io::Result<u16> {
    use termion::terminal_size;
    Ok(terminal_size()?.0)
}

const FILLED: &str = "=";
const EMPTY: &str = "-";
const START: &str = "[";
const END: &str = "]";
const UNIT: &str = "%";

/**
manager for all current progress bars and text output.

when printing text, when text is printed it removes all (unfinished) 
bars, prints the text, and reprints all bars
*/
pub struct BarManager {
    bars: Vec<Rc<RefCell<Bar>>>,
    last_lines: usize,
}

impl BarManager {
    pub fn new() -> Self {
        Self {
            bars: vec![],
            last_lines: 0,
        }
    }

    pub fn new_bar(&mut self, name: String) -> BarWrapper {
        let bar = Rc::new(RefCell::new(Bar::new(name)));
        self.bars.push(bar.clone());
        bar.into()
    }

    pub fn display(&mut self, text: &str) -> String {
        let mut res = String::new();
        // ESC CSI n F (move to the start of the line n lines up)
        // (this is to overwrite previous bars)
        if self.last_lines != 0 {
            res += &format!("\x1b[{}F", self.last_lines);
        }
        // ESC CSI 0 J (clears from cursor to end of screen)
        res += "\x1b[0J";
        // print stuff
        res += text;
        // go through all bars, removing ones that are done
        let _ = self.bars.drain_filter(|b| {
            res += &b.borrow_mut().display();
            res += "\n";
            b.borrow().is_done()
        });
        self.last_lines = self.bars.len();
        res
    }

    pub fn queue_text(&mut self, text: &str) {
        std::print!("{}", self.display(text));
        std::io::stdout().flush().unwrap();
    }

    pub fn print(&mut self) {
        std::print!("{}", self.display(""));
        std::io::stdout().flush().unwrap();
    }
}

impl Default for BarManager {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone)]
pub struct BarWrapper (Rc<RefCell<Bar>>);

impl BarWrapper {
    pub fn set_precent(&mut self, precent: usize) {
        self.0.borrow_mut().set_precent(precent);
    }

    pub fn done(&mut self) {
        self.0.borrow_mut().done();
    }
}

impl From<Rc<RefCell<Bar>>> for BarWrapper {
    fn from(item: Rc<RefCell<Bar>>) -> Self {
        Self (item)
    }
}

impl Drop for BarWrapper {
    fn drop(&mut self) {
        if let Ok(mut b) = self.0.try_borrow_mut() {
            b.done();
        }
    }
}

pub struct Bar {
    job_name: String,
    precentage: usize,
    finished: bool,
}

impl Bar {
    pub fn new(name: String) -> Self {
        Self {
            job_name: name.chars().filter(|ch| {ch != &'\n' || ch != &'\r'}).collect(),
            precentage: 0,
            finished: false,
        }
    }

    pub fn done(&mut self) {
        self.finished = true;
    }

    pub fn is_done(&self) -> bool {
        self.finished
    }

    pub fn set_precent(&mut self, precent: usize) {
        self.precentage = precent;
    }

    /// Some implementation details:
    /// 
    /// starts with "\r" and has no end char
    /// 
    ///  if it cannot get the real term size, uses 81 as the size
    pub fn display(&self) -> String {
        //TODO make this not use default
        let width = term_width().unwrap_or(81) as i32;

        let mut res = String::with_capacity(width as usize /* starts out as a u16, so its fine */);

        let overhead = self.precentage / 100;
        let left_percentage = self.precentage - overhead * 100;
        let bar_len = width - (50 + 5) - 2;
        let bar_finished_len = ((bar_len as f32) *
                                (left_percentage as f32 / 100.0)) as i32;
        let filled_symbol = if overhead & 0b1 == 0 {
            FILLED
        } else {
            EMPTY
        };
        let empty_symbol = if overhead & 0b1 == 0 {
            EMPTY
        } else {
            FILLED
        };

        res += "\r";

        // pad to 50 chars on right
        res += &format!("{:<50}", self.job_name);
        res += START;
        for _ in 0..bar_finished_len {
            res += filled_symbol;
        }
        for _ in bar_finished_len..bar_len {
            res += empty_symbol;
        }
        res += END;

        //pad to 4 chars on left
        res += &format!("{:>4}", self.precentage);
        res += UNIT;

        res
    }
}

pub mod macros {
    #[macro_export]
    macro_rules! print {
        ($bm:ident, $($arg:tt)*) => ({
            $bm.queue_text(&format!($($arg)*));
        })
    }

    #[macro_export]
    macro_rules! println {
        ($bm: ident) => ($bm.queue_text("\n"));
        ($bm:ident, $($arg:tt)*) => ({
            $bm.queue_text(&format!("{}\n", format!($($arg)*)));
        })
    }
}