1use crate::tty::{terminal_size, Width};
2use std::io::Stdout;
3use std::io::{self, Write};
4use std::time::{Duration, Instant};
5
6macro_rules! kb_fmt {
7 ($n: ident) => {{
8 let kb = 1024f64;
9 match $n {
10 $n if $n >= kb.powf(4_f64) => format!("{:.*} TB", 2, $n / kb.powf(4_f64)),
11 $n if $n >= kb.powf(3_f64) => format!("{:.*} GB", 2, $n / kb.powf(3_f64)),
12 $n if $n >= kb.powf(2_f64) => format!("{:.*} MB", 2, $n / kb.powf(2_f64)),
13 $n if $n >= kb => format!("{:.*} KB", 2, $n / kb),
14 _ => format!("{:.*} B", 0, $n),
15 }
16 }};
17}
18
19const FORMAT: &str = "[=>-]";
20const TICK_FORMAT: &str = "\\|/-";
21
22#[derive(Debug)]
25pub enum Units {
26 Default,
27 Bytes,
28}
29
30pub struct ProgressBar<T: Write> {
31 start_time: Instant,
32 units: Units,
33 pub total: u64,
34 current: u64,
35 bar_start: String,
36 bar_current: String,
37 bar_current_n: String,
38 bar_remain: String,
39 bar_end: String,
40 tick: Vec<String>,
41 tick_state: usize,
42 width: Option<usize>,
43 message: String,
44 last_refresh_time: Instant,
45 max_refresh_rate: Option<Duration>,
46 pub is_finish: bool,
47 pub is_multibar: bool,
48 pub show_bar: bool,
49 pub show_speed: bool,
50 pub show_percent: bool,
51 pub show_counter: bool,
52 pub show_time_left: bool,
53 pub show_tick: bool,
54 pub show_message: bool,
55 handle: T,
56}
57
58impl ProgressBar<Stdout> {
59 pub fn new(total: u64) -> ProgressBar<Stdout> {
77 let handle = ::std::io::stdout();
78 ProgressBar::on(handle, total)
79 }
80}
81
82impl<T: Write> ProgressBar<T> {
83 pub fn on(handle: T, total: u64) -> ProgressBar<T> {
103 let mut pb = ProgressBar {
104 total,
105 current: 0,
106 start_time: Instant::now(),
107 units: Units::Default,
108 is_finish: false,
109 is_multibar: false,
110 show_bar: true,
111 show_speed: true,
112 show_percent: true,
113 show_counter: true,
114 show_time_left: true,
115 show_tick: false,
116 show_message: true,
117 bar_start: String::new(),
118 bar_current: String::new(),
119 bar_current_n: String::new(),
120 bar_remain: String::new(),
121 bar_end: String::new(),
122 tick: Vec::new(),
123 tick_state: 0,
124 width: None,
125 message: String::new(),
126 last_refresh_time: Instant::now(),
127 max_refresh_rate: None,
128 handle,
129 };
130 pb.format(FORMAT);
131 pb.tick_format(TICK_FORMAT);
132 pb
133 }
134
135 pub fn set_units(&mut self, u: Units) {
147 self.units = u;
148 }
149
150 pub fn format(&mut self, fmt: &str) {
159 if fmt.len() >= 5 {
160 let v: Vec<&str> = fmt.split("").collect();
161 self.bar_start = v[1].to_owned();
162 self.bar_current = v[2].to_owned();
163 self.bar_current_n = v[3].to_owned();
164 self.bar_remain = v[4].to_owned();
165 self.bar_end = v[5].to_owned();
166 }
167 }
168
169 pub fn message(&mut self, message: &str) {
189 self.message = message.replace(['\n', '\r'], " ")
190 }
191
192 pub fn tick_format(&mut self, tick_fmt: &str) {
205 if tick_fmt != TICK_FORMAT {
206 self.show_tick = true;
207 }
208 self.tick = tick_fmt
209 .split("")
210 .map(|x| x.to_owned())
211 .filter(|x| !x.is_empty())
212 .collect();
213 }
214
215 pub fn set_width(&mut self, w: Option<usize>) {
224 self.width = w;
225 }
226
227 pub fn set_max_refresh_rate(&mut self, w: Option<Duration>) {
236 self.max_refresh_rate = w;
237 if let Some(dur) = self.max_refresh_rate {
238 self.last_refresh_time = self.last_refresh_time - dur;
239 }
240 }
241
242 pub fn tick(&mut self) {
261 self.tick_state = (self.tick_state + 1) % self.tick.len();
262 if self.current <= self.total {
263 self.draw()
264 }
265 }
266
267 pub fn add(&mut self, i: u64) -> u64 {
279 self.current += i;
280 self.tick();
281 self.current
282 }
283
284 pub fn set(&mut self, i: u64) -> u64 {
294 self.current = i;
295 self.tick();
296 self.current
297 }
298
299 pub fn inc(&mut self) -> u64 {
301 self.add(1)
302 }
303
304 pub fn reset_start_time(&mut self) {
306 self.start_time = Instant::now();
307 }
308
309 fn draw(&mut self) {
310 let now = Instant::now();
311 if let Some(mrr) = self.max_refresh_rate {
312 if now - self.last_refresh_time < mrr && self.current < self.total {
313 return;
314 }
315 }
316
317 let mut time_elapsed = now - self.start_time;
318 if time_elapsed.is_zero() {
319 time_elapsed = Duration::from_nanos(1);
320 }
321 let speed = self.current as f64 / time_elapsed.as_secs_f64();
322 let width = self.width();
323
324 let mut out;
325 let mut parts = Vec::new();
326 let mut base = String::new();
327 let mut prefix = String::new();
328 let mut suffix = String::from(" ");
329
330 if self.show_percent {
332 let percent = self.current as f64 / (self.total as f64 / 100f64);
333 parts.push(format!(
334 "{:.*} %",
335 2,
336 if percent.is_nan() { 0.0 } else { percent }
337 ));
338 }
339 if self.show_speed {
341 match self.units {
342 Units::Default => parts.push(format!("{:.*}/s", 2, speed)),
343 Units::Bytes => parts.push(format!("{}/s", kb_fmt!(speed))),
344 };
345 }
346 if self.show_time_left && self.current > 0 && self.total > self.current {
348 let left = 1. / speed * (self.total - self.current) as f64;
349 if left < 60. {
350 parts.push(format!("{:.0}s", left));
351 } else {
352 parts.push(format!("{:.0}m", left / 60.));
353 };
354 }
355 suffix += &parts.join(" ");
356 if self.show_message {
358 prefix = prefix + &self.message;
359 }
360 if self.show_counter {
362 let (c, t) = (self.current as f64, self.total as f64);
363 prefix = prefix
364 + &match self.units {
365 Units::Default => format!("{} / {} ", c, t),
366 Units::Bytes => format!("{} / {} ", kb_fmt!(c), kb_fmt!(t)),
367 };
368 }
369 if self.show_tick {
371 prefix = prefix + &format!("{} ", self.tick[self.tick_state]);
372 }
373 if self.show_bar {
375 let p = prefix.chars().count() + suffix.chars().count() + 3;
376 if p < width {
377 let size = width - p;
378 let curr_count =
379 ((self.current as f64 / self.total as f64) * size as f64).ceil() as usize;
380 if size >= curr_count {
381 let rema_count = size - curr_count;
382 base = self.bar_start.clone();
383 if rema_count > 0 && curr_count > 0 {
384 base =
385 base + &self.bar_current.repeat(curr_count - 1) + &self.bar_current_n;
386 } else {
387 base = base + &self.bar_current.repeat(curr_count);
388 }
389 base = base + &self.bar_remain.repeat(rema_count) + &self.bar_end;
390 }
391 }
392 }
393 out = prefix + &base + &suffix;
394 if out.len() < width {
396 let gap = width - out.len();
397 out = out + &" ".repeat(gap);
398 }
399 printfl!(self.handle, "\r{}", out);
401
402 self.last_refresh_time = Instant::now();
403 }
404
405 fn finish_draw(&mut self) {
408 let mut redraw = false;
409
410 if let Some(mrr) = self.max_refresh_rate {
411 if Instant::now() - self.last_refresh_time < mrr {
412 self.max_refresh_rate = None;
413 redraw = true;
414 }
415 }
416
417 if self.current < self.total {
418 self.current = self.total;
419 redraw = true;
420 }
421
422 if redraw {
423 self.draw();
424 }
425 self.is_finish = true;
426 }
427
428 pub fn finish(&mut self) {
431 self.finish_draw();
432 self.handle.write(b"").expect("write() failed");
433 }
434
435 pub fn finish_print(&mut self, s: &str) {
437 self.finish_draw();
438 let width = self.width();
439 let mut out = s.to_owned();
440 if s.len() < width {
441 out += &" ".repeat(width - s.len());
442 };
443 printfl!(self.handle, "\r{}", out);
444 self.finish();
445 }
446
447 pub fn finish_println(&mut self, s: &str) {
452 if self.is_multibar {
455 return self.finish_print(s);
456 }
457 self.finish_draw();
458 printfl!(self.handle, "\n{}", s);
459 }
460
461 fn width(&mut self) -> usize {
463 if let Some(w) = self.width {
464 w
465 } else if let Some((Width(w), _)) = terminal_size() {
466 w as usize
467 } else {
468 80
469 }
470 }
471}
472
473impl<T: Write> Write for ProgressBar<T> {
475 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
476 let n = buf.len();
477 self.add(n as u64);
478 Ok(n)
479 }
480 fn flush(&mut self) -> io::Result<()> {
481 Ok(())
482 }
483}
484
485#[cfg(test)]
486mod test {
487 use crate::{ProgressBar, Units};
488 use std::time::Duration;
489
490 #[test]
491 fn add() {
492 let mut pb = ProgressBar::new(10);
493 pb.add(2);
494 assert!(pb.current == 2, "should add the given `n` to current");
495 assert!(
496 pb.add(2) == pb.current,
497 "add should return the current value"
498 );
499 }
500
501 #[test]
502 fn inc() {
503 let mut pb = ProgressBar::new(10);
504 pb.inc();
505 assert!(pb.current == 1, "should increment current by 1");
506 }
507
508 #[test]
509 fn format() {
510 let fmt = "[~> ]";
511 let mut pb = ProgressBar::new(1);
512 pb.format(fmt);
513 assert!(
514 pb.bar_start + &pb.bar_current + &pb.bar_current_n + &pb.bar_remain + &pb.bar_end
515 == fmt
516 );
517 }
518
519 #[test]
520 fn finish() {
521 let mut pb = ProgressBar::new(10);
522 pb.finish();
523 assert!(pb.current == pb.total, "should set current to total");
524 assert!(pb.is_finish, "should set is_finish to true");
525 }
526
527 #[test]
528 fn kb_fmt() {
529 let kb = 1024f64;
530 let mb = kb.powf(2f64);
531 let gb = kb.powf(3f64);
532 let tb = kb.powf(4f64);
533 assert_eq!(kb_fmt!(kb), "1.00 KB");
534 assert_eq!(kb_fmt!(mb), "1.00 MB");
535 assert_eq!(kb_fmt!(gb), "1.00 GB");
536 assert_eq!(kb_fmt!(tb), "1.00 TB");
537 }
538
539 #[test]
540 fn disable_speed_percent() {
541 let mut out = Vec::new();
542 let mut pb = ProgressBar::on(&mut out, 10);
543 pb.show_speed = false;
544 pb.show_percent = false;
545 pb.set_width(Some(80));
546 pb.add(2);
547 assert_eq!(
548 std::str::from_utf8(&out).unwrap(),
549 "\r2 / 10 [=============>-----------------------------------------------------] 0s ",
550 );
551 }
552
553 #[test]
554 fn disable_speed_time_left() {
555 let mut out = Vec::new();
556 let mut pb = ProgressBar::on(&mut out, 10);
557 pb.show_speed = false;
558 pb.show_time_left = false;
559 pb.set_width(Some(65));
560 pb.add(1);
561 assert_eq!(
562 std::str::from_utf8(&out).unwrap(),
563 "\r1 / 10 [====>------------------------------------------] 10.00 % ",
564 );
565 }
566
567 #[test]
568 fn disable_percent_time_left() {
569 let mut out = Vec::new();
570 let mut pb = ProgressBar::on(&mut out, 10);
571 pb.show_percent = false;
572 pb.show_time_left = false;
573 pb.set_units(Units::Bytes);
574 pb.set_width(Some(65));
575 pb.draw();
576 assert_eq!(
577 std::str::from_utf8(&out).unwrap(),
578 "\r0 B / 10 B [---------------------------------------------] 0 B/s ",
579 );
580 }
581
582 #[test]
583 fn disable_suffix() {
584 let mut out = Vec::new();
585 let mut pb = ProgressBar::on(&mut out, 10);
586 pb.show_speed = false;
587 pb.show_percent = false;
588 pb.show_time_left = false;
589 pb.set_units(Units::Bytes);
590 pb.set_width(Some(65));
591 pb.draw();
592 assert_eq!(
593 std::str::from_utf8(&out).unwrap(),
594 "\r0 B / 10 B [--------------------------------------------------] ",
595 );
596 }
597
598 #[test]
599 fn max_refresh_rate_finish() {
600 let count = 500;
601 let mut out = Vec::new();
602 let mut pb = ProgressBar::on(&mut out, count);
603 pb.format("╢▌▌░╟");
604 pb.set_width(Some(80));
605 pb.set_max_refresh_rate(Some(Duration::from_millis(100)));
606 pb.show_speed = false;
607 pb.show_time_left = false;
608 pb.add(count / 2);
609 pb.add(count / 2);
610 let mut split = std::str::from_utf8(&out)
611 .unwrap()
612 .trim_start_matches('\r')
613 .split('\r');
614 assert_eq!(
615 split.next(),
616 Some("250 / 500 ╢▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌░░░░░░░░░░░░░░░░░░░░░░░░░░░░░╟ 50.00 %")
617 );
618 assert_eq!(
619 split.next(),
620 Some("500 / 500 ╢▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌╟ 100.00 %")
621 );
622 }
623}