git_config_value/
integer.rs

1use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr};
2
3use bstr::{BStr, BString};
4
5use crate::{Error, Integer};
6
7impl Integer {
8    /// Canonicalize values as simple decimal numbers.
9    /// An optional suffix of k, m, or g (case-insensitive), will cause the
10    /// value to be multiplied by 1024 (k), 1048576 (m), or 1073741824 (g) respectively.
11    ///
12    /// Returns the result if there is no multiplication overflow.
13    pub fn to_decimal(&self) -> Option<i64> {
14        match self.suffix {
15            None => Some(self.value),
16            Some(suffix) => match suffix {
17                Suffix::Kibi => self.value.checked_mul(1024),
18                Suffix::Mebi => self.value.checked_mul(1024 * 1024),
19                Suffix::Gibi => self.value.checked_mul(1024 * 1024 * 1024),
20            },
21        }
22    }
23}
24
25impl Display for Integer {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        write!(f, "{}", self.value)?;
28        if let Some(suffix) = self.suffix {
29            write!(f, "{suffix}")
30        } else {
31            Ok(())
32        }
33    }
34}
35
36#[cfg(feature = "serde")]
37impl serde::Serialize for Integer {
38    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
39    where
40        S: serde::Serializer,
41    {
42        if let Some(suffix) = self.suffix {
43            serializer.serialize_i64(self.value << suffix.bitwise_offset())
44        } else {
45            serializer.serialize_i64(self.value)
46        }
47    }
48}
49
50fn int_err(input: impl Into<BString>) -> Error {
51    Error::new(
52        "Integers needs to be positive or negative numbers which may have a suffix like 1k, 42, or 50G",
53        input,
54    )
55}
56
57impl TryFrom<&BStr> for Integer {
58    type Error = Error;
59
60    fn try_from(s: &BStr) -> Result<Self, Self::Error> {
61        let s = std::str::from_utf8(s).map_err(|err| int_err(s).with_err(err))?;
62        if let Ok(value) = s.parse() {
63            return Ok(Self { value, suffix: None });
64        }
65
66        if s.len() <= 1 {
67            return Err(int_err(s));
68        }
69
70        let (number, suffix) = s.split_at(s.len() - 1);
71        if let (Ok(value), Ok(suffix)) = (number.parse(), suffix.parse()) {
72            Ok(Self {
73                value,
74                suffix: Some(suffix),
75            })
76        } else {
77            Err(int_err(s))
78        }
79    }
80}
81
82impl TryFrom<Cow<'_, BStr>> for Integer {
83    type Error = Error;
84
85    fn try_from(c: Cow<'_, BStr>) -> Result<Self, Self::Error> {
86        Self::try_from(c.as_ref())
87    }
88}
89
90/// Integer suffixes that are supported by `git-config`.
91///
92/// These values are base-2 unit of measurements, not the base-10 variants.
93#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
94#[allow(missing_docs)]
95pub enum Suffix {
96    Kibi,
97    Mebi,
98    Gibi,
99}
100
101impl Suffix {
102    /// Returns the number of bits that the suffix shifts left by.
103    #[must_use]
104    pub const fn bitwise_offset(self) -> usize {
105        match self {
106            Self::Kibi => 10,
107            Self::Mebi => 20,
108            Self::Gibi => 30,
109        }
110    }
111}
112
113impl Display for Suffix {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        match self {
116            Self::Kibi => write!(f, "k"),
117            Self::Mebi => write!(f, "m"),
118            Self::Gibi => write!(f, "g"),
119        }
120    }
121}
122
123#[cfg(feature = "serde")]
124impl serde::Serialize for Suffix {
125    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
126    where
127        S: serde::Serializer,
128    {
129        serializer.serialize_str(match self {
130            Self::Kibi => "k",
131            Self::Mebi => "m",
132            Self::Gibi => "g",
133        })
134    }
135}
136
137impl FromStr for Suffix {
138    type Err = ();
139
140    fn from_str(s: &str) -> Result<Self, Self::Err> {
141        match s {
142            "k" | "K" => Ok(Self::Kibi),
143            "m" | "M" => Ok(Self::Mebi),
144            "g" | "G" => Ok(Self::Gibi),
145            _ => Err(()),
146        }
147    }
148}
149
150impl TryFrom<&BStr> for Suffix {
151    type Error = ();
152
153    fn try_from(s: &BStr) -> Result<Self, Self::Error> {
154        Self::from_str(std::str::from_utf8(s).map_err(|_| ())?)
155    }
156}