1use std::boxed::Box;
4use std::cmp::min;
5use std::io;
6use std::io::prelude::*;
7use std::time::{Duration, Instant};
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub enum BarPosition {
19 Right,
22
23 Left,
25}
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum OutputStream {
30 StdOut,
32
33 StdErr,
35}
36
37impl OutputStream {
38 fn get(self) -> Box<dyn Write> {
39 use OutputStream::*;
40 match self {
41 StdOut => Box::new(io::stdout()),
42 StdErr => Box::new(io::stderr()),
43 }
44 }
45}
46
47#[derive(Clone, Debug, Eq, PartialEq)]
48struct ProgressConfig {
49 bar_position: BarPosition,
50 bar_width: usize,
51 display_width: Option<usize>,
52 extra_infos: String,
53 output_stream: OutputStream,
54 prefix: String,
55 refresh_delay: Duration,
56 shape_body: char,
57 shape_head: char,
58 shape_void: char,
59}
60
61impl Default for ProgressConfig {
62 fn default() -> Self {
63 Self {
64 bar_position: BarPosition::Left,
65 bar_width: 40,
66 display_width: None,
67 extra_infos: String::new(),
68 output_stream: OutputStream::StdOut,
69 prefix: String::new(),
70 refresh_delay: Duration::from_millis(200),
71 shape_body: '=',
72 shape_head: '>',
73 shape_void: ' ',
74 }
75 }
76}
77
78#[derive(Clone, Debug)]
106pub struct Progress {
107 config: ProgressConfig,
108 last_update_time: Option<Instant>,
109}
110
111impl<'a> Progress {
112 pub fn new() -> Self {
114 Self {
115 config: ProgressConfig::default(),
116 last_update_time: None,
117 }
118 }
119
120 pub fn set_extra_infos<S>(&mut self, extra_infos: S)
122 where
123 S: Into<String>,
124 {
125 self.config.extra_infos = extra_infos.into()
126 }
127
128 pub fn need_refresh(&self) -> bool {
130 if let Some(last_update_time) = self.last_update_time {
131 return last_update_time.elapsed() >= self.config.refresh_delay;
132 }
133 true
134 }
135
136 fn bar_shape(&self, progress: f32) -> (usize, usize, usize) {
137 let body_length = min(
138 self.config.bar_width + 1,
139 (progress * (self.config.bar_width + 1) as f32).round() as usize,
140 );
141 let mut void_length = (self.config.bar_width + 1) - body_length;
142 let mut head_length = 0;
143
144 if void_length > 0 {
145 void_length -= 1;
146 head_length += 1;
147 }
148
149 (body_length, void_length, head_length)
150 }
151
152 pub fn update(&mut self, progress: f32) -> io::Result<()> {
154 if !self.need_refresh() {
155 return Ok(());
156 }
157
158 self.last_update_time = Some(Instant::now());
159
160 let (body, void, head) = self.bar_shape(progress);
161 let body = self.config.shape_body.to_string().repeat(body);
162 let head = self.config.shape_head.to_string().repeat(head);
163 let void = self.config.shape_void.to_string().repeat(void);
164
165 let required_width =
167 self.config.bar_width + self.config.prefix.len() + self.config.extra_infos.len() + 13;
168 let display_width = self
169 .config
170 .display_width
171 .unwrap_or_else(|| term_size::dimensions_stdout().map(|(w, _)| w).unwrap_or(80));
172
173 let (prefix, padding) = {
174 if display_width >= required_width {
175 (
176 &self.config.prefix[..],
177 " ".repeat(display_width - required_width),
178 )
179 } else if self.config.prefix.len() >= required_width - display_width {
180 let prefix_len = self.config.prefix.len() - (required_width - display_width);
181 (&self.config.prefix[0..prefix_len], String::new())
182 } else {
183 ("", String::new())
184 }
185 };
186
187 let text = match self.config.bar_position {
188 BarPosition::Left => format!(
189 "\r{} {:>5.1}% [{}{}{}] {}{}",
190 prefix,
191 100. * progress,
192 body,
193 head,
194 void,
195 self.config.extra_infos,
196 padding
197 ),
198 BarPosition::Right => format!(
199 "\r{} {}{} [{}{}{}] {:>5.1}%",
200 prefix,
201 padding,
202 self.config.extra_infos,
203 body,
204 head,
205 void,
206 100. * progress
207 ),
208 };
209
210 let mut stream = self.config.output_stream.get();
212 stream.write_all(&text.as_bytes())?;
213 stream.flush()
214 }
215
216 pub fn finished(&mut self) -> io::Result<()> {
218 self.last_update_time = None;
219 self.update(1.0)?;
220 writeln!(&mut self.config.output_stream.get())
221 }
222}
223
224impl<'a> Default for Progress {
225 fn default() -> Self {
226 Self::new()
227 }
228}
229
230pub trait WithProgress: Sized {
239 fn get_progress(&mut self) -> &mut Progress;
240
241 fn with_progress(mut self, progress: Progress) -> Self {
262 *self.get_progress() = progress;
263 self
264 }
265
266 fn with_bar_position(mut self, bar_position: BarPosition) -> Self {
268 self.get_progress().config.bar_position = bar_position;
269 self
270 }
271
272 fn with_bar_width(mut self, bar_width: usize) -> Self {
274 self.get_progress().config.bar_width = bar_width;
275 self
276 }
277
278 fn with_display_width(mut self, display_width: usize) -> Self {
281 self.get_progress().config.display_width = Some(display_width);
282 self
283 }
284
285 fn with_extra_infos<S>(mut self, extra_infos: S) -> Self
287 where
288 S: Into<String>,
289 {
290 self.get_progress().config.extra_infos = extra_infos.into();
291 self
292 }
293
294 fn with_output_stream(mut self, output_stream: OutputStream) -> Self {
297 self.get_progress().config.output_stream = output_stream;
298 self
299 }
300
301 fn with_prefix<S>(mut self, prefix: S) -> Self
315 where
316 S: Into<String>,
317 {
318 self.get_progress().config.prefix = prefix.into();
319 self
320 }
321
322 fn with_refresh_delay(mut self, refresh_delay: Duration) -> Self {
324 self.get_progress().config.refresh_delay = refresh_delay;
325 self
326 }
327
328 fn with_shape_body(mut self, shape_body: char) -> Self {
330 self.get_progress().config.shape_body = shape_body;
331 self
332 }
333
334 fn with_shape_head(mut self, shape_head: char) -> Self {
336 self.get_progress().config.shape_head = shape_head;
337 self
338 }
339
340 fn with_shape_void(mut self, shape_void: char) -> Self {
342 self.get_progress().config.shape_void = shape_void;
343 self
344 }
345}
346
347impl WithProgress for Progress {
348 fn get_progress(&mut self) -> &mut Progress {
349 self
350 }
351}