added functionality for reusable hook functions that will execute on commands

This commit is contained in:
2021-09-22 21:12:29 +01:00
parent a0974795e1
commit d84d7ab62b
18 changed files with 864 additions and 954 deletions

View File

@ -13,3 +13,4 @@ proc-macro = true
quote = "^1.0"
syn = { version = "^1.0", features = ["full", "derive", "extra-traits"] }
proc-macro2 = "1.0"
uuid = { version = "0.8", features = ["v4"] }

View File

@ -8,7 +8,7 @@ use syn::{
};
use crate::{
structures::{ApplicationCommandOptionType, Arg, PermissionLevel},
structures::{ApplicationCommandOptionType, Arg},
util::{AsOption, LitExt},
};
@ -46,24 +46,15 @@ impl fmt::Display for ValueKind {
fn to_ident(p: Path) -> Result<Ident> {
if p.segments.is_empty() {
return Err(Error::new(
p.span(),
"cannot convert an empty path to an identifier",
));
return Err(Error::new(p.span(), "cannot convert an empty path to an identifier"));
}
if p.segments.len() > 1 {
return Err(Error::new(
p.span(),
"the path must not have more than one segment",
));
return Err(Error::new(p.span(), "the path must not have more than one segment"));
}
if !p.segments[0].arguments.is_empty() {
return Err(Error::new(
p.span(),
"the singular path segment must not have any arguments",
));
return Err(Error::new(p.span(), "the singular path segment must not have any arguments"));
}
Ok(p.segments[0].ident.clone())
@ -85,12 +76,7 @@ impl Values {
literals: Vec<(Option<String>, Lit)>,
span: Span,
) -> Self {
Values {
name,
literals,
kind,
span,
}
Values { name, literals, kind, span }
}
}
@ -145,11 +131,7 @@ pub fn parse_values(attr: &Attribute) -> Result<Values> {
}
}
let kind = if lits.len() == 1 {
ValueKind::SingleList
} else {
ValueKind::List
};
let kind = if lits.len() == 1 { ValueKind::SingleList } else { ValueKind::List };
Ok(Values::new(name, kind, lits, attr.span()))
} else {
@ -183,12 +165,7 @@ pub fn parse_values(attr: &Attribute) -> Result<Values> {
let name = to_ident(meta.path)?;
let lit = meta.lit;
Ok(Values::new(
name,
ValueKind::Equals,
vec![(None, lit)],
attr.span(),
))
Ok(Values::new(name, ValueKind::Equals, vec![(None, lit)], attr.span()))
}
}
}
@ -231,10 +208,7 @@ fn validate(values: &Values, forms: &[ValueKind]) -> Result<()> {
return Err(Error::new(
values.span,
// Using the `_args` version here to avoid an allocation.
format_args!(
"the attribute must be in of these forms:\n{}",
DisplaySlice(forms)
),
format_args!("the attribute must be in of these forms:\n{}", DisplaySlice(forms)),
));
}
@ -254,11 +228,7 @@ impl AttributeOption for Vec<String> {
fn parse(values: Values) -> Result<Self> {
validate(&values, &[ValueKind::List])?;
Ok(values
.literals
.into_iter()
.map(|(_, l)| l.to_str())
.collect())
Ok(values.literals.into_iter().map(|(_, l)| l.to_str()).collect())
}
}
@ -294,37 +264,18 @@ impl AttributeOption for Vec<Ident> {
fn parse(values: Values) -> Result<Self> {
validate(&values, &[ValueKind::List])?;
Ok(values
.literals
.into_iter()
.map(|(_, l)| l.to_ident())
.collect())
Ok(values.literals.into_iter().map(|(_, l)| l.to_ident()).collect())
}
}
impl AttributeOption for Option<String> {
fn parse(values: Values) -> Result<Self> {
validate(
&values,
&[ValueKind::Name, ValueKind::Equals, ValueKind::SingleList],
)?;
validate(&values, &[ValueKind::Name, ValueKind::Equals, ValueKind::SingleList])?;
Ok(values.literals.get(0).map(|(_, l)| l.to_str()))
}
}
impl AttributeOption for PermissionLevel {
fn parse(values: Values) -> Result<Self> {
validate(&values, &[ValueKind::SingleList])?;
Ok(values
.literals
.get(0)
.map(|(_, l)| PermissionLevel::from_str(&*l.to_str()).unwrap())
.unwrap())
}
}
impl AttributeOption for Arg {
fn parse(values: Values) -> Result<Self> {
validate(&values, &[ValueKind::EqualsList])?;

View File

@ -2,6 +2,8 @@ pub mod suffixes {
pub const COMMAND: &str = "COMMAND";
pub const ARG: &str = "ARG";
pub const SUBCOMMAND: &str = "SUBCOMMAND";
pub const CHECK: &str = "CHECK";
pub const HOOK: &str = "HOOK";
}
pub use self::suffixes::*;

View File

@ -5,6 +5,7 @@ use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use syn::{parse::Error, parse_macro_input, parse_quote, spanned::Spanned, Lit, Type};
use uuid::Uuid;
pub(crate) mod attributes;
pub(crate) mod consts;
@ -43,6 +44,7 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
fun.name.to_string()
};
let mut hooks: Vec<Ident> = Vec::new();
let mut options = Options::new();
for attribute in &fun.attributes {
@ -76,11 +78,13 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
util::append_line(&mut options.description, line);
}
}
"hook" => {
hooks.push(propagate_err!(attributes::parse(values)));
}
_ => {
match_options!(name, values, options, span => [
aliases;
group;
required_permissions;
can_blacklist;
supports_dm
]);
@ -93,7 +97,6 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
description,
group,
examples,
required_permissions,
can_blacklist,
supports_dm,
mut cmd_args,
@ -235,10 +238,10 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
desc: #description,
group: #group,
examples: &[#(#examples),*],
required_permissions: #required_permissions,
can_blacklist: #can_blacklist,
supports_dm: #supports_dm,
args: &[#(&#arg_idents),*],
hooks: &[#(&#hooks),*],
};
});
@ -256,3 +259,44 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
tokens.into()
}
#[proc_macro_attribute]
pub fn check(_attr: TokenStream, input: TokenStream) -> TokenStream {
let mut fun = parse_macro_input!(input as CommandFun);
let n = fun.name.clone();
let name = n.with_suffix(HOOK);
let fn_name = n.with_suffix(CHECK);
let visibility = fun.visibility;
let cooked = fun.cooked;
let body = fun.body;
let ret = fun.ret;
populate_fut_lifetimes_on_refs(&mut fun.args);
let args = fun.args;
let hook_path = quote!(crate::framework::Hook);
let uuid = Uuid::new_v4().as_u128();
(quote! {
#(#cooked)*
#[allow(missing_docs)]
#visibility fn #fn_name<'fut>(#(#args),*) -> ::serenity::futures::future::BoxFuture<'fut, #ret> {
use ::serenity::futures::future::FutureExt;
async move {
let _output: #ret = { #(#body)* };
#[allow(unreachable_code)]
_output
}.boxed()
}
#(#cooked)*
#[allow(missing_docs)]
pub static #name: #hook_path = #hook_path {
fun: #fn_name,
uuid: #uuid,
};
})
.into()
}

