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::flatten::{Merged, static_self};
use super::version::version_value;
use crate::help::{command_name, has_long_help};
use crate::model::{FieldKind, FieldModel, FlagAction, Model};
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;

fn arg_info(field: &FieldModel) -> TokenStream {
    let id = &field.name;
    let kind = match field.kind {
        FieldKind::Positional => quote! { Positional },
        FieldKind::Option => quote! { Option },
        FieldKind::Flag(_) => quote! { Flag },
    };
    let mut calls = Vec::new();
    if let Some(short) = field.short {
        calls.push(quote! { .short(#short) });
    }
    if let Some(long) = &field.long {
        calls.push(quote! { .long(#long) });
    }
    let (visible_shorts, shorts): (Vec<_>, Vec<_>) =
        field.short_aliases.iter().partition(|alias| alias.visible);
    let (visible_longs, longs): (Vec<_>, Vec<_>) =
        field.long_aliases.iter().partition(|alias| alias.visible);
    let lists = [
        (quote! { visible_short_aliases }, names(&visible_shorts)),
        (quote! { short_aliases }, names(&shorts)),
        (quote! { visible_aliases }, names(&visible_longs)),
        (quote! { aliases }, names(&longs)),
    ];
    for (method, names) in lists {
        if !names.is_empty() {
            calls.push(quote! { .#method(&[#(#names),*]) });
        }
    }
    if !matches!(field.kind, FieldKind::Flag(_)) {
        let value_name = &field.value_name;
        calls.push(quote! { .value_name(#value_name) });
    }
    if let Some(help) = field.help.as_deref().filter(|help| !help.is_empty()) {
        calls.push(quote! { .help(#help) });
    }
    if let Some(long_help) = field.long_help.as_deref().filter(|help| !help.is_empty()) {
        calls.push(quote! { .long_help(#long_help) });
    }
    if field.value_enum {
        let value_ty = &field.value_ty;
        calls.push(quote! {
            .possible_values(<#value_ty as getargs_derive::ValueEnum>::POSSIBLE_VALUES)
        });
    } else if !field.possible_values.is_empty() {
        let values = &field.possible_values;
        calls.push(quote! { .possible_values(&[#(#values),*]) });
    }
    if let Some(env) = &field.env {
        calls.push(quote! { .env(#env) });
    }
    if field.hide {
        calls.push(quote! { .hidden(true) });
    }
    if !matches!(field.kind, FieldKind::Flag(_)) && !field.is_optional() {
        calls.push(quote! { .required(true) });
    }
    if matches!(field.kind, FieldKind::Flag(FlagAction::Count)) || field.collection.is_some() {
        calls.push(quote! { .multiple(true) });
    }
    if field.parsing.last {
        calls.push(quote! { .last(true) });
    }
    if field.parsing.global {
        calls.push(quote! { .global(true) });
    }
    quote! {
        getargs_derive::ArgInfo::new(#id, getargs_derive::ArgKind::#kind) #(#calls)*
    }
}

fn names<T: quote::ToTokens>(aliases: &[&crate::model::Alias<T>]) -> Vec<TokenStream> {
    aliases
        .iter()
        .map(|alias| {
            let name = &alias.name;
            quote! { #name }
        })
        .collect()
}

pub(super) fn constants(input: &DeriveInput, model: &Model) -> TokenStream {
    let container = &model.container;
    let args = Merged {
        element: quote! { getargs_derive::ArgInfo },
        fill: quote! { getargs_derive::ArgInfo::new("", getargs_derive::ArgKind::Flag) },
        constant: "ARGS",
    }
    .tokens(model, |field| Some(vec![arg_info(field)]));
    let has_long = if model.flattened.is_empty() {
        let has_long = has_long_help(container, &model.fields);
        quote! { #has_long }
    } else {
        let args = static_self(input);
        let long_about = container.long_about.is_some();
        quote! { (#long_about || #args::HAS_LONG_HELP) }
    };
    let mut reserved = Vec::new();
    if !container.disable_help_flag {
        reserved.push(quote! { getargs_derive::__private::help_arg(#has_long) });
    }
    if container.version.is_some() {
        reserved.push(quote! { getargs_derive::__private::version_arg() });
    }
    let command_args = if model.flattened.is_empty() {
        let items = model.fields.iter().map(arg_info);
        quote! { &[#(#items,)* #(#reserved),*] }
    } else {
        let args = static_self(input);
        quote! {
            {
                const PARTS: &[&[getargs_derive::ArgInfo]] = &[#args::ARGS, &[#(#reserved),*]];
                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),
                );
                &MERGED
            }
        }
    };
    quote! {
        const ARGS: &'static [getargs_derive::ArgInfo] = #args;
        const COMMAND_ARGS: &'static [getargs_derive::ArgInfo] = #command_args;
    }
}

pub(super) fn command_impl(input: &DeriveInput, model: &Model) -> TokenStream {
    let container = &model.container;
    let name = &input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let command_name = command_name(container);
    let mut calls = Vec::new();
    if let Some(about) = container.about.as_deref().filter(|about| !about.is_empty()) {
        calls.push(quote! { .about(#about) });
    }
    if let Some(long_about) = container
        .long_about
        .as_deref()
        .filter(|about| !about.is_empty())
    {
        calls.push(quote! { .long_about(#long_about) });
    }
    if let Some(version) = version_value(container) {
        calls.push(quote! { .version(#version) });
    }
    if model.subcommand.is_some() {
        calls.push(quote! {
            .commands(<Self as getargs_derive::__private::Args<'static>>::COMMAND.infos)
            .subcommand_required(
                <Self as getargs_derive::__private::Args<'static>>::COMMAND.subcommand_required,
            )
        });
    }
    quote! {
        impl #impl_generics getargs_derive::Command for #name #ty_generics #where_clause {
            const COMMAND: getargs_derive::CommandInfo = getargs_derive::CommandInfo::new(#command_name)
                #(#calls)*
                .args(<Self as getargs_derive::__private::Args<'static>>::COMMAND_ARGS);
        }
    }
}