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