1use std::ffi::OsString;
19use std::path::PathBuf;
20
21use winnow::stream::BStr;
22
23use crate::error::BoxError;
24
25pub trait FromArg: Sized {
55 const CHOICES: &'static [&'static str] = &[];
59
60 fn from_arg(value: &BStr) -> Result<Self, BoxError>;
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub struct ChoiceError {
68 pub choices: &'static [&'static str],
70}
71
72impl std::fmt::Display for ChoiceError {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 write!(f, "expected one of {}", self.choices.join(", "))
75 }
76}
77
78impl std::error::Error for ChoiceError {}
79
80pub fn to_str(value: &BStr) -> Result<&str, std::str::Utf8Error> {
82 std::str::from_utf8(value)
83}
84
85impl FromArg for String {
86 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
87 Ok(to_str(value)?.to_owned())
88 }
89}
90
91impl FromArg for Vec<u8> {
92 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
93 Ok(value.to_vec())
94 }
95}
96
97impl FromArg for OsString {
98 #[cfg(unix)]
99 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
100 use std::os::unix::ffi::OsStrExt as _;
101 Ok(std::ffi::OsStr::from_bytes(value).to_owned())
102 }
103
104 #[cfg(not(unix))]
105 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
106 Ok(to_str(value)?.into())
107 }
108}
109
110impl FromArg for PathBuf {
111 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
112 OsString::from_arg(value).map(PathBuf::from)
113 }
114}
115
116macro_rules! from_str {
117 ($($ty:ty),* $(,)?) => {$(
118 impl FromArg for $ty {
119 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
120 Ok(to_str(value)?.parse::<$ty>()?)
121 }
122 }
123 )*};
124}
125
126from_str!(
127 bool,
128 char,
129 u8,
130 u16,
131 u32,
132 u64,
133 u128,
134 usize,
135 i8,
136 i16,
137 i32,
138 i64,
139 i128,
140 isize,
141 f32,
142 f64,
143 std::net::IpAddr,
144 std::net::Ipv4Addr,
145 std::net::Ipv6Addr,
146 std::net::SocketAddr,
147);
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
165pub struct Parsed<T>(pub T);
166
167impl<T> FromArg for Parsed<T>
168where
169 T: std::str::FromStr,
170 T::Err: Into<BoxError>,
171{
172 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
173 to_str(value)?.parse().map(Parsed).map_err(Into::into)
174 }
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
184pub struct Spanned<T> {
185 pub value: T,
187 pub offset: usize,
190 pub attached: bool,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
198pub struct CInt<T>(pub T);
199
200#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct CIntError(&'static str);
203
204impl std::fmt::Display for CIntError {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 f.write_str(self.0)
207 }
208}
209
210impl std::error::Error for CIntError {}
211
212fn c_digits(text: &str) -> Result<(bool, u32, &str), CIntError> {
214 let (negative, rest) = match text.strip_prefix('-') {
215 Some(rest) => (true, rest),
216 None => (false, text.strip_prefix('+').unwrap_or(text)),
217 };
218 let (radix, digits) =
219 if let Some(hex) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) {
220 (16, hex)
221 } else if rest.len() > 1 && rest.starts_with('0') {
222 (8, &rest[1..])
223 } else {
224 (10, rest)
225 };
226 if digits.is_empty() || !digits.chars().all(|c| c.is_digit(radix)) {
227 return Err(CIntError("expected a number: decimal, 0x hex or 0 octal"));
228 }
229 Ok((negative, radix, digits))
230}
231
232macro_rules! c_int {
233 ($($ty:ty),* $(,)?) => {$(
234 impl FromArg for CInt<$ty> {
235 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
236 let (negative, radix, digits) = c_digits(to_str(value)?)?;
237 let magnitude = u128::from_str_radix(digits, radix)?;
238 let n = if negative {
239 0i128.checked_sub_unsigned(magnitude).and_then(|n| <$ty>::try_from(n).ok())
240 } else {
241 <$ty>::try_from(magnitude).ok()
242 };
243 n.map(CInt).ok_or_else(|| CIntError("number out of range").into())
244 }
245 }
246 )*};
247}
248
249c_int!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
255pub struct KeyValue<K, V> {
256 pub key: K,
258 pub value: V,
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
264pub struct MissingEquals;
265
266impl std::fmt::Display for MissingEquals {
267 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268 f.write_str("expected `key=value`")
269 }
270}
271
272impl std::error::Error for MissingEquals {}
273
274impl<K: FromArg, V: FromArg> FromArg for KeyValue<K, V> {
275 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
276 let eq = value.iter().position(|&b| b == b'=').ok_or(MissingEquals)?;
277 Ok(KeyValue {
278 key: K::from_arg(BStr::new(&value[..eq]))?,
279 value: V::from_arg(BStr::new(&value[eq + 1..]))?,
280 })
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 fn c<T>(text: &str) -> Result<T, String>
289 where
290 CInt<T>: FromArg,
291 {
292 CInt::<T>::from_arg(BStr::new(text))
293 .map(|CInt(n)| n)
294 .map_err(|e| e.to_string())
295 }
296
297 #[test]
298 fn c_integers() {
299 assert_eq!(c::<u64>("0x400000"), Ok(0x40_0000));
300 assert_eq!(c::<u64>("0X1f"), Ok(31));
301 assert_eq!(c::<u64>("010"), Ok(8));
302 assert_eq!(c::<u64>("0"), Ok(0));
303 assert_eq!(c::<u64>("4096"), Ok(4096));
304 assert_eq!(c::<i64>("-0x10"), Ok(-16));
305 assert_eq!(c::<i8>("-128"), Ok(-128));
306 assert!(c::<u8>("256").is_err());
307 assert!(c::<u64>("-1").is_err());
308 assert!(c::<u64>("08").is_err());
309 assert!(c::<u64>("0x").is_err());
310 assert!(c::<u64>("").is_err());
311 }
312
313 #[test]
314 fn key_values() {
315 let kv = KeyValue::<String, String>::from_arg(BStr::new("sym=a=b")).unwrap();
316 assert_eq!((kv.key.as_str(), kv.value.as_str()), ("sym", "a=b"));
317 let kv = KeyValue::<String, CInt<u64>>::from_arg(BStr::new(".text=0x1000")).unwrap();
318 assert_eq!((kv.key.as_str(), kv.value.0), (".text", 0x1000));
319 assert!(KeyValue::<String, String>::from_arg(BStr::new("nothing")).is_err());
320 }
321}