pistonite-cu-proc-macros 0.2.9

Proc-macros for Cu
Documentation
use pm::pre::*;

pub fn expand(attr: TokenStream, input: TokenStream) -> pm::Result<TokenStream2> {
    let attrs = parse_attributes(attr)?;

    let mut item: syn::ItemFn = syn::parse(input)?;
    let is_async = item.sig.asyncness.is_some();

    let generated_main_name = {
        let ident = &item.sig.ident;
        let ident_str = ident.to_string();
        let gen_ident = format!("__cu{}{ident_str}", ident_str.len());
        syn::Ident::new(&gen_ident, ident.span())
    };

    let fn_flag_impl = match attrs.flags_ident {
        Some(flags) => pm::quote! { |x| &x.#flags },
        None => pm::quote! { |x| x.as_ref() },
    };

    let fn_preproc_impl = match attrs.preprocess_fn {
        Some(value) => pm::quote! { { #value } },
        None => pm::quote! { (|_| {}) },
    };

    let fn_log_config_impl = match attrs.log_config {
        Some(value) => pm::quote! { { #value } },
        None => pm::quote! { |_| cu::cli::DefaultLogConfig  },
    };

    let main_impl = if is_async {
        pm::quote! {
            cu::cli::__co_run(
                #fn_preproc_impl,
                #fn_log_config_impl,
                #generated_main_name,
                #fn_flag_impl
            )
        }
    } else {
        pm::quote! {
            cu::cli::__run(
                #fn_preproc_impl,
                #fn_log_config_impl,
                #generated_main_name,
                #fn_flag_impl
            )
        }
    };

    let old_name = {
        let mut new_name = generated_main_name;
        std::mem::swap(&mut new_name, &mut item.sig.ident);
        new_name
    };

    let expanded = pm::quote! {
        fn #old_name() -> std::process::ExitCode {
            unsafe { #main_impl }
        }
        #item
    };

    Ok(expanded)
}

fn parse_attributes(attr: TokenStream) -> pm::Result<ParsedAttributes> {
    let attrs = pm::parse_punctuated::<syn::MetaNameValue, syn::Token![,]>(attr)?;
    let mut out = ParsedAttributes::default();

    for attr in attrs {
        if attr.path.is_ident("flags") {
            let syn::Expr::Lit(syn::ExprLit {
                lit: syn::Lit::Str(s),
                ..
            }) = attr.value
            else {
                pm::bail!(attr.value, "expecting string literal");
            };
            let ident: syn::Ident = s.parse()?;
            out.flags_ident = Some(ident);
            continue;
        }
        if attr.path.is_ident("preprocess") {
            out.preprocess_fn = Some(attr.value);
            continue;
        }
        if attr.path.is_ident("log_config") {
            out.log_config = Some(attr.value);
            continue;
        }
        pm::bail!(attr, "unknown attribute");
    }
    Ok(out)
}
#[derive(Default)]
struct ParsedAttributes {
    flags_ident: Option<syn::Ident>,
    preprocess_fn: Option<syn::Expr>,
    log_config: Option<syn::Expr>,
}