1use std::fmt::{self, Debug, Formatter};
2use std::str::FromStr;
3
4use anyhow::anyhow;
5use derive_more::*;
6
7macro_rules! percentage {
8 ($name:ident, $range:pat, $repr:ty) => {
9 #[derive(
10 Clone,
11 Copy,
12 PartialEq,
13 Eq,
14 PartialOrd,
15 Ord,
16 Display,
17 Binary,
18 Octal,
19 LowerHex,
20 UpperHex,
21 LowerExp,
22 UpperExp,
23 )]
24 pub struct $name($repr);
25
26 impl $name {
27 pub fn new(percent: $repr) -> anyhow::Result<Self> {
28 percent.try_into()
29 }
30
31 pub fn value(self) -> $repr {
32 self.into()
33 }
34
35 pub fn to_fraction(self) -> f32 {
36 self.value() as f32 / 100_f32
37 }
38 }
39
40 impl TryFrom<$repr> for $name {
41 type Error = anyhow::Error;
42
43 fn try_from(value: $repr) -> anyhow::Result<Self> {
44 if let $range = value {
45 Ok($name(value))
46 } else {
47 Err(anyhow!("invalid percentage"))
48 }
49 }
50 }
51
52 impl FromStr for $name {
53 type Err = anyhow::Error;
54
55 fn from_str(s: &str) -> anyhow::Result<Self> {
56 s.parse::<$repr>()?.try_into()
57 }
58 }
59
60 impl Debug for $name {
61 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
62 write!(f, "{}%", self.value())
63 }
64 }
65
66 impl From<$name> for $repr {
67 fn from($name(percent): $name) -> Self {
68 percent
69 }
70 }
71 };
72}
73
74percentage!(Percentage, 0..=100, u8);
75percentage!(SignedPercentage, -100..=100, i8);