added functionality for reusable hook functions that will execute on commands
This commit is contained in:
		@@ -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])?;
 | 
			
		||||
 
 | 
			
		||||
@@ -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::*;
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user