use crate::extractors::{Either, FixedList, FunctionPath, Map};
use crate::optional::Optional;
use crate::util;
use darling::FromMeta;
use darling::export::NestedMeta;
use proc_macro2::{Ident, TokenStream};
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::{FnArg, Type, LitStr, Error};
#[derive(FromMeta)]
pub struct ArgumentAttributes {
#[darling(default)]
pub localized_names: Option<Map<LitStr, LitStr>>,
#[darling(default)]
pub description: Option<Either<String, FixedList<1, String>>>,
#[darling(default)]
pub localized_descriptions: Option<Map<LitStr, LitStr>>,
#[darling(rename = "rename")]
pub renaming: Option<Either<String, FixedList<1, String>>>,
pub autocomplete: Optional<Either<FunctionPath, FixedList<1, FunctionPath>>>,
#[darling(default)]
pub skip: bool
}
pub struct Argument<'a> {
pub ident: Ident,
pub ty: Box<Type>,
pub attributes: Option<ArgumentAttributes>,
trait_type: &'a Type,
pub chat_command: bool
}
impl<'a> Argument<'a> {
pub fn new(mut arg: FnArg, trait_type: &'a Type, chat_command: bool) -> darling::Result<Self> {
let pat = util::get_pat_mut(&mut arg)?;
let ident = util::get_ident(&pat.pat)?;
let ty = pat.ty.clone();
let attributes = pat.attrs
.drain(..)
.map(|attribute| attribute.meta)
.map(NestedMeta::Meta)
.collect::<Vec<_>>();
let this = Self {
ident,
ty,
attributes: if chat_command {
Some(ArgumentAttributes::from_list(attributes.as_slice())?)
} else {
None
},
trait_type,
chat_command
};
if chat_command
&& !this.attributes.as_ref().map(|a| a.skip).unwrap_or(false)
&& this.attributes.as_ref().unwrap()
.description.as_ref().map(|d| d.inner().is_empty()).unwrap_or(true)
{
return Err(Error::new(
arg.span(),
"Missing `description`"
)).map_err(From::from);
}
Ok(this)
}
}
impl ToTokens for Argument<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.attributes.as_ref().map(|a| a.skip).unwrap_or(false) || !self.chat_command {
return;
}
let attributes = self.attributes.as_ref().unwrap();
let des = attributes.description.as_ref().unwrap().inner();
let ty = &self.ty;
let tt = &self.trait_type;
let argument_path = quote::quote!(::zephyrus::argument::CommandArgument);
let name = match &attributes.renaming {
Some(rename) => rename.inner().clone(),
None => self.ident.to_string(),
};
let add_localized_names = attributes.localized_names.as_ref().map(|map| {
let localized_names = map.pairs();
quote::quote!(.localized_names(vec![#(#localized_names),*]))
});
let add_localized_descriptions = attributes.localized_descriptions.as_ref().map(|map| {
let localized_descriptions = map.pairs();
quote::quote!(.localized_descriptions(vec![#(#localized_descriptions),*]))
});
let autocomplete = attributes.autocomplete.as_ref().map(|either| {
let inner = either.inner();
quote::quote!(#inner())
});
tokens.extend(quote::quote! {
.add_argument(#argument_path::<#tt>::new::<#ty>(
#name,
#des,
#autocomplete
)
#add_localized_names
#add_localized_descriptions
)
});
}
}