getargs-derive 0.1.0

Clap-style #[derive] command-line argument parser for no_std and embedded Rust: zero allocation, compile-time help, subcommands. Built on getargs.
Documentation
/// An enum whose variants are the accepted values of an argument.
///
/// Usually implemented with [`#[derive(ValueEnum)]`](derive@crate::ValueEnum). Fields of a
/// `ValueEnum` type marked with `#[arg(value_enum)]` are parsed with [`from_value`](Self::from_value)
/// and list their [`POSSIBLE_VALUES`](Self::POSSIBLE_VALUES) in the generated help.
pub trait ValueEnum: Sized + 'static {
    /// Every variant, in declaration order.
    const VARIANTS: &'static [Self];

    /// The name of every variant, in declaration order, as accepted on the command line and
    /// shown in the generated help. Aliases are not included.
    const POSSIBLE_VALUES: &'static [&'static str];

    #[doc(hidden)]
    const __VARIANT_IDENTS: &'static [&'static str] = &[];

    /// Returns the name of this variant, as listed in [`POSSIBLE_VALUES`](Self::POSSIBLE_VALUES).
    fn value_name(&self) -> &'static str;

    /// Returns the variant whose name or alias is exactly `value`, if any.
    fn from_value(value: &str) -> Option<Self>;
}

/// The error returned by the [`FromStr`](core::str::FromStr) implementation generated by
/// [`#[derive(ValueEnum)]`](derive@crate::ValueEnum) when a string matches no variant.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ParseValueEnumError;

impl core::fmt::Display for ParseValueEnumError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str("the value matches none of the possible values")
    }
}

impl core::error::Error for ParseValueEnumError {}

#[doc(hidden)]
pub enum HelpPart {
    Text(&'static str),
    List(&'static [&'static str]),
}

const SEPARATOR: &str = ", ";

#[doc(hidden)]
pub const fn help_len(parts: &[HelpPart]) -> usize {
    let mut length = 0;
    let mut index = 0;
    while index < parts.len() {
        match parts[index] {
            HelpPart::Text(text) => length += text.len(),
            HelpPart::List(values) => {
                let mut value = 0;
                while value < values.len() {
                    if value > 0 {
                        length += SEPARATOR.len();
                    }
                    length += values[value].len();
                    value += 1;
                }
            }
        }
        index += 1;
    }
    length
}

const fn copy(bytes: &mut [u8], mut position: usize, text: &str) -> usize {
    let text = text.as_bytes();
    let mut index = 0;
    while index < text.len() {
        bytes[position] = text[index];
        position += 1;
        index += 1;
    }
    position
}

#[doc(hidden)]
pub const fn help_bytes<const N: usize>(parts: &[HelpPart]) -> [u8; N] {
    let mut bytes = [0; N];
    let mut position = 0;
    let mut index = 0;
    while index < parts.len() {
        match parts[index] {
            HelpPart::Text(text) => position = copy(&mut bytes, position, text),
            HelpPart::List(values) => {
                let mut value = 0;
                while value < values.len() {
                    if value > 0 {
                        position = copy(&mut bytes, position, SEPARATOR);
                    }
                    position = copy(&mut bytes, position, values[value]);
                    value += 1;
                }
            }
        }
        index += 1;
    }
    bytes
}

#[doc(hidden)]
pub const fn help_str(bytes: &'static [u8]) -> &'static str {
    match core::str::from_utf8(bytes) {
        Ok(text) => text,
        Err(_) => panic!("generated help is not valid UTF-8"),
    }
}

const fn str_eq(left: &str, right: &str) -> bool {
    let left = left.as_bytes();
    let right = right.as_bytes();
    if left.len() != right.len() {
        return false;
    }
    let mut index = 0;
    while index < left.len() {
        if left[index] != right[index] {
            return false;
        }
        index += 1;
    }
    true
}

#[doc(hidden)]
pub const fn value_name(
    idents: &[&str],
    values: &'static [&'static str],
    ident: &str,
    fallback: &'static str,
) -> &'static str {
    let mut index = 0;
    while index < idents.len() && index < values.len() {
        if str_eq(idents[index], ident) {
            return values[index];
        }
        index += 1;
    }
    fallback
}