View File

@ -4,7 +4,7 @@ use syn::{
braced,
parse::{Error, Parse, ParseStream, Result},
spanned::Spanned,
Attribute, Block, FnArg, Ident, Pat, Stmt, Token, Visibility,
Attribute, Block, FnArg, Ident, Pat, ReturnType, Stmt, Token, Type, Visibility,
};
use crate::util::{Argument, Parenthesised};
@ -78,6 +78,7 @@ pub struct CommandFun {
pub visibility: Visibility,
pub name: Ident,
pub args: Vec<Argument>,
pub ret: Type,
pub body: Vec<Stmt>,
}
@ -97,6 +98,11 @@ impl Parse for CommandFun {
// (...)
let Parenthesised(args) = input.parse::<Parenthesised<FnArg>>()?;
let ret = match input.parse::<ReturnType>()? {
ReturnType::Type(_, t) => (*t).clone(),
ReturnType::Default => Type::Verbatim(quote!(())),
};
// { ... }
let bcont;
braced!(bcont in input);
@ -104,72 +110,23 @@ impl Parse for CommandFun {
let args = args.into_iter().map(parse_argument).collect::<Result<Vec<_>>>()?;
Ok(Self { attributes, cooked, visibility, name, args, body })
Ok(Self { attributes, cooked, visibility, name, args, ret, body })
}
}
impl ToTokens for CommandFun {
fn to_tokens(&self, stream: &mut TokenStream2) {
let Self { attributes: _, cooked, visibility, name, args, body } = self;
let Self { attributes: _, cooked, visibility, name, args, ret, body } = self;
stream.extend(quote! {
#(#cooked)*
#visibility async fn #name (#(#args),*) {
#visibility async fn #name (#(#args),*) -> #ret {
#(#body)*
}
});
}
}
#[derive(Debug)]
pub enum PermissionLevel {
Unrestricted,
Managed,
Restricted,
}
impl Default for PermissionLevel {
fn default() -> Self {
Self::Unrestricted
}
}
impl PermissionLevel {
pub fn from_str(s: &str) -> Option<Self> {
Some(match s.to_uppercase().as_str() {
"UNRESTRICTED" => Self::Unrestricted,
"MANAGED" => Self::Managed,
"RESTRICTED" => Self::Restricted,
_ => return None,
})
}
}
impl ToTokens for PermissionLevel {
fn to_tokens(&self, stream: &mut TokenStream2) {
let path = quote!(crate::framework::PermissionLevel);
let variant;
match self {
Self::Unrestricted => {
variant = quote!(Unrestricted);
}
Self::Managed => {
variant = quote!(Managed);
}
Self::Restricted => {
variant = quote!(Restricted);
}
}
stream.extend(quote! {
#path::#variant
});
}
}
#[derive(Debug)]
pub(crate) enum ApplicationCommandOptionType {
SubCommand,
@ -272,7 +229,6 @@ pub(crate) struct Options {
pub description: String,
pub group: String,
pub examples: Vec<String>,
pub required_permissions: PermissionLevel,
pub can_blacklist: bool,
pub supports_dm: bool,
pub cmd_args: Vec<Arg>,