serenity_commands_macros/
lib.rs1#![allow(missing_docs)]
2mod basic_option;
7mod command;
8mod commands;
9mod field;
10mod sub_command;
11mod sub_command_group;
12mod utils;
13mod variant;
14
15use darling::{ast::NestedMeta, Error, FromDeriveInput, FromMeta};
16use proc_macro2::TokenStream;
17use quote::{quote, quote_spanned, ToTokens};
18use syn::{
19 parse::{Parse, Parser},
20 parse_macro_input,
21 punctuated::Punctuated,
22 token::Paren,
23 Expr, Ident, MacroDelimiter, Meta, Token,
24};
25
26#[derive(Debug)]
27struct DetachedMethodCall {
28 method: Ident,
29 #[allow(dead_code)]
30 paren_token: Paren,
31 args: Punctuated<Expr, Token![,]>,
32}
33
34impl Parse for DetachedMethodCall {
35 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
36 let content;
37
38 Ok(Self {
39 method: input.parse()?,
40 paren_token: syn::parenthesized!(content in input),
41 args: content.parse_terminated(Expr::parse, Token![,])?,
42 })
43 }
44}
45
46impl FromMeta for DetachedMethodCall {
47 fn from_meta(item: &Meta) -> darling::Result<Self> {
48 let Meta::List(list) = item else {
49 return Err(Error::unsupported_format("non-meta list"));
50 };
51
52 let method = list
53 .path
54 .get_ident()
55 .cloned()
56 .ok_or_else(|| Error::unsupported_format("non-ident path"))?;
57
58 let MacroDelimiter::Paren(paren_token) = list.delimiter else {
59 return Err(Error::unsupported_format("non-parenthesized arguments"));
60 };
61
62 let args = Punctuated::<Expr, Token![,]>::parse_terminated.parse2(list.tokens.clone())?;
63
64 Ok(Self {
65 method,
66 paren_token,
67 args,
68 })
69 }
70}
71
72#[derive(Debug)]
73struct BuilderMethodList {
74 methods: Vec<DetachedMethodCall>,
75}
76
77impl FromMeta for BuilderMethodList {
78 fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
79 let methods = items
80 .iter()
81 .map(DetachedMethodCall::from_nested_meta)
82 .collect::<darling::Result<_>>()?;
83
84 Ok(Self { methods })
85 }
86}
87
88impl ToTokens for BuilderMethodList {
89 fn to_tokens(&self, tokens: &mut TokenStream) {
90 let methods = self.methods.iter().map(|method| {
91 let method_name = &method.method;
92 let args = &method.args;
93
94 quote_spanned! {method_name.span()=>
95 .#method_name(#args)
96 }
97 });
98
99 tokens.extend(quote! {
100 #(#methods)*
101 });
102 }
103}
104
105#[proc_macro_derive(Commands, attributes(command))]
106pub fn derive_commands(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
107 commands::Args::from_derive_input(&parse_macro_input!(tokens))
108 .map_or_else(Error::write_errors, ToTokens::into_token_stream)
109 .into()
110}
111
112#[proc_macro_derive(Command, attributes(command))]
113pub fn derive_command(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
114 command::Args::from_derive_input(&parse_macro_input!(tokens))
115 .map_or_else(Error::write_errors, ToTokens::into_token_stream)
116 .into()
117}
118
119#[proc_macro_derive(SubCommandGroup, attributes(command))]
120pub fn derive_sub_command_group(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
121 sub_command_group::Args::from_derive_input(&parse_macro_input!(tokens))
122 .map_or_else(Error::write_errors, ToTokens::into_token_stream)
123 .into()
124}
125
126#[proc_macro_derive(SubCommand, attributes(command))]
127pub fn derive_sub_command(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
128 sub_command::Args::from_derive_input(&parse_macro_input!(tokens))
129 .map_or_else(Error::write_errors, ToTokens::into_token_stream)
130 .into()
131}
132
133#[proc_macro_derive(BasicOption, attributes(option))]
134pub fn derive_basic_option(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
135 basic_option::Args::from_derive_input(&parse_macro_input!(tokens))
136 .map_or_else(Error::write_errors, ToTokens::into_token_stream)
137 .into()
138}