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::bytes::{BYTES, Flavor, STR, os_args_method};
use super::validation::convert;
use crate::model::{FieldModel, Model};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

fn has_env(fields: &[FieldModel]) -> bool {
    fields.iter().any(|field| field.env.is_some())
}

pub(super) fn env_lookup(field: &FieldModel) -> Option<TokenStream> {
    let env = field.env.as_deref()?;
    let value_ident = format_ident!("__value_{}", field.name);
    let error_name = format!("{} [env: {env}]", field.error_name());
    let parsed = convert(field, quote! { value }, &error_name);
    Some(quote! {
        let #value_ident = match #value_ident {
            core::option::Option::Some(value) => core::option::Option::Some(value),
            core::option::Option::None => match __getargs_env(#env) {
                core::option::Option::Some(value) => core::option::Option::Some(#parsed),
                core::option::Option::None => core::option::Option::None,
            },
        };
    })
}

fn hidden_parse(lifetime_params: &[syn::Lifetime], body: TokenStream) -> TokenStream {
    quote! {
        #[doc(hidden)]
        pub fn __parse<'__getargs, __A, I, E>(
            options: &mut getargs::Options<__A, I>,
            __raw: core::option::Option<&core::cell::Cell<core::option::Option<&'__getargs [u8]>>>,
            mut __getargs_env: E,
        ) -> core::result::Result<Self, getargs_derive::Error<'__getargs>>
        where
            __A: getargs_derive::__private::Input<'__getargs>,
            I: Iterator<Item = __A>,
            E: FnMut(&str) -> core::option::Option<&'__getargs str>,
            #('__getargs: #lifetime_params,)*
        {
            #body
        }
    }
}

fn with_env_methods(flavor: &Flavor, lifetime_params: &[syn::Lifetime]) -> TokenStream {
    let input = flavor.input();
    let parse_with_env = format_ident!("{}", flavor.parse_with_env);
    let from_args_with_env = format_ident!("{}", flavor.from_args_with_env);
    let parse_doc = flavor.doc("Parse CLI arguments from an iterator, using `getargs::Options`, reading environment\nvariable fallbacks with `env`.\n\n`env` is called with the name of an environment variable and returns its value, or\n`None` when it is not set. It is only called for arguments absent from the command line.");
    let from_doc = flavor.doc(&format!(
        "Parse CLI arguments directly from an iterator, reading environment variable fallbacks\nwith `env`.\n\nConvenience wrapper that creates a `getargs::Options` internally\nand calls [`{0}`](Self::{0}).",
        flavor.parse_with_env
    ));
    quote! {
        #parse_doc
        pub fn #parse_with_env<'__getargs, I, E>(options: &mut getargs::Options<#input, I>, env: E) -> core::result::Result<Self, getargs_derive::Error<'__getargs>>
        where
            I: Iterator<Item = #input>,
            E: FnMut(&str) -> core::option::Option<&'__getargs str>,
            #('__getargs: #lifetime_params,)*
        {
            Self::__parse(options, core::option::Option::None, env)
        }

        #from_doc
        pub fn #from_args_with_env<'__getargs, I, E>(iter: I, env: E) -> core::result::Result<Self, getargs_derive::Error<'__getargs>>
        where
            I: Iterator<Item = #input>,
            E: FnMut(&str) -> core::option::Option<&'__getargs str>,
            #('__getargs: #lifetime_params,)*
        {
            let raw = core::cell::Cell::new(core::option::Option::None);
            let mut options = getargs::Options::new(getargs_derive::__RawArgs::new(iter, &raw));
            Self::__parse(&mut options, core::option::Option::Some(&raw), env)
        }
    }
}

