use crate::util::{Argument, Parenthesised}; use proc_macro2::Span; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens}; use syn::{ braced, parse::{Error, Parse, ParseStream, Result}, spanned::Spanned, Attribute, Block, FnArg, Ident, Pat, Path, PathSegment, ReturnType, Stmt, Token, Type, Visibility, }; fn parse_argument(arg: FnArg) -> Result { match arg { FnArg::Typed(typed) => { let pat = typed.pat; let kind = typed.ty; match *pat { Pat::Ident(id) => { let name = id.ident; let mutable = id.mutability; Ok(Argument { mutable, name, kind: *kind, }) } Pat::Wild(wild) => { let token = wild.underscore_token; let name = Ident::new("_", token.spans[0]); Ok(Argument { mutable: None, name, kind: *kind, }) } _ => Err(Error::new( pat.span(), format_args!("unsupported pattern: {:?}", pat), )), } } FnArg::Receiver(_) => Err(Error::new( arg.span(), format_args!("`self` arguments are prohibited: {:?}", arg), )), } } /// Test if the attribute is cooked. fn is_cooked(attr: &Attribute) -> bool { const COOKED_ATTRIBUTE_NAMES: &[&str] = &[ "cfg", "cfg_attr", "doc", "derive", "inline", "allow", "warn", "deny", "forbid", ]; COOKED_ATTRIBUTE_NAMES.iter().any(|n| attr.path.is_ident(n)) } /// Removes cooked attributes from a vector of attributes. Uncooked attributes are left in the vector. /// /// # Return /// /// Returns a vector of cooked attributes that have been removed from the input vector. fn remove_cooked(attrs: &mut Vec) -> Vec { let mut cooked = Vec::new(); // FIXME: Replace with `Vec::drain_filter` once it is stable. let mut i = 0; while i < attrs.len() { if !is_cooked(&attrs[i]) { i += 1; continue; } cooked.push(attrs.remove(i)); } cooked } #[derive(Debug)] pub struct CommandFun { /// `#[...]`-style attributes. pub attributes: Vec, /// Populated cooked attributes. These are attributes outside of the realm of this crate's procedural macros /// and will appear in generated output. pub cooked: Vec, pub visibility: Visibility, pub name: Ident, pub args: Vec, pub ret: Type, pub body: Vec, } impl Parse for CommandFun { fn parse(input: ParseStream<'_>) -> Result { let mut attributes = input.call(Attribute::parse_outer)?; // `#[doc = "..."]` is a cooked attribute but it is special-cased for commands. for attr in &mut attributes { // Rename documentation comment attributes (`#[doc = "..."]`) to `#[description = "..."]`. if attr.path.is_ident("doc") { attr.path = Path::from(PathSegment::from(Ident::new( "description", Span::call_site(), ))); } } let cooked = remove_cooked(&mut attributes); let visibility = input.parse::()?; input.parse::()?; input.parse::()?; let name = input.parse()?; // (...) let Parenthesised(args) = input.parse::>()?; let ret = match input.parse::()? { ReturnType::Type(_, t) => (*t).clone(), ReturnType::Default => { return Err(input .error("expected a result type of either `CommandResult` or `CheckResult`")) } }; // { ... } let bcont; braced!(bcont in input); let body = bcont.call(Block::parse_within)?; let args = args .into_iter() .map(parse_argument) .collect::>>()?; 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, ret, body, } = self; stream.extend(quote! { #(#cooked)* #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 { 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, Default)] pub struct Options { pub permission_level: PermissionLevel, pub supports_dm: bool, pub can_blacklist: bool, } impl Options { #[inline] pub fn new() -> Self { let mut options = Self::default(); options.can_blacklist = true; options.supports_dm = true; options } }