getargs-derive-macros 0.1.0

Internal implementation detail of getargs-derive. Not meant for direct use; depend on getargs-derive instead.
Documentation
use super::{FieldKind, FieldModel, RawField};
use crate::model::attr::set_once;
use crate::model::errors::Errors;
use proc_macro2::Span;
use syn::{Lit, Token, meta::ParseNestedMeta, spanned::Spanned};

#[derive(Default)]
pub(super) struct RawParsing {
    allow_hyphen_values: Option<(bool, Span)>,
    allow_negative_numbers: Option<(bool, Span)>,
    last: Option<(bool, Span)>,
    global: Option<(bool, Span)>,
}

#[derive(Default)]
pub(crate) struct Parsing {
    pub(crate) allow_hyphen_values: bool,
    pub(crate) allow_negative_numbers: bool,
    pub(crate) last: bool,
    pub(crate) global: bool,
    last_span: Option<Span>,
}

pub(in crate::model) fn parse_bool(meta: &ParseNestedMeta, key: &str) -> syn::Result<(bool, Span)> {
    if !meta.input.peek(Token![=]) {
        if meta.input.peek(syn::token::Paren) {
            return Err(meta.error(format!(
                "`{key}` expects a boolean, e.g. `{key}` or `{key} = true`"
            )));
        }
        return Ok((true, meta.path.span()));
    }
    match meta.value()?.parse::<Lit>()? {
        Lit::Bool(literal) => Ok((literal.value, literal.span())),
        other => Err(syn::Error::new(
            other.span(),
            format!("`{key}` expects a boolean literal, e.g. `{key} = true`"),
        )),
    }
}

pub(super) fn allow_hyphen_values(meta: &ParseNestedMeta, raw: &mut RawField) -> syn::Result<()> {
    let value = parse_bool(meta, "allow_hyphen_values")?;
    set_once(&mut raw.parsing.allow_hyphen_values, value, meta)
}

pub(super) fn allow_negative_numbers(
    meta: &ParseNestedMeta,
    raw: &mut RawField,
) -> syn::Result<()> {
    let value = parse_bool(meta, "allow_negative_numbers")?;
    set_once(&mut raw.parsing.allow_negative_numbers, value, meta)
}

pub(super) fn last(meta: &ParseNestedMeta, raw: &mut RawField) -> syn::Result<()> {
    let value = parse_bool(meta, "last")?;
    set_once(&mut raw.parsing.last, value, meta)
}

pub(super) fn global(meta: &ParseNestedMeta, raw: &mut RawField) -> syn::Result<()> {
    let value = parse_bool(meta, "global")?;
    set_once(&mut raw.parsing.global, value, meta)
}

fn enabled(setting: Option<(bool, Span)>) -> Option<Span> {
    setting.and_then(|(value, span)| value.then_some(span))
}

pub(super) fn build_parsing(raw: RawParsing, kind: &FieldKind, errors: &mut Errors) -> Parsing {
    let allow_hyphen_values = enabled(raw.allow_hyphen_values);
    let allow_negative_numbers = enabled(raw.allow_negative_numbers);
    let last = enabled(raw.last);
    let global = enabled(raw.global);

    if let (Some(span), FieldKind::Positional) = (global, kind) {
        errors.push(syn::Error::new(
            span,
            "`global` can only be used on options and flags; positional arguments of a parent are not accepted after its subcommand",
        ));
    }

    if let Some(span) = allow_hyphen_values {
        match kind {
            FieldKind::Option => {}
            FieldKind::Flag(_) => errors.push(syn::Error::new(
                span,
                "`allow_hyphen_values` can only be used on options taking a value; flags do not take a value",
            )),
            FieldKind::Positional => errors.push(syn::Error::new(
                span,
                "`allow_hyphen_values` is not supported on positional arguments, because `getargs` splits `-x` into options before the field sees it; use `allow_negative_numbers` for numbers, or `last = true` to accept any value after `--`",
            )),
        }
    }

    if let (Some(span), FieldKind::Flag(_)) = (allow_negative_numbers, kind) {
        errors.push(syn::Error::new(
            span,
            "`allow_negative_numbers` can only be used on options taking a value and positional arguments; flags do not take a value",
        ));
    }

    if let Some(span) = last
        && !matches!(kind, FieldKind::Positional)
    {
        errors.push(syn::Error::new(
            span,
            "`last` can only be used on positional arguments; remove `short` and `long`",
        ));
    }

    Parsing {
        allow_hyphen_values: allow_hyphen_values.is_some(),
        allow_negative_numbers: allow_negative_numbers.is_some(),
        last: last.is_some(),
        global: global.is_some(),
        last_span: last,
    }
}

pub(in crate::model) fn apply_container(fields: &mut [FieldModel], allow_negative_numbers: bool) {
    if !allow_negative_numbers {
        return;
    }
    for field in fields {
        if matches!(field.kind, FieldKind::Option | FieldKind::Positional) {
            field.parsing.allow_negative_numbers = true;
        }
    }
}

pub(in crate::model) fn validate_last(fields: &[FieldModel], errors: &mut Errors) {
    let positionals: Vec<&FieldModel> = fields
        .iter()
        .filter(|field| matches!(field.kind, FieldKind::Positional))
        .collect();
    let lasts: Vec<(usize, &FieldModel, Span)> = positionals
        .iter()
        .enumerate()
        .filter_map(|(index, field)| field.parsing.last_span.map(|span| (index, *field, span)))
        .collect();
    if let [(_, first, _), others @ ..] = lasts.as_slice()
        && !others.is_empty()
    {
        for (_, _, span) in others {
            errors.push(syn::Error::new(
                *span,
                format!(
                    "only one positional argument can use `last`, and `{}` already does",
                    first.name
                ),
            ));
        }
        return;
    }
    for (index, field, span) in lasts {
        if index + 1 != positionals.len() {
            errors.push(syn::Error::new(
                span,
                format!(
                    "`last` positional argument `{}` must be the final positional argument; move it after `{}`",
                    field.name,
                    positionals[positionals.len() - 1].name
                ),
            ));
        }
    }
}