prodash/unit/
traits.rs

1use std::{fmt, hash::Hasher};
2
3use crate::{progress::Step, unit::display};
4
5/// A trait to encapsulate all capabilities needed to display a value with unit within a renderer.
6pub trait DisplayValue {
7    /// Display the absolute `value` representing the current progress of an operation and write it to `w`.
8    ///
9    /// The `upper` bound is possibly provided when known to add context, even though it is not to be output
10    /// as part of this method call.
11    fn display_current_value(&self, w: &mut dyn fmt::Write, value: Step, _upper: Option<Step>) -> fmt::Result {
12        fmt::write(w, format_args!("{}", value))
13    }
14    /// Emit a token to separate two values.
15    ///
16    /// The `value` and its `upper` bound are provided to add context, even though it is not to be output
17    /// as part of this method call.
18    fn separator(&self, w: &mut dyn fmt::Write, _value: Step, _upper: Option<Step>) -> fmt::Result {
19        w.write_str("/")
20    }
21
22    /// Emit the `upper_bound` to `w`.
23    ///
24    /// The `value` is provided to add context, even though it is not to be output as part of this method call.
25    fn display_upper_bound(&self, w: &mut dyn fmt::Write, upper_bound: Step, _value: Step) -> fmt::Result {
26        fmt::write(w, format_args!("{}", upper_bound))
27    }
28
29    /// A way to hash our state without using generics.
30    ///
31    /// This helps to determine quickly if something changed.
32    fn dyn_hash(&self, state: &mut dyn std::hash::Hasher);
33
34    /// Emit the unit of `value` to `w`.
35    ///
36    /// The `value` is provided to add context, even though it is not to be output as part of this method call.
37    fn display_unit(&self, w: &mut dyn fmt::Write, value: Step) -> fmt::Result;
38
39    /// Emit `percentage` to `w`.
40    fn display_percentage(&self, w: &mut dyn fmt::Write, percentage: f64) -> fmt::Result {
41        w.write_fmt(format_args!("[{}%]", percentage as usize))
42    }
43
44    /// Emit the `throughput` of an operation to `w`.
45    fn display_throughput(&self, w: &mut dyn fmt::Write, throughput: &display::Throughput) -> fmt::Result {
46        let (fraction, unit) = self.fraction_and_time_unit(throughput.timespan);
47        w.write_char('|')?;
48        self.display_current_value(w, throughput.value_change_in_timespan, None)?;
49        w.write_char('/')?;
50        match fraction {
51            Some(fraction) => w.write_fmt(format_args!("{}", fraction)),
52            None => Ok(()),
53        }?;
54        w.write_fmt(format_args!("{}|", unit))
55    }
56
57    /// Given a `timespan`, return a fraction of the timespan based on the given unit, i.e. `(possible fraction, unit`).
58    fn fraction_and_time_unit(&self, timespan: std::time::Duration) -> (Option<f64>, &'static str) {
59        fn skip_one(v: f64) -> Option<f64> {
60            if (v - 1.0).abs() < f64::EPSILON {
61                None
62            } else {
63                Some(v)
64            }
65        }
66        const HOUR_IN_SECS: u64 = 60 * 60;
67        let secs = timespan.as_secs();
68        let h = secs / HOUR_IN_SECS;
69        if h > 0 {
70            return (skip_one(secs as f64 / HOUR_IN_SECS as f64), "h");
71        }
72        const MINUTES_IN_SECS: u64 = 60;
73        let m = secs / MINUTES_IN_SECS;
74        if m > 0 {
75            return (skip_one(secs as f64 / MINUTES_IN_SECS as f64), "m");
76        }
77        if secs > 0 {
78            return (skip_one(secs as f64), "s");
79        }
80
81        (skip_one(timespan.as_millis() as f64), "ms")
82    }
83}
84
85impl DisplayValue for &'static str {
86    fn dyn_hash(&self, state: &mut dyn Hasher) {
87        state.write(self.as_bytes())
88    }
89
90    fn display_unit(&self, w: &mut dyn fmt::Write, _value: usize) -> fmt::Result {
91        w.write_fmt(format_args!("{}", self))
92    }
93}