gix_config_value/
integer.rs

1use std::{borrow::Cow, 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 last_idx = s.len() - 1;
71        if !s.is_char_boundary(last_idx) {
72            return Err(int_err(s));
73        }
74
75        let (number, suffix) = s.split_at(s.len() - 1);
76        if let (Ok(value), Ok(suffix)) = (number.parse(), suffix.parse()) {
77            Ok(Self {
78                value,
79                suffix: Some(suffix),
80            })
81        } else {
82            Err(int_err(s))
83        }
84    }
85}
86
87impl TryFrom<Cow<'_, BStr>> for Integer {
88    type Error = Error;
89
90    fn try_from(c: Cow<'_, BStr>) -> Result<Self, Self::Error> {
91        Self::try_from(c.as_ref())
92    }
93}
94
95/// Integer suffixes that are supported by `git-config`.
96///
97/// These values are base-2 unit of measurements, not the base-10 variants.
98#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
99#[allow(missing_docs)]
100pub enum Suffix {
101    Kibi,
102    Mebi,
103    Gibi,
104}
105
106impl Suffix {
107    /// Returns the number of bits that the suffix shifts left by.
108    #[must_use]
109    pub const fn bitwise_offset(self) -> usize {
110        match self {
111            Self::Kibi => 10,
112            Self::Mebi => 20,
113            Self::Gibi => 30,
114        }
115    }
116}
117
118impl Display for Suffix {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        match self {
121            Self::Kibi => write!(f, "k"),
122            Self::Mebi => write!(f, "m"),
123            Self::Gibi => write!(f, "g"),
124        }
125    }
126}
127
128#[cfg(feature = "serde")]
129impl serde::Serialize for Suffix {
130    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
131    where
132        S: serde::Serializer,
133    {
134        serializer.serialize_str(match self {
135            Self::Kibi => "k",
136            Self::Mebi => "m",
137            Self::Gibi => "g",
138        })
139    }
140}
141
142impl FromStr for Suffix {
143    type Err = ();
144
145    fn from_str(s: &str) -> Result<Self, Self::Err> {
146        match s {
147            "k" | "K" => Ok(Self::Kibi),
148            "m" | "M" => Ok(Self::Mebi),
149            "g" | "G" => Ok(Self::Gibi),
150            _ => Err(()),
151        }
152    }
153}
154
155impl TryFrom<&BStr> for Suffix {
156    type Error = ();
157
158    fn try_from(s: &BStr) -> Result<Self, Self::Error> {
159        Self::from_str(std::str::from_utf8(s).map_err(|_| ())?)
160    }
161}