tycho_util/
progress_bar.rs1pub struct ProgressBar {
2 percentage_step: u64,
3 current: u64,
4 total: Option<u64>,
5 exact_unit: Option<&'static str>,
6 mapper: Box<MapperFn>,
7 printer: Box<PrinterFn>,
8}
9
10type MapperFn = dyn Fn(u64) -> String + Send + 'static;
11type PrinterFn = dyn Fn(&dyn std::fmt::Display) + Send + 'static;
12
13impl ProgressBar {
14 pub fn builder() -> ProgressBarBuilder {
15 ProgressBarBuilder {
16 percentage_step: PERCENTAGE_STEP,
17 total: None,
18 exact_unit: None,
19 mapper: None,
20 }
21 }
22
23 pub fn set_total(&mut self, total: impl Into<u64>) {
24 self.total = Some(total.into());
25 }
26
27 pub fn set_progress(&mut self, current: impl Into<u64>) {
28 let old = self.compute_current_progress();
29 self.current = current.into();
30 let new = self.compute_current_progress();
31
32 if matches!(
33 (old, new),
34 (Some(old), Some(new)) if old / self.percentage_step != new / self.percentage_step
35 ) {
36 self.progress_message();
37 }
38 }
39
40 pub fn complete(&self) {
41 self.message("complete");
42 }
43
44 #[inline(always)]
45 fn progress_message(&self) {
46 let total = match self.total {
47 Some(total) if total > 0 => total,
48 _ => return,
49 };
50
51 let percent = self.current * 100 / total;
52 let current = (self.mapper)(self.current);
53 let total = (self.mapper)(total);
54
55 match self.exact_unit {
56 Some(exact_unit) => self.message(format_args!(
57 "{percent}% ({current} / {total} {exact_unit})",
58 )),
59 None => self.message(format_args!("{percent}%")),
60 }
61 }
62
63 #[inline(always)]
64 fn message(&self, text: impl std::fmt::Display) {
65 (self.printer)(&text);
66 }
67
68 fn compute_current_progress(&self) -> Option<u64> {
69 self.total
70 .filter(|&total| total > 0)
71 .map(|total| self.current * 100u64 / total)
72 }
73}
74
75pub struct ProgressBarBuilder {
76 percentage_step: u64,
77 total: Option<u64>,
78 exact_unit: Option<&'static str>,
79 mapper: Option<Box<dyn Fn(u64) -> String + Send + 'static>>,
80}
81
82impl ProgressBarBuilder {
83 pub fn with_mapper<F>(mut self, mapper: F) -> Self
84 where
85 F: Fn(u64) -> String + Send + 'static,
86 {
87 self.mapper = Some(Box::new(mapper));
88 self
89 }
90
91 pub fn percentage_step(mut self, step: u64) -> Self {
92 self.percentage_step = std::cmp::max(step, 1);
93 self
94 }
95
96 pub fn total(mut self, total: impl Into<u64>) -> Self {
97 self.total = Some(total.into());
98 self
99 }
100
101 pub fn exact_unit(mut self, unit: &'static str) -> Self {
102 self.exact_unit = Some(unit);
103 self
104 }
105
106 pub fn build<F>(self, printer: F) -> ProgressBar
107 where
108 F: Fn(&dyn std::fmt::Display) + Send + 'static,
109 {
110 let pg = ProgressBar {
111 percentage_step: self.percentage_step,
112 current: 0,
113 total: self.total,
114 exact_unit: self.exact_unit,
115 mapper: self.mapper.unwrap_or_else(|| Box::new(|x| x.to_string())),
116 printer: Box::new(printer),
117 };
118
119 if self.total.is_some() {
120 pg.progress_message();
121 } else {
122 pg.message("estimating total");
123 }
124
125 pg
126 }
127}
128
129const PERCENTAGE_STEP: u64 = 5;