tycho_util/
progress_bar.rs

1pub 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;