getargs-derive-macros 0.1.0

Internal implementation detail of getargs-derive. Not meant for direct use; depend on getargs-derive instead.
Documentation
pub(crate) mod attr;
mod container;
pub(crate) mod doc;
pub(crate) mod errors;
mod field;
mod relations;
pub(crate) mod types;
mod validate;

pub(crate) use container::{ContainerModel, Keys, Version, parse_container, validate_command_name};
use field::Member;
pub(crate) use field::{
    Alias, CollectionModel, DefaultValue, FieldKind, FieldModel, FlagAction, FlattenModel,
    SubcommandModel, ValueParser,
};
pub(crate) use relations::resolve_groups;

use errors::Errors;
use syn::{Data, DeriveInput, Fields, GenericParam};

pub(crate) enum Slot<'a> {
    Arg(&'a FieldModel),
    Flatten(&'a FlattenModel),
}

pub(crate) struct Model {
    pub(crate) container: ContainerModel,
    pub(crate) fields: Vec<FieldModel>,
    pub(crate) flattened: Vec<(usize, FlattenModel)>,
    pub(crate) subcommand: Option<SubcommandModel>,
}

impl Model {
    pub(crate) fn slots(&self) -> Vec<Slot<'_>> {
        let mut slots = Vec::new();
        let mut flattened = self.flattened.iter().peekable();
        for (index, field) in self.fields.iter().enumerate() {
            while let Some((_, flatten)) = flattened.next_if(|(before, _)| *before == index) {
                slots.push(Slot::Flatten(flatten));
            }
            slots.push(Slot::Arg(field));
        }
        slots.extend(flattened.map(|(_, flatten)| Slot::Flatten(flatten)));
        slots
    }
}

pub(crate) fn parse(input: &DeriveInput, keys: Keys) -> syn::Result<Model> {
    let fields = match &input.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(fields) => &fields.named,
            Fields::Unnamed(fields) => {
                return Err(syn::Error::new_spanned(
                    fields,
                    "GetArgs requires a struct with named fields; tuple structs are not supported",
                ));
            }
            Fields::Unit => {
                return Err(syn::Error::new_spanned(
                    &input.ident,
                    "GetArgs requires a struct with named fields; unit structs are not supported",
                ));
            }
        },
        Data::Enum(data) => {
            return Err(syn::Error::new_spanned(
                data.enum_token,
                "GetArgs can only be derived for structs with named fields",
            ));
        }
        Data::Union(data) => {
            return Err(syn::Error::new_spanned(
                data.union_token,
                "GetArgs can only be derived for structs with named fields",
            ));
        }
    };

    let mut errors = Errors::default();
    let container = match container::parse_container(&input.attrs, keys) {
        Ok(container) => Some(container),
        Err(error) => {
            errors.push(error);
            None
        }
    };
    let mut models = Vec::new();
    let mut flattened = Vec::new();
    let mut subcommand: Option<SubcommandModel> = None;
    let mut built = true;

    for field in fields {
        match field::build_member(field) {
            Ok(Member::Arg(model)) => models.push(*model),
            Ok(Member::Flatten(model)) => flattened.push((models.len(), *model)),
            Ok(Member::Subcommand(model)) => match &subcommand {
                Some(first) => errors.push(syn::Error::new(
                    model.span,
                    format!(
                        "a struct can only have one `#[command(subcommand)]` field, and `{}` already is one",
                        first.name
                    ),
                )),
                None => subcommand = Some(*model),
            },
            Err(error) => {
                built = false;
                errors.push(error);
            }
        }
    }

    let generic = input
        .generics
        .params
        .iter()
        .any(|parameter| !matches!(parameter, GenericParam::Lifetime(_)));
    if generic {
        for (_, flatten) in &flattened {
            errors.push(syn::Error::new(
                flatten.span,
                "`#[command(flatten)]` is not supported in structs with type or const parameters",
            ));
        }
        if let Some(subcommand) = &subcommand {
            errors.push(syn::Error::new(
                subcommand.span,
                "`#[command(subcommand)]` is not supported in structs with type or const parameters",
            ));
        }
    }

    let help_enabled = container
        .as_ref()
        .is_some_and(|container| !container.disable_help_flag);
    let version_enabled = container
        .as_ref()
        .is_some_and(|container| container.version.is_some());
    if let Some(container) = &container {
        field::apply_container(&mut models, container.allow_negative_numbers);
    }
    field::validate_last(&models, &mut errors);
    field::validate_positionals(&models, &mut errors);
    validate::validate_struct(&models, help_enabled, version_enabled, &mut errors);
    let groups = container
        .as_ref()
        .map_or(&[][..], |container| container.groups.as_slice());
    if built {
        relations::validate_relations(groups, &models, &mut errors);
    }
    match container {
        Some(container) => errors.finish(Model {
            container,
            fields: models,
            flattened,
            subcommand,
        }),
        None => Err(errors.0.expect("container errors are recorded")),
    }
}