fn own_env_methods(flavor: &Flavor, lifetime_params: &[syn::Lifetime]) -> TokenStream {
    let input = flavor.input();
    let parse = format_ident!("{}", flavor.parse);
    let from_args = format_ident!("{}", flavor.from_args);
    let with_env = with_env_methods(flavor, lifetime_params);
    let parse_doc = flavor.doc(&format!(
        "Parse CLI arguments from an iterator, using `getargs::Options`, reading environment\nvariable fallbacks from the process environment.\n\nThis requires the `std` feature of `getargs-derive`. Without it, use\n[`{0}`](Self::{0}) with your own lookup function.\nPrefer [`{1}`](Self::{1}) for convenience.",
        flavor.parse_with_env, flavor.from_args
    ));
    let from_doc = flavor.doc(&format!(
        "Parse CLI arguments directly from an iterator, reading environment variable fallbacks\nfrom the process environment.\n\nConvenience wrapper that creates a `getargs::Options` internally\nand calls [`{0}`](Self::{0}). This requires the `std` feature of `getargs-derive`.\nWithout it, use [`{1}`](Self::{1}).",
        flavor.parse, flavor.from_args_with_env
    ));
    quote! {
        #parse_doc
        pub fn #parse<'__getargs, I>(options: &mut getargs::Options<#input, I>) -> core::result::Result<Self, getargs_derive::Error<'__getargs>>
        where
            I: Iterator<Item = #input> + getargs_derive::__EnvSupport,
            #('__getargs: #lifetime_params,)*
        {
            Self::__parse(options, core::option::Option::None, getargs_derive::__env)
        }

        #with_env

        #from_doc
        pub fn #from_args<'__getargs, I>(iter: I) -> core::result::Result<Self, getargs_derive::Error<'__getargs>>
        where
            I: Iterator<Item = #input> + getargs_derive::__EnvSupport,
            #('__getargs: #lifetime_params,)*
        {
            let raw = core::cell::Cell::new(core::option::Option::None);
            let mut options = getargs::Options::new(getargs_derive::__RawArgs::new(iter, &raw));
            Self::__parse(&mut options, core::option::Option::Some(&raw), getargs_derive::__env)
        }
    }
}

fn plain_methods(flavor: &Flavor, model: &Model, lifetime_params: &[syn::Lifetime]) -> TokenStream {
    let input = flavor.input();
    let parse = format_ident!("{}", flavor.parse);
    let from_args = format_ident!("{}", flavor.from_args);
    let (lookup, bound, with_env) = if model.flattened.is_empty() && model.subcommand.is_none() {
        (
            quote! { getargs_derive::__private::no_env },
            quote! {},
            quote! {},
        )
    } else {
        (
            quote! { getargs_derive::__env },
            quote! {
                + getargs_derive::__private::EnvSupport<
                    <Self as getargs_derive::__private::Args<'__getargs>>::Env,
                >
            },
            with_env_methods(flavor, lifetime_params),
        )
    };
    let parse_doc = flavor.doc(&format!(
        "Parse CLI arguments from an iterator, using `getargs::Options`.\n\nThis is the low-level method. Prefer [`{0}`](Self::{0}) for convenience.",
        flavor.from_args
    ));
    let from_doc = flavor.doc(&format!(
        "Parse CLI arguments directly from an iterator.\n\nConvenience wrapper that creates a `getargs::Options` internally\nand calls [`{0}`](Self::{0}).{1}",
        flavor.parse, flavor.example
    ));
    quote! {
        #parse_doc
        pub fn #parse<'__getargs, I>(options: &mut getargs::Options<#input, I>) -> core::result::Result<Self, getargs_derive::Error<'__getargs>>
        where
            I: Iterator<Item = #input> #bound,
            #('__getargs: #lifetime_params,)*
        {
            Self::__parse(options, core::option::Option::None, #lookup)
        }

        #with_env

        #from_doc
        pub fn #from_args<'__getargs, I>(iter: I) -> core::result::Result<Self, getargs_derive::Error<'__getargs>>
        where
            I: Iterator<Item = #input> #bound,
            #('__getargs: #lifetime_params,)*
        {
            let raw = core::cell::Cell::new(core::option::Option::None);
            let mut options = getargs::Options::new(getargs_derive::__RawArgs::new(iter, &raw));
            Self::__parse(&mut options, core::option::Option::Some(&raw), #lookup)
        }
    }
}

pub(super) fn parse_methods(
    model: &Model,
    lifetime_params: &[syn::Lifetime],
    body: TokenStream,
) -> TokenStream {
    let hidden = hidden_parse(lifetime_params, body);
    let flavors = [&STR, &BYTES];
    let methods = flavors.map(|flavor| {
        if has_env(&model.fields) {
            own_env_methods(flavor, lifetime_params)
        } else {
            plain_methods(flavor, model, lifetime_params)
        }
    });
    let os_args = os_args_method();
    quote! {
        #(#methods)*

        #os_args

        #hidden
    }
}