Skip to main content

getargs_derive/
input.rs

1use crate::{Error, Opt};
2use core::fmt;
3
4/// Displays bytes that may not be valid UTF-8, without allocating.
5///
6/// Valid UTF-8 sequences are written as they are and every byte of an invalid sequence is written
7/// as a `\xNN` escape, e.g. `caf\xE9` for the Latin-1 encoding of `café`. It is used by the
8/// [`Display`](fmt::Display) implementation of [`Error::InvalidUtf8`].
9///
10/// ```
11/// use getargs_derive::Lossy;
12///
13/// assert_eq!(Lossy(b"caf\xE9.txt").to_string(), "caf\\xE9.txt");
14/// assert_eq!(Lossy("café".as_bytes()).to_string(), "café");
15/// ```
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct Lossy<'a>(pub &'a [u8]);
18
19impl fmt::Display for Lossy<'_> {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        for chunk in self.0.utf8_chunks() {
22            f.write_str(chunk.valid())?;
23            for byte in chunk.invalid() {
24                write!(f, "\\x{byte:02X}")?;
25            }
26        }
27        Ok(())
28    }
29}
30
31/// An argument type accepted by the generated parsers: `&str` or `&[u8]`.
32pub trait Input<'a>: getargs::Argument + 'a {
33    /// The raw bytes of the argument.
34    fn bytes(self) -> &'a [u8];
35
36    /// The argument as a string, or its bytes when they are not valid UTF-8.
37    fn text(self) -> Result<&'a str, &'a [u8]>;
38
39    /// A short option name as a character.
40    fn short(short: Self::ShortOpt) -> char;
41
42    /// Splits the argument at the first `delimiter`.
43    fn split_once(self, delimiter: char) -> Option<(Self, Self)>;
44}
45
46impl<'a, 'b: 'a> Input<'a> for &'b str {
47    fn bytes(self) -> &'a [u8] {
48        self.as_bytes()
49    }
50
51    fn text(self) -> Result<&'a str, &'a [u8]> {
52        Ok(self)
53    }
54
55    fn short(short: char) -> char {
56        short
57    }
58
59    fn split_once(self, delimiter: char) -> Option<(Self, Self)> {
60        str::split_once(self, delimiter)
61    }
62}
63
64impl<'a, 'b: 'a> Input<'a> for &'b [u8] {
65    fn bytes(self) -> &'a [u8] {
66        self
67    }
68
69    fn text(self) -> Result<&'a str, &'a [u8]> {
70        core::str::from_utf8(self).map_err(|_| self)
71    }
72
73    fn short(short: u8) -> char {
74        if short.is_ascii() {
75            char::from(short)
76        } else {
77            char::REPLACEMENT_CHARACTER
78        }
79    }
80
81    fn split_once(self, delimiter: char) -> Option<(Self, Self)> {
82        let mut buffer = [0; 4];
83        let delimiter = delimiter.encode_utf8(&mut buffer).as_bytes();
84        let index = self
85            .windows(delimiter.len())
86            .position(|window| window == delimiter)?;
87        Some((&self[..index], &self[index + delimiter.len()..]))
88    }
89}
90
91/// An argument read from the command line, with option names converted to UTF-8.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum Arg<'a, A> {
94    /// A short option, like `-v`.
95    Short(char),
96    /// A long option, like `--verbose`, without its value.
97    Long(&'a str),
98    /// A positional argument, or every argument after `--`.
99    Positional(A),
100}
101
102/// Converts a value to a string for the argument `name`.
103pub fn text<'a, V: Input<'a>>(value: V, name: &'static str) -> Result<&'a str, Error<'a>> {
104    value.text().map_err(|value| Error::InvalidUtf8 {
105        name: Some(name),
106        value,
107    })
108}
109
110/// Converts an argument that matches no field to a string.
111pub fn unnamed<'a, V: Input<'a>>(value: V) -> Result<&'a str, Error<'a>> {
112    value
113        .text()
114        .map_err(|value| Error::InvalidUtf8 { name: None, value })
115}
116
117/// The raw bytes of a value, for `&[u8]` fields.
118pub fn bytes<'a, V: Input<'a>>(value: V) -> &'a [u8] {
119    value.bytes()
120}
121
122/// Converts an argument read by `getargs`.
123pub fn arg<'a, A: Input<'a>>(argument: getargs::Arg<A>) -> Result<Arg<'a, A>, Error<'a>> {
124    Ok(match argument {
125        getargs::Arg::Short(short) => Arg::Short(A::short(short)),
126        getargs::Arg::Long(long) => Arg::Long(unnamed(long)?),
127        getargs::Arg::Positional(value) => Arg::Positional(value),
128    })
129}
130
131/// Converts an option named by `getargs`.
132pub fn opt<'a, A: Input<'a>>(option: getargs::Opt<A>) -> Result<Opt<'a>, Error<'a>> {
133    Ok(match option {
134        getargs::Opt::Short(short) => Opt::Short(A::short(short)),
135        getargs::Opt::Long(long) => Opt::Long(unnamed(long)?),
136    })
137}
138
139/// The error for a positional argument given where the struct expects no more of them.
140pub fn unexpected_argument<'a, V: Input<'a>>(value: V) -> Error<'a> {
141    match value.text() {
142        Ok(value) => Error::UnexpectedArgument(value),
143        Err(value) => Error::InvalidUtf8 { name: None, value },
144    }
145}
146
147/// The error for a positional argument given where a subcommand is expected, suggesting one of
148/// `names`.
149pub fn unknown_subcommand<'a, V: Input<'a>>(value: V, names: &[&[&'static str]]) -> Error<'a> {
150    match value.text() {
151        Ok(name) => Error::UnknownSubcommand {
152            name,
153            suggestion: crate::suggest::suggest(name, names),
154        },
155        Err(value) => Error::InvalidUtf8 { name: None, value },
156    }
157}