ataraxy-macros 0.1.1

Proc-macros for Ataraxy
Documentation
mod actions;
mod params;

use crate::utils::MacroError::*;
use crate::utils::{quote_option, quote_vec, MacroError, Multiple};
use proc_macro::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
use syn::Lit::Str;
use syn::{FnArg, ItemFn, Meta};

#[derive(Default, Debug, darling::FromMeta)]
#[darling(default)]
pub struct CommandArgs {
    name: Option<String>,
    guild: Option<Multiple<u64>>,
    description: Option<String>,
}

fn extract_doc_comments(function: &ItemFn) -> Option<String> {
    let mut doc_lines = String::new();
    for attr in &function.attrs {
        if attr.path == quote::format_ident!("doc").into() {
            if let Ok(Meta::NameValue(nv)) = attr.parse_meta() {
                if let Str(literal) = nv.lit {
                    let literal = literal.value();
                    let literal = literal.strip_prefix(' ').unwrap_or(&literal);

                    doc_lines += literal;
                    doc_lines += "\n";
                }
            }
        }
    }
    if doc_lines.is_empty() {
        None
    } else {
        Some(doc_lines)
    }
}

pub fn command(args: CommandArgs, function: ItemFn) -> Result<TokenStream, MacroError> {
    let description = match args.description {
        Some(desc) => desc,
        None => extract_doc_comments(&function).ok_or_else(|| SynError(syn::Error::new(function.sig.span(), "You must provide a slash command description in either doc comments of the function or as the `description` parameter to the macro.")))?,
    };

    let name = args
        .name
        .unwrap_or_else(|| (&function.sig.ident).to_string());
    let func_name = &function.sig.ident;
    let visibility = &function.vis;
    if function.sig.asyncness.is_none() {
        return Err(SynError(syn::Error::new(
            function.sig.ident.span(),
            "Command handler must be marked as async",
        )));
    }

    let mut inner_function = function.clone();
    inner_function.sig.ident = syn::parse_quote! { inner };
    for i in &mut inner_function.sig.inputs {
        match i {
            FnArg::Receiver(_) => (),
            FnArg::Typed(t) => {
                t.attrs = vec![];
            }
        };
    }

    let parameters = params::get_args(&function)?;

    let signature = parameters.as_signature();

    let action =
        actions::create_slash_command_action(parameters.context, parameters.args.len() as u8);

    let guilds = quote_option(&args.guild.map(|g| quote_vec(&g.0)));

    Ok(quote! {
        #visibility fn #func_name() -> ::ataraxy::Command {
            #inner_function

            ::ataraxy::Command {
                name: #name.to_string(),
                description: #description.to_string(),
                arguments: #signature,
                guilds: #guilds,
                action: ::ataraxy::framework::command::CommandHandler(#action),
            }
        }
    }
    .into())
}