185 lines
4.9 KiB
Rust

#![deny(rust_2018_idioms)]
#![deny(broken_intra_doc_links)]
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use syn::{parse::Error, parse_macro_input, parse_quote, spanned::Spanned, Lit, Type};
pub(crate) mod attributes;
pub(crate) mod consts;
pub(crate) mod structures;
#[macro_use]
pub(crate) mod util;
use attributes::*;
use consts::*;
use structures::*;
use util::*;
macro_rules! match_options {
($v:expr, $values:ident, $options:ident, $span:expr => [$($name:ident);*]) => {
match $v {
$(
stringify!($name) => $options.$name = propagate_err!($crate::attributes::parse($values)),
)*
_ => {
return Error::new($span, format_args!("invalid attribute: {:?}", $v))
.to_compile_error()
.into();
},
}
};
}
#[proc_macro_attribute]
pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
let mut fun = parse_macro_input!(input as CommandFun);
let _name = if !attr.is_empty() {
parse_macro_input!(attr as Lit).to_str()
} else {
fun.name.to_string()
};
let mut options = Options::new();
for attribute in &fun.attributes {
let span = attribute.span();
let values = propagate_err!(parse_values(attribute));
let name = values.name.to_string();
let name = &name[..];
match name {
"arg" => options
.cmd_args
.push(propagate_err!(attributes::parse(values))),
"example" => {
options
.examples
.push(propagate_err!(attributes::parse(values)));
}
"description" => {
let line: String = propagate_err!(attributes::parse(values));
util::append_line(&mut options.description, line);
}
_ => {
match_options!(name, values, options, span => [
aliases;
group;
required_permissions;
can_blacklist;
supports_dm
]);
}
}
}
let Options {
aliases,
description,
group,
examples,
required_permissions,
can_blacklist,
supports_dm,
mut cmd_args,
} = options;
let visibility = fun.visibility;
let name = fun.name.clone();
let body = fun.body;
let n = name.with_suffix(COMMAND);
let cooked = fun.cooked.clone();
let command_path = quote!(crate::framework::Command);
let arg_path = quote!(crate::framework::Arg);
populate_fut_lifetimes_on_refs(&mut fun.args);
let args = fun.args;
let arg_idents = cmd_args
.iter()
.map(|arg| {
n.with_suffix(arg.name.replace(" ", "_").replace("-", "_").as_str())
.with_suffix(ARG)
})
.collect::<Vec<Ident>>();
let mut tokens = cmd_args
.iter_mut()
.map(|arg| {
let Arg {
name,
description,
kind,
required,
} = arg;
let an = n.with_suffix(name.as_str()).with_suffix(ARG);
quote! {
#(#cooked)*
#[allow(missing_docs)]
pub static #an: #arg_path = #arg_path {
name: #name,
description: #description,
kind: #kind,
required: #required,
};
}
})
.fold(quote! {}, |mut a, b| {
a.extend(b);
a
});
let variant = if args.len() == 2 {
quote!(crate::framework::CommandFnType::Multi)
} else {
let string: Type = parse_quote!(std::string::String);
let final_arg = args.get(2).unwrap();
if final_arg.kind == string {
quote!(crate::framework::CommandFnType::Text)
} else {
quote!(crate::framework::CommandFnType::Slash)
}
};
tokens.extend(quote! {
#(#cooked)*
#[allow(missing_docs)]
pub static #n: #command_path = #command_path {
fun: #variant(#name),
names: &[#_name, #(#aliases),*],
desc: #description,
group: #group,
examples: &[#(#examples),*],
required_permissions: #required_permissions,
can_blacklist: #can_blacklist,
supports_dm: #supports_dm,
args: &[#(&#arg_idents),*],
};
});
tokens.extend(quote! {
#(#cooked)*
#[allow(missing_docs)]
#visibility fn #name<'fut> (#(#args),*) -> ::serenity::futures::future::BoxFuture<'fut, ()> {
use ::serenity::futures::future::FutureExt;
async move {
#(#body)*;
}.boxed()
}
});
tokens.into()
}