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::model::{Kind, Variant};
use crate::expand::{static_args, variant_args};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Fields, GenericParam, Lifetime, LifetimeParam, Type, parse_quote};

fn hidden_struct(input: &DeriveInput) -> TokenStream {
    let name = &input.ident;
    let vis = &input.vis;
    let (impl_generics, _, _) = input.generics.split_for_impl();
    let fields = match &input.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(fields) => fields
                .named
                .iter()
                .map(|field| {
                    let ident = &field.ident;
                    let ty = &field.ty;
                    quote! { #vis #ident: #ty }
                })
                .collect::<Vec<_>>(),
            _ => Vec::new(),
        },
        _ => Vec::new(),
    };
    let generics = impl_generics;
    quote! {
        #[doc(hidden)]
        #[allow(non_camel_case_types)]
        #vis struct #name #generics {
            #(#fields,)*
        }
    }
}

fn variant_type(variant: &Variant) -> Type {
    match (&variant.kind, &variant.args) {
        (Kind::Newtype(ty), _) => ty.clone(),
        (_, Some((input, _))) => {
            let name = &input.ident;
            let (_, ty_generics, _) = input.generics.split_for_impl();
            parse_quote! { #name #ty_generics }
        }
        _ => unreachable!("unit and named variants have a generated struct"),
    }
}

fn entry(variant: &Variant, ty: &Type) -> TokenStream {
    let args = static_args(ty);
    let name = &variant.name;
    let hidden = variant.hidden;
    let (about, long_about) = match (&variant.kind, &variant.about) {
        (Kind::Newtype(_), Some((about, long_about))) => {
            (quote! { #about }, quote! { #long_about })
        }
        _ => (
            quote! { #args::COMMAND.about },
            quote! { #args::COMMAND.long_about },
        ),
    };
    let visible = variant.visible_aliases();
    let description = if visible.is_empty() {
        about.clone()
    } else {
        let aliases = format!("[aliases: {}]", visible.join(", "));
        quote! {
            {
                const ABOUT: &str = #about;
                const PARTS: &[getargs_derive::__HelpPart] = &[
                    getargs_derive::__HelpPart::Text(ABOUT),
                    getargs_derive::__HelpPart::Text(
                        getargs_derive::__private::separator(ABOUT, #aliases),
                    ),
                    getargs_derive::__HelpPart::Text(#aliases),
                ];
                const LENGTH: usize = getargs_derive::__help_len(PARTS);
                const BYTES: [u8; LENGTH] = getargs_derive::__help_bytes::<LENGTH>(PARTS);
                getargs_derive::__help_str(&BYTES)
            }
        }
    };
    quote! {
        getargs_derive::__private::CommandEntry {
            name: #name,
            description: #description,
            about: #about,
            long_about: #long_about,
            hidden: #hidden,
            command: &#args::COMMAND,
        }
    }
}

fn info(variant: &Variant, ty: &Type) -> TokenStream {
    let args = static_args(ty);
    let name = &variant.name;
    let hidden = variant.hidden;
    let (about, long_about) = match (&variant.kind, &variant.about) {
        (Kind::Newtype(_), Some((about, long_about))) => {
            (quote! { #about }, quote! { #long_about })
        }
        _ => (
            quote! { #args::COMMAND.about },
            quote! { #args::COMMAND.long_about },
        ),
    };
    let visible: Vec<&str> = variant.visible_aliases();
    let aliases: Vec<&str> = variant
        .aliases
        .iter()
        .filter(|alias| !alias.visible)
        .map(|alias| alias.name.as_str())
        .collect();
    quote! {
        {
            const LONG_ABOUT: &str = #long_about;
            const HELP: &[getargs_derive::ArgInfo] = &[getargs_derive::__private::help_arg(
                !LONG_ABOUT.is_empty() || #args::HAS_LONG_HELP,
            )];
            const NONE: &[getargs_derive::ArgInfo] = &[];
            const PARTS: &[&[getargs_derive::ArgInfo]] = &[
                #args::ARGS,
                if #args::COMMAND.help_flag { HELP } else { NONE },
            ];
            const LENGTH: usize = getargs_derive::__private::count(PARTS);
            const MERGED: [getargs_derive::ArgInfo; LENGTH] = getargs_derive::__private::concat(
                PARTS,
                getargs_derive::ArgInfo::new("", getargs_derive::ArgKind::Flag),
            );
            getargs_derive::__private::subcommand_info(
                #name,
                #about,
                LONG_ABOUT,
                &MERGED,
                &#args::COMMAND,
            )
            .visible_aliases(&[#(#visible),*])
            .aliases(&[#(#aliases),*])
            .hidden(#hidden)
        }
    }
}

fn build(variant: &Variant, ty: &Type) -> TokenStream {
    let ident = &variant.ident;
    let args = quote! { <#ty as getargs_derive::__private::Args<'__getargs>> };
    match &variant.kind {
        Kind::Unit => quote! {
            let _ = #args::finish(state, __getargs_env)?;
            core::result::Result::Ok(Self::#ident)
        },
        Kind::Named(fields) => quote! {
            let value = #args::finish(state, __getargs_env)?;
            core::result::Result::Ok(Self::#ident { #(#fields: value.#fields),* })
        },
        Kind::Newtype(_) => quote! {
            core::result::Result::Ok(Self::#ident(#args::finish(state, __getargs_env)?))
        },
    }
}

pub(crate) fn expand(input: &DeriveInput, variants: &[Variant]) -> TokenStream {
    let name = &input.ident;
    let (_, ty_generics, _) = input.generics.split_for_impl();
    let lifetime: Lifetime = parse_quote! { '__getargs };
    let mut generics = input.generics.clone();
    let outlives: Vec<Lifetime> = generics
        .lifetimes()
        .map(|parameter| parameter.lifetime.clone())
        .collect();
    generics.params.insert(
        0,
        GenericParam::Lifetime(LifetimeParam::new(lifetime.clone())),
    );
    let where_clause = generics.make_where_clause();
    for outlived in outlives {
        where_clause
            .predicates
            .push(parse_quote! { #lifetime: #outlived });
    }
    let (impl_generics, _, where_clause) = generics.split_for_impl();

    let mut items = Vec::new();
    for variant in variants {
        if let Some((args_input, model)) = &variant.args {
            items.push(hidden_struct(args_input));
            items.push(variant_args(args_input, model));
        }
    }

    let types: Vec<Type> = variants.iter().map(variant_type).collect();
    let args: Vec<TokenStream> = types
        .iter()
        .map(|ty| quote! { <#ty as getargs_derive::__private::Args<#lifetime>> })
        .collect();
    let entries = variants
        .iter()
        .zip(&types)
        .map(|(variant, ty)| entry(variant, ty));
    let infos = variants
        .iter()
        .zip(&types)
        .map(|(variant, ty)| info(variant, ty));
    let names: Vec<&str> = variants
        .iter()
        .filter(|variant| !variant.hidden)
        .flat_map(|variant| std::iter::once(variant.name.as_str()).chain(variant.visible_aliases()))
        .collect();
    let visible: Vec<&str> = variants
        .iter()
        .filter(|variant| !variant.hidden)
        .map(|variant| variant.name.as_str())
        .collect();
    let mut env = quote! { getargs_derive::__private::NoEnv };
    for args in &args {
        env = quote! { <#env as getargs_derive::__private::EnvFlag>::Or<#args::Env> };
    }
    let states: Vec<_> = (0..variants.len())
        .map(|index| format_ident!("__state_{}", index))
        .collect();
    let arms = variants.iter().enumerate().map(|(index, variant)| {
        let primary = &variant.name;
        let aliases = variant.aliases.iter().map(|alias| &alias.name);
        let ty = &types[index];
        let values = (0..variants.len()).map(|position| {
            if position == index {
                quote! { core::option::Option::Some(__state) }
            } else {
                quote! { core::option::Option::None }
            }
        });
        quote! {
            #primary #(| #aliases)* => {
                let __context = __context.command(getargs_derive::__private::node(
                    <Self as getargs_derive::__private::Subcommand<#lifetime>>::COMMANDS,
                    __context.node,
                    #index,
                ));
                let __state = getargs_derive::__private::command::<#ty, __A, __I>(
                    __options,
                    __context,
                    __globals,
                )?;
                core::result::Result::Ok(core::option::Option::Some((#(#values,)*)))
            }
        }
    });
    let finishes = variants
        .iter()
        .zip(&types)
        .zip(&states)
        .map(|((variant, ty), state_ident)| {
            let build = build(variant, ty);
            quote! {
                if let core::option::Option::Some(state) = #state_ident {
                    #build
                } else
            }
        });

    quote! {
        #(#items)*

        impl #impl_generics getargs_derive::__private::Subcommand<#lifetime> for #name #ty_generics #where_clause {
            type State = (#(core::option::Option<#args::State>,)*);
            type Env = #env;

            const COMMANDS: &'static [getargs_derive::__private::CommandEntry] = &[#(#entries),*];
            const NAMES: &'static [&'static str] = &[#(#names),*];
            const VISIBLE: &'static [&'static str] = &[#(#visible),*];
            const INFOS: &'static [getargs_derive::CommandInfo] = &[#(#infos),*];

            fn try_command<
                __A: getargs_derive::__private::Input<#lifetime>,
                __I: Iterator<Item = __A>,
            >(
                __name: &#lifetime str,
                __options: &mut getargs::Options<__A, __I>,
                __context: getargs_derive::__private::Context<#lifetime, '_>,
                __globals: &mut getargs_derive::__private::Globals<'_, #lifetime, __A, __I>,
            ) -> core::result::Result<
                core::option::Option<Self::State>,
                getargs_derive::Error<#lifetime>,
            > {
                match __name {
                    #(#arms)*
                    _ => core::result::Result::Ok(core::option::Option::None),
                }
            }

            fn finish<__E: FnMut(&str) -> core::option::Option<&#lifetime str>>(
                __state: Self::State,
                __getargs_env: &mut __E,
            ) -> core::result::Result<Self, getargs_derive::Error<#lifetime>> {
                let (#(#states,)*) = __state;
                #(#finishes)* {
                    core::unreachable!("a subcommand state is set when it is parsed")
                }
            }
        }
    }
}