gix_config_value/
integer.rs1use std::{borrow::Cow, fmt::Display, str::FromStr};
2
3use bstr::{BStr, BString, ByteSlice};
4use gix_error::{ErrorExt, Message, ResultExt, validation};
5
6use crate::Integer;
7
8impl Integer {
9 pub fn to_decimal(&self) -> Option<i64> {
15 match self.suffix {
16 None => Some(self.value),
17 Some(suffix) => match suffix {
18 Suffix::Kibi => self.value.checked_mul(1024),
19 Suffix::Mebi => self.value.checked_mul(1024 * 1024),
20 Suffix::Gibi => self.value.checked_mul(1024 * 1024 * 1024),
21 },
22 }
23 }
24}
25
26impl Display for Integer {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 write!(f, "{}", self.value)?;
29 if let Some(suffix) = self.suffix {
30 write!(f, "{suffix}")
31 } else {
32 Ok(())
33 }
34 }
35}
36
37#[cfg(feature = "serde")]
38impl serde::Serialize for Integer {
39 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
40 where
41 S: serde::Serializer,
42 {
43 if let Some(suffix) = self.suffix {
44 serializer.serialize_i64(self.value << suffix.bitwise_offset())
45 } else {
46 serializer.serialize_i64(self.value)
47 }
48 }
49}
50
51fn int_err(input: impl Into<BString>) -> Message {
52 validation("Integers needs to be positive or negative numbers which may have a suffix like 1k, 42, or 50G")
53 .with("input", gix_error::MetadataValue::Bytes(input.into()))
54}
55
56fn parse_like_git(input: &str) -> Option<i64> {
60 let (negative, rest) = match input.as_bytes().first() {
61 Some(b'+') => (false, &input[1..]),
62 Some(b'-') => (true, &input[1..]),
63 _ => (false, input),
64 };
65
66 let Some(prefixed) = rest.strip_prefix('0') else {
67 return input.parse().ok();
68 };
69 let (digits, radix) = if let Some(hexadecimal) = prefixed.strip_prefix(['x', 'X']) {
70 (hexadecimal, 16)
71 } else if let Some(binary) = prefixed.strip_prefix(['b', 'B']) {
72 (binary, 2)
73 } else if !prefixed.is_empty() {
74 (prefixed, 8)
75 } else {
76 return input.parse().ok();
77 };
78
79 if digits.starts_with('+') || digits.starts_with('-') {
80 return None;
81 }
82 let magnitude = i128::from_str_radix(digits, radix).ok()?;
83 i64::try_from(if negative { -magnitude } else { magnitude }).ok()
84}
85
86impl TryFrom<&BStr> for Integer {
87 type Error = gix_error::Exn<gix_error::Message>;
88
89 fn try_from(s: &BStr) -> Result<Self, Self::Error> {
90 let s = std::str::from_utf8(s).or_raise(|| int_err(s))?;
91 if let Some(value) = parse_like_git(s) {
92 return Ok(Self { value, suffix: None });
93 }
94
95 if s.len() <= 1 {
96 return Err(int_err(s).raise());
97 }
98
99 let last_idx = s.len() - 1;
100 if !s.is_char_boundary(last_idx) {
101 return Err(int_err(s).raise());
102 }
103
104 let (number, suffix) = s.split_at(s.len() - 1);
105 if let (Some(value), Ok(suffix)) = (parse_like_git(number), suffix.parse()) {
106 Ok(Self {
107 value,
108 suffix: Some(suffix),
109 })
110 } else {
111 Err(int_err(s).raise())
112 }
113 }
114}
115
116impl TryFrom<&str> for Integer {
117 type Error = gix_error::Exn<gix_error::Message>;
118
119 fn try_from(value: &str) -> Result<Self, Self::Error> {
120 Self::try_from(BStr::new(value))
121 }
122}
123
124impl TryFrom<Cow<'_, BStr>> for Integer {
125 type Error = gix_error::Exn<gix_error::Message>;
126
127 fn try_from(c: Cow<'_, BStr>) -> Result<Self, Self::Error> {
128 Self::try_from(c.as_ref())
129 }
130}
131
132impl TryFrom<BString> for Integer {
133 type Error = gix_error::Exn<gix_error::Message>;
134
135 fn try_from(value: BString) -> Result<Self, Self::Error> {
136 Self::try_from(BStr::new(&value))
137 }
138}
139
140#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
144pub enum Suffix {
145 Kibi,
147 Mebi,
149 Gibi,
151}
152
153impl Suffix {
154 #[must_use]
156 pub const fn bitwise_offset(self) -> usize {
157 match self {
158 Self::Kibi => 10,
159 Self::Mebi => 20,
160 Self::Gibi => 30,
161 }
162 }
163}
164
165impl Display for Suffix {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 match self {
168 Self::Kibi => write!(f, "k"),
169 Self::Mebi => write!(f, "m"),
170 Self::Gibi => write!(f, "g"),
171 }
172 }
173}
174
175#[cfg(feature = "serde")]
176impl serde::Serialize for Suffix {
177 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
178 where
179 S: serde::Serializer,
180 {
181 serializer.serialize_str(match self {
182 Self::Kibi => "k",
183 Self::Mebi => "m",
184 Self::Gibi => "g",
185 })
186 }
187}
188
189impl FromStr for Suffix {
190 type Err = ();
191
192 fn from_str(s: &str) -> Result<Self, Self::Err> {
193 Self::try_from(BStr::new(s))
194 }
195}
196
197impl TryFrom<&BStr> for Suffix {
198 type Error = ();
199
200 fn try_from(s: &BStr) -> Result<Self, Self::Error> {
201 match s.as_bytes() {
202 b"k" | b"K" => Ok(Self::Kibi),
203 b"m" | b"M" => Ok(Self::Mebi),
204 b"g" | b"G" => Ok(Self::Gibi),
205 _ => Err(()),
206 }
207 }
208}