1use std::iter::repeat;
2use std::io::{stdout, Write};
3use std::ops::{AddAssign, SubAssign};
4use terminal_size::{terminal_size, Height, Width};
5
6pub struct Progress {
11 current: usize,
12 total: usize,
13
14 caption: String,
15
16 width: Option<u16>,
17
18 started: bool,
19}
20
21impl Default for Progress {
22 fn default() -> Progress {
23 Progress::new("Progress", 0, 100)
24 }
25}
26
27impl Progress {
28 pub fn new<S: Into<String>>(caption: S, start: usize, end: usize) -> Self {
29 Progress {
30 current: start,
31 total: end,
32 caption: caption.into(),
33 width: None,
34 started: false,
35 }
36 }
37
38 pub fn new_with_width<S>(caption: S, start: usize, end: usize, width: u16) -> Self
39 where
40 S: Into<String>,
41 {
42 Progress {
43 current: start,
44 total: end,
45 caption: caption.into(),
46 width: Some(width),
47 started: false,
48 }
49 }
50
51 pub fn current(&self) -> usize {
61 self.current
62 }
63
64 pub fn total(&self) -> usize {
74 self.total
75 }
76
77 pub fn forward(&mut self, step: usize) -> &Self {
91 *self += step;
92 self
93 }
94
95 pub fn backward(&mut self, step: usize) -> &Self {
109 *self -= step;
110 self
111 }
112
113 pub fn increment(&mut self) -> &Self {
115 self.forward(1)
116 }
117
118 pub fn decrement(&mut self) -> &Self {
120 self.backward(1)
121 }
122
123 pub fn finished(&self) -> bool {
125 self.current >= self.total
126 }
127
128 pub fn process(&self) -> u8 {
130 (self.current * 100 / self.total) as u8
131 }
132
133 pub fn start(&mut self) -> &Self {
135 self.started = true;
136 self
137 }
138
139 pub fn caption(&self) -> &String {
141 &self.caption
142 }
143}
144
145impl AddAssign<usize> for Progress {
146 fn add_assign(&mut self, step: usize) {
147 let new = self.current + step;
148 if new > self.total {
149 self.current = self.total;
150 } else {
151 self.current += step;
152 }
153 print_bar(self);
154 }
155}
156
157impl SubAssign<usize> for Progress {
158 fn sub_assign(&mut self, step: usize) {
159 self.current = self.current.saturating_sub(step);
160 print_bar(self);
161 }
162}
163
164fn print_bar(p: &Progress) {
165 let p_info = format!(
166 "{current} / {total} ({process})",
167 current = p.current(),
168 total = p.total(),
169 process = p.process()
170 );
171 let caption = p.caption();
172
173 let terminal_width = p.width
174 .unwrap_or_else(|| terminal_size().unwrap_or((Width(79), Height(0))).0 .0); let bar_width = terminal_width as usize - p_info.len() - caption.len() - 3 - 2; let done_width = (bar_width * p.process() as usize) / 100;
182 let todo_width = bar_width - done_width;
183 let done_bar = repeat("#").take(done_width).collect::<String>();
184 let todo_bar = repeat("-").take(todo_width).collect::<String>();
185 let bar = format!("|{}{}|", done_bar, todo_bar);
186
187 print!(
188 "{caption}: {bar} {info}\r",
189 caption = caption,
190 bar = bar,
191 info = p_info
192 );
193 stdout().flush().unwrap();
194}