Skip to main content

getargs_derive/
value_enum.rs

1/// An enum whose variants are the accepted values of an argument.
2///
3/// Usually implemented with [`#[derive(ValueEnum)]`](derive@crate::ValueEnum). Fields of a
4/// `ValueEnum` type marked with `#[arg(value_enum)]` are parsed with [`from_value`](Self::from_value)
5/// and list their [`POSSIBLE_VALUES`](Self::POSSIBLE_VALUES) in the generated help.
6pub trait ValueEnum: Sized + 'static {
7    /// Every variant, in declaration order.
8    const VARIANTS: &'static [Self];
9
10    /// The name of every variant, in declaration order, as accepted on the command line and
11    /// shown in the generated help. Aliases are not included.
12    const POSSIBLE_VALUES: &'static [&'static str];
13
14    #[doc(hidden)]
15    const __VARIANT_IDENTS: &'static [&'static str] = &[];
16
17    /// Returns the name of this variant, as listed in [`POSSIBLE_VALUES`](Self::POSSIBLE_VALUES).
18    fn value_name(&self) -> &'static str;
19
20    /// Returns the variant whose name or alias is exactly `value`, if any.
21    fn from_value(value: &str) -> Option<Self>;
22}
23
24/// The error returned by the [`FromStr`](core::str::FromStr) implementation generated by
25/// [`#[derive(ValueEnum)]`](derive@crate::ValueEnum) when a string matches no variant.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
27pub struct ParseValueEnumError;
28
29impl core::fmt::Display for ParseValueEnumError {
30    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31        f.write_str("the value matches none of the possible values")
32    }
33}
34
35impl core::error::Error for ParseValueEnumError {}
36
37#[doc(hidden)]
38pub enum HelpPart {
39    Text(&'static str),
40    List(&'static [&'static str]),
41}
42
43const SEPARATOR: &str = ", ";
44
45#[doc(hidden)]
46pub const fn help_len(parts: &[HelpPart]) -> usize {
47    let mut length = 0;
48    let mut index = 0;
49    while index < parts.len() {
50        match parts[index] {
51            HelpPart::Text(text) => length += text.len(),
52            HelpPart::List(values) => {
53                let mut value = 0;
54                while value < values.len() {
55                    if value > 0 {
56                        length += SEPARATOR.len();
57                    }
58                    length += values[value].len();
59                    value += 1;
60                }
61            }
62        }
63        index += 1;
64    }
65    length
66}
67
68const fn copy(bytes: &mut [u8], mut position: usize, text: &str) -> usize {
69    let text = text.as_bytes();
70    let mut index = 0;
71    while index < text.len() {
72        bytes[position] = text[index];
73        position += 1;
74        index += 1;
75    }
76    position
77}
78
79#[doc(hidden)]
80pub const fn help_bytes<const N: usize>(parts: &[HelpPart]) -> [u8; N] {
81    let mut bytes = [0; N];
82    let mut position = 0;
83    let mut index = 0;
84    while index < parts.len() {
85        match parts[index] {
86            HelpPart::Text(text) => position = copy(&mut bytes, position, text),
87            HelpPart::List(values) => {
88                let mut value = 0;
89                while value < values.len() {
90                    if value > 0 {
91                        position = copy(&mut bytes, position, SEPARATOR);
92                    }
93                    position = copy(&mut bytes, position, values[value]);
94                    value += 1;
95                }
96            }
97        }
98        index += 1;
99    }
100    bytes
101}
102
103#[doc(hidden)]
104pub const fn help_str(bytes: &'static [u8]) -> &'static str {
105    match core::str::from_utf8(bytes) {
106        Ok(text) => text,
107        Err(_) => panic!("generated help is not valid UTF-8"),
108    }
109}
110
111const fn str_eq(left: &str, right: &str) -> bool {
112    let left = left.as_bytes();
113    let right = right.as_bytes();
114    if left.len() != right.len() {
115        return false;
116    }
117    let mut index = 0;
118    while index < left.len() {
119        if left[index] != right[index] {
120            return false;
121        }
122        index += 1;
123    }
124    true
125}
126
127#[doc(hidden)]
128pub const fn value_name(
129    idents: &[&str],
130    values: &'static [&'static str],
131    ident: &str,
132    fallback: &'static str,
133) -> &'static str {
134    let mut index = 0;
135    while index < idents.len() && index < values.len() {
136        if str_eq(idents[index], ident) {
137            return values[index];
138        }
139        index += 1;
140    }
141    fallback
142}