Skip to main content

winnow_args/
value.rs

1//! Turning a value's bytes into a Rust type.
2//!
3//! A field's type says how its value is read, through [`FromArg`]:
4//!
5//! | Type | Reads |
6//! |---|---|
7//! | `String`, `char`, `bool`, integers, floats, `std::net` addresses | UTF-8 text, parsed with `str::parse` |
8//! | `PathBuf`, `OsString` | the bytes as they are on Unix, UTF-8 elsewhere |
9//! | `Vec<u8>` | the bytes as they are |
10//! | an enum deriving `ValueEnum` | one of its variants' names |
11//! | [`Parsed<T>`] | any `T: FromStr`, a type from another crate included |
12//! | [`CInt<T>`] | an integer in C syntax: `0x400000`, `010000` |
13//! | [`KeyValue<K, V>`] | `key=value`, split at the first `=` |
14//!
15//! Any other type implements [`FromArg`] itself. In an `Occurrence` variant,
16//! [`Spanned<T>`] is a `T` with where it was found.
17
18use std::ffi::OsString;
19use std::path::PathBuf;
20
21use winnow::stream::BStr;
22
23use crate::error::BoxError;
24
25/// A type a flag's value can be converted into.
26///
27/// Values arrive as bytes borrowed from the command line. Conversion is the
28/// only place a value can fail, so a command line that is not valid UTF-8 still
29/// parses — flags still match — and only a value that is actually converted to
30/// text can be rejected for it.
31///
32/// For a type that is [`FromStr`](std::str::FromStr), [`Parsed<T>`] needs no
33/// impl. Otherwise:
34///
35/// ```
36/// use winnow_args::error::BoxError;
37/// use winnow_args::{BStr, FromArg};
38///
39/// /// `WIDTHxHEIGHT`.
40/// struct Size(u32, u32);
41///
42/// impl FromArg for Size {
43///     fn from_arg(value: &BStr) -> Result<Self, BoxError> {
44///         let (w, h) = std::str::from_utf8(value)?
45///             .split_once('x')
46///             .ok_or("expected WIDTHxHEIGHT")?;
47///         Ok(Size(w.parse()?, h.parse()?))
48///     }
49/// }
50///
51/// let Size(w, h) = Size::from_arg(BStr::new("80x24")).unwrap();
52/// assert_eq!((w, h), (80, 24));
53/// ```
54pub trait FromArg: Sized {
55    /// The values accepted, when there is a fixed set: help lists them as
56    /// `[possible values: …]`. Empty by default; `#[derive(ValueEnum)]` fills
57    /// it with each visible variant's name.
58    const CHOICES: &'static [&'static str] = &[];
59
60    /// Convert one value.
61    fn from_arg(value: &BStr) -> Result<Self, BoxError>;
62}
63
64/// The cause of an [`ErrorKind::InvalidChoice`](crate::ErrorKind::InvalidChoice):
65/// the value is not one of `choices`.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub struct ChoiceError {
68    /// Every accepted spelling.
69    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
80/// The value as `&str`.
81pub 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/// Any [`FromStr`](std::str::FromStr) type as a value: the text is parsed with
150/// `str::parse`, and its error becomes the invalid value's cause.
151///
152/// For a type from another crate, which can implement neither [`FromArg`]
153/// here nor derive `ValueEnum` (a `strum` enum, say).
154///
155/// ```
156/// use winnow_args::FromArg;
157/// use winnow_args::value::Parsed;
158///
159/// let Parsed(ip) = Parsed::<std::net::Ipv4Addr>::from_arg("127.0.0.1".into())?;
160/// assert!(ip.is_loopback());
161/// assert!(Parsed::<std::net::Ipv4Addr>::from_arg("localhost".into()).is_err());
162/// # Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
163/// ```
164#[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/// A value with where it was found: an `Occurrence` variant holding a
178/// `Spanned<T>` gets the flag's (or word's) offset and whether the value was
179/// attached (`-zfoo`, `--name=value`) or the next word (`-z foo`).
180///
181/// For errors that point at an item of a sequence (`--pop-state` with nothing
182/// pushed), and for messages that echo the spelling given.
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
184pub struct Spanned<T> {
185    /// The value.
186    pub value: T,
187    /// Byte offset of the flag, or of the word for a positional, as
188    /// [`Argv::offset`](crate::Argv::offset) counts.
189    pub offset: usize,
190    /// Whether the value was in the flag's own word.
191    pub attached: bool,
192}
193
194/// An integer in C syntax, as linkers and assemblers read one: `0x`/`0X` hex,
195/// a leading `0` for octal, otherwise decimal, with a sign for signed types.
196/// `--image-base=0x400000`, `-z max-page-size=0x1000`, `-Ttext=010000`.
197#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
198pub struct CInt<T>(pub T);
199
200/// The error of a malformed [`CInt`].
201#[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
212/// The sign, radix and digits of a C integer.
213fn 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/// `key=value`, split at the first `=`: `--defsym=sym=expr`,
252/// `-z max-page-size=4096`, `--section-start=.text=0x1000`. The value may hold
253/// more `=`.
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
255pub struct KeyValue<K, V> {
256    /// Before the first `=`.
257    pub key: K,
258    /// After it.
259    pub value: V,
260}
261
262/// The error of a [`KeyValue`] with no `=`.
263#[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}