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::env::env_lookup;
use super::parsing;
use super::validation::convert;
use crate::model::{
    DefaultValue, FieldKind, FieldModel, FlagAction, FlattenModel, Slot, SubcommandModel,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Ident, Index, Type};

#[derive(Default)]
pub(super) struct FieldTokens {
    pub(super) state_types: Vec<TokenStream>,
    pub(super) initializers: Vec<TokenStream>,
    pub(super) value_idents: Vec<Ident>,
    pub(super) prologue: Vec<TokenStream>,
    pub(super) option_match_arms: Vec<TokenStream>,
    pub(super) steps: Vec<TokenStream>,
    pub(super) filled: Vec<TokenStream>,
    pub(super) finalizers: Vec<TokenStream>,
    pub(super) field_builds: Vec<TokenStream>,
    pub(super) global_arms: Vec<TokenStream>,
    pub(super) global_steps: Vec<TokenStream>,
    pub(super) subcommand: Option<Index>,
}

pub(super) fn subcommand_trait(ty: &Type) -> TokenStream {
    quote! { <#ty as getargs_derive::__private::Subcommand<'__getargs>> }
}

pub(super) fn option_pattern(field: &FieldModel) -> TokenStream {
    let shorts = field
        .short
        .into_iter()
        .chain(field.short_aliases.iter().map(|alias| alias.name));
    let longs = field
        .long
        .iter()
        .chain(field.long_aliases.iter().map(|alias| &alias.name));
    let patterns: Vec<TokenStream> = shorts
        .map(|short| quote! { getargs_derive::__private::Arg::Short(#short) })
        .chain(longs.map(|long| quote! { getargs_derive::__private::Arg::Long(#long) }))
        .collect();
    quote! { #(#patterns)|* }
}

fn fallback(field: &FieldModel) -> TokenStream {
    let error_name = field.error_name();
    match (&field.default, &field.kind) {
        (Some(DefaultValue::Literal(literal)), _) => {
            convert(field, quote! { #literal }, &error_name)
        }
        (Some(DefaultValue::Expr(expr)), _) => quote! { #expr },
        (None, FieldKind::Flag(FlagAction::SetTrue)) => quote! { false },
        (None, FieldKind::Flag(FlagAction::SetFalse)) => quote! { true },
        (None, FieldKind::Flag(FlagAction::Count)) => quote! { 0 },
        (None, _) => quote! {
            return Err(getargs_derive::Error::MissingArgument(#error_name))
        },
    }
}

pub(super) fn args_trait(ty: &Type) -> TokenStream {
    quote! { <#ty as getargs_derive::__private::Args<'__getargs>> }
}

pub(super) fn all(conditions: &[TokenStream]) -> TokenStream {
    if conditions.is_empty() {
        quote! { true }
    } else {
        quote! { #(#conditions)&&* }
    }
}

impl FieldTokens {
    pub(super) fn push(&mut self, slot: &Slot, negative_numbers: bool) {
        match slot {
            Slot::Arg(field) => self.push_arg(field),
            Slot::Flatten(flatten) => self.push_flatten(flatten, negative_numbers),
        }
    }

    fn push_flatten(&mut self, flatten: &FlattenModel, negative_numbers: bool) {
        let index = Index::from(self.state_types.len());
        let field_ident = &flatten.ident;
        let value_ident = format_ident!("__value_{}", flatten.name);
        let args = args_trait(&flatten.ty);
        let earlier = all(&self.filled);

        self.state_types.push(quote! { #args::State });
        self.initializers.push(quote! { #args::init() });
        self.steps.push(quote! {
            if #args::try_arg(
                &mut __state.#index,
                __argument,
                __options,
                __context.child(__last, #negative_numbers, !(#earlier)),
                __globals,
            )? {
                return core::result::Result::Ok(true);
            }
        });
        self.global_steps.push(quote! {
            if #args::try_global(
                &mut __state.#index,
                __argument,
                __options,
                __context.child(false, #negative_numbers, false),
            )? {
                return core::result::Result::Ok(true);
            }
        });
        self.filled.push(quote! { #args::filled(&__state.#index) });
        self.finalizers.push(quote! {
            let #value_ident = #args::finish(#value_ident, __getargs_env)?;
        });
        self.field_builds
            .push(quote! { #field_ident: #value_ident });
        self.value_idents.push(value_ident);
    }

    fn push_option_arm(&mut self, field: &FieldModel, arm: TokenStream) {
        if field.parsing.global {
            self.global_arms.push(arm.clone());
        }
        self.option_match_arms.push(arm);
    }

    pub(super) fn push_subcommand(&mut self, subcommand: &SubcommandModel) {
        let index = Index::from(self.state_types.len());
        let field_ident = &subcommand.ident;
        let value_ident = format_ident!("__value_{}", subcommand.name);
        let commands = subcommand_trait(&subcommand.ty);

        self.state_types
            .push(quote! { core::option::Option<#commands::State> });
        self.initializers
            .push(quote! { core::option::Option::None });
        let missing = if subcommand.optional {
            quote! { core::option::Option::None }
        } else {
            quote! {
                return Err(getargs_derive::Error::MissingSubcommand(#commands::VISIBLE))
            }
        };
        let found = if subcommand.optional {
            quote! { core::option::Option::Some(#commands::finish(state, __getargs_env)?) }
        } else {
            quote! { #commands::finish(state, __getargs_env)? }
        };
        self.finalizers.push(quote! {
            let #value_ident = match #value_ident {
                core::option::Option::Some(state) => #found,
                core::option::Option::None => #missing,
            };
        });
        self.field_builds
            .push(quote! { #field_ident: #value_ident });
        self.value_idents.push(value_ident);
        self.subcommand = Some(index);
    }

    fn push_arg(&mut self, field: &FieldModel) {
        if let Some(collection) = &field.collection {
            self.push_collection(field, collection);
            return;
        }
        let index = Index::from(self.state_types.len());
        let slot = quote! { __state.#index };
        let field_ident = &field.ident;
        let field_type = &field.ty;
        let value_type = &field.value_ty;
        let value_ident = format_ident!("__value_{}", field.name);

        self.state_types
            .push(quote! { core::option::Option<#value_type> });
        self.initializers
            .push(quote! { core::option::Option::None });

        match field.kind {
            FieldKind::Flag(action) => {
                let pattern = option_pattern(field);
                let update = match action {
                    FlagAction::SetTrue => quote! { true },
                    FlagAction::SetFalse => quote! { false },
                    FlagAction::Count => quote! {
                        match #slot {
                            core::option::Option::Some(count) => count.saturating_add(1),
                            core::option::Option::None => 1,
                        }
                    },
                };
                self.push_option_arm(
                    field,
                    quote! {
                        #pattern => {
                            #slot = core::option::Option::Some(#update);
                            return core::result::Result::Ok(true);
                        }
                    },
                );
            }
            FieldKind::Option => {
                let pattern = option_pattern(field);
                let error_name = field.error_name();
                let parsed = convert(field, quote! { value }, &error_name);
                let value = parsing::option_value(
                    field,
                    quote! { getargs_derive::Error::MissingOptionValue(#error_name) },
                );
                self.push_option_arm(
                    field,
                    quote! {
                        #pattern => {
                            let value = #value;
                            #slot = core::option::Option::Some(#parsed);
                            return core::result::Result::Ok(true);
                        }
                    },
                );
            }
            FieldKind::Positional => {
                let parsed = convert(field, quote! { value }, &field.error_name());
                let earlier = all(&self.filled);
                self.prologue.push(parsing::negative_positional(
                    field, &slot, &earlier, &parsed,
                ));
                self.steps.push(parsing::positional(field, &slot, &parsed));
                self.filled.push(quote! { #slot.is_some() });
            }
        }

        self.finalizers.extend(env_lookup(field));

        if !field.optional {
            let fallback = fallback(field);
            self.finalizers.push(quote! {
                let #value_ident: #field_type = match #value_ident {
                    core::option::Option::Some(value) => value,
                    core::option::Option::None => #fallback,
                };
            });
        }

        self.field_builds
            .push(quote! { #field_ident: #value_ident });
        self.value_idents.push(value_ident);
    }
}