subcommand group syntax
This commit is contained in:
@ -2,6 +2,7 @@ pub mod suffixes {
|
||||
pub const COMMAND: &str = "COMMAND";
|
||||
pub const ARG: &str = "ARG";
|
||||
pub const SUBCOMMAND: &str = "SUBCOMMAND";
|
||||
pub const SUBCOMMAND_GROUP: &str = "GROUP";
|
||||
pub const CHECK: &str = "CHECK";
|
||||
pub const HOOK: &str = "HOOK";
|
||||
}
|
||||
|
@ -36,6 +36,13 @@ macro_rules! match_options {
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
enum LastItem {
|
||||
Fun,
|
||||
SubFun,
|
||||
SubGroup,
|
||||
SubGroupFun,
|
||||
}
|
||||
|
||||
let mut fun = parse_macro_input!(input as CommandFun);
|
||||
|
||||
let _name = if !attr.is_empty() {
|
||||
@ -46,6 +53,7 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
|
||||
let mut hooks: Vec<Ident> = Vec::new();
|
||||
let mut options = Options::new();
|
||||
let mut last_desc = LastItem::Fun;
|
||||
|
||||
for attribute in &fun.attributes {
|
||||
let span = attribute.span();
|
||||
@ -56,15 +64,39 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
|
||||
match name {
|
||||
"subcommand" => {
|
||||
options
|
||||
.subcommands
|
||||
.push(Subcommand::new(propagate_err!(attributes::parse(values))));
|
||||
let new_subcommand = Subcommand::new(propagate_err!(attributes::parse(values)));
|
||||
|
||||
if let Some(subcommand_group) = options.subcommand_groups.last_mut() {
|
||||
last_desc = LastItem::SubGroupFun;
|
||||
subcommand_group.subcommands.push(new_subcommand);
|
||||
} else {
|
||||
last_desc = LastItem::SubFun;
|
||||
options.subcommands.push(new_subcommand);
|
||||
}
|
||||
}
|
||||
"subcommandgroup" => {
|
||||
let new_group = SubcommandGroup::new(propagate_err!(attributes::parse(values)));
|
||||
last_desc = LastItem::SubGroup;
|
||||
|
||||
options.subcommand_groups.push(new_group);
|
||||
}
|
||||
"arg" => {
|
||||
if let Some(subcommand) = options.subcommands.last_mut() {
|
||||
subcommand.cmd_args.push(propagate_err!(attributes::parse(values)));
|
||||
if let Some(subcommand_group) = options.subcommand_groups.last_mut() {
|
||||
if let Some(subcommand) = subcommand_group.subcommands.last_mut() {
|
||||
subcommand.cmd_args.push(propagate_err!(attributes::parse(values)));
|
||||
} else {
|
||||
if let Some(subcommand) = options.subcommands.last_mut() {
|
||||
subcommand.cmd_args.push(propagate_err!(attributes::parse(values)));
|
||||
} else {
|
||||
options.cmd_args.push(propagate_err!(attributes::parse(values)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
options.cmd_args.push(propagate_err!(attributes::parse(values)));
|
||||
if let Some(subcommand) = options.subcommands.last_mut() {
|
||||
subcommand.cmd_args.push(propagate_err!(attributes::parse(values)));
|
||||
} else {
|
||||
options.cmd_args.push(propagate_err!(attributes::parse(values)));
|
||||
}
|
||||
}
|
||||
}
|
||||
"example" => {
|
||||
@ -72,10 +104,36 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
}
|
||||
"description" => {
|
||||
let line: String = propagate_err!(attributes::parse(values));
|
||||
if let Some(subcommand) = options.subcommands.last_mut() {
|
||||
util::append_line(&mut subcommand.description, line);
|
||||
} else {
|
||||
util::append_line(&mut options.description, line);
|
||||
|
||||
match last_desc {
|
||||
LastItem::Fun => {
|
||||
util::append_line(&mut options.description, line);
|
||||
}
|
||||
LastItem::SubFun => {
|
||||
util::append_line(
|
||||
&mut options.subcommands.last_mut().unwrap().description,
|
||||
line,
|
||||
);
|
||||
}
|
||||
LastItem::SubGroup => {
|
||||
util::append_line(
|
||||
&mut options.subcommand_groups.last_mut().unwrap().description,
|
||||
line,
|
||||
);
|
||||
}
|
||||
LastItem::SubGroupFun => {
|
||||
util::append_line(
|
||||
&mut options
|
||||
.subcommand_groups
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.subcommands
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.description,
|
||||
line,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"hook" => {
|
||||
@ -101,120 +159,81 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
supports_dm,
|
||||
mut cmd_args,
|
||||
mut subcommands,
|
||||
mut subcommand_groups,
|
||||
} = 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 root_ident = name.with_suffix(COMMAND);
|
||||
|
||||
let command_path = quote!(crate::framework::Command);
|
||||
let arg_path = quote!(crate::framework::Arg);
|
||||
let subcommand_path = ApplicationCommandOptionType::SubCommand;
|
||||
|
||||
populate_fut_lifetimes_on_refs(&mut fun.args);
|
||||
let args = fun.args;
|
||||
|
||||
let mut subcommand_group_idents = subcommand_groups
|
||||
.iter()
|
||||
.map(|subcommand| {
|
||||
root_ident
|
||||
.with_suffix(subcommand.name.replace("-", "_").as_str())
|
||||
.with_suffix(SUBCOMMAND_GROUP)
|
||||
})
|
||||
.collect::<Vec<Ident>>();
|
||||
|
||||
let mut subcommand_idents = subcommands
|
||||
.iter()
|
||||
.map(|subcommand| {
|
||||
n.with_suffix(subcommand.name.replace("-", "_").as_str()).with_suffix(SUBCOMMAND)
|
||||
root_ident
|
||||
.with_suffix(subcommand.name.replace("-", "_").as_str())
|
||||
.with_suffix(SUBCOMMAND)
|
||||
})
|
||||
.collect::<Vec<Ident>>();
|
||||
|
||||
let mut tokens = subcommands
|
||||
.iter_mut()
|
||||
.zip(subcommand_idents.iter())
|
||||
.map(|(subcommand, sc_ident)| {
|
||||
let arg_idents = subcommand
|
||||
.cmd_args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
n.with_suffix(subcommand.name.as_str())
|
||||
.with_suffix(arg.name.as_str())
|
||||
.with_suffix(ARG)
|
||||
})
|
||||
.collect::<Vec<Ident>>();
|
||||
|
||||
let mut tokens = subcommand
|
||||
.cmd_args
|
||||
.iter_mut()
|
||||
.zip(arg_idents.iter())
|
||||
.map(|(arg, ident)| {
|
||||
let Arg { name, description, kind, required } = arg;
|
||||
|
||||
quote! {
|
||||
#(#cooked)*
|
||||
#[allow(missing_docs)]
|
||||
pub static #ident: #arg_path = #arg_path {
|
||||
name: #name,
|
||||
description: #description,
|
||||
kind: #kind,
|
||||
required: #required,
|
||||
options: &[]
|
||||
};
|
||||
}
|
||||
})
|
||||
.fold(quote! {}, |mut a, b| {
|
||||
a.extend(b);
|
||||
a
|
||||
});
|
||||
|
||||
let Subcommand { name, description, .. } = subcommand;
|
||||
|
||||
tokens.extend(quote! {
|
||||
#(#cooked)*
|
||||
#[allow(missing_docs)]
|
||||
pub static #sc_ident: #arg_path = #arg_path {
|
||||
name: #name,
|
||||
description: #description,
|
||||
kind: #subcommand_path,
|
||||
required: false,
|
||||
options: &[#(&#arg_idents),*],
|
||||
};
|
||||
});
|
||||
|
||||
tokens
|
||||
})
|
||||
.fold(quote! {}, |mut a, b| {
|
||||
a.extend(b);
|
||||
a
|
||||
});
|
||||
|
||||
let mut arg_idents = cmd_args
|
||||
.iter()
|
||||
.map(|arg| n.with_suffix(arg.name.replace("-", "_").as_str()).with_suffix(ARG))
|
||||
.map(|arg| root_ident.with_suffix(arg.name.replace("-", "_").as_str()).with_suffix(ARG))
|
||||
.collect::<Vec<Ident>>();
|
||||
|
||||
let arg_tokens = cmd_args
|
||||
.iter_mut()
|
||||
.zip(arg_idents.iter())
|
||||
.map(|(arg, ident)| {
|
||||
let Arg { name, description, kind, required } = arg;
|
||||
let mut tokens = quote! {};
|
||||
|
||||
quote! {
|
||||
#(#cooked)*
|
||||
#[allow(missing_docs)]
|
||||
pub static #ident: #arg_path = #arg_path {
|
||||
name: #name,
|
||||
description: #description,
|
||||
kind: #kind,
|
||||
required: #required,
|
||||
options: &[],
|
||||
};
|
||||
}
|
||||
})
|
||||
.fold(quote! {}, |mut a, b| {
|
||||
a.extend(b);
|
||||
a
|
||||
});
|
||||
tokens.extend(
|
||||
subcommand_groups
|
||||
.iter_mut()
|
||||
.zip(subcommand_group_idents.iter())
|
||||
.map(|(group, group_ident)| group.as_tokens(group_ident))
|
||||
.fold(quote! {}, |mut a, b| {
|
||||
a.extend(b);
|
||||
a
|
||||
}),
|
||||
);
|
||||
|
||||
tokens.extend(arg_tokens);
|
||||
tokens.extend(
|
||||
subcommands
|
||||
.iter_mut()
|
||||
.zip(subcommand_idents.iter())
|
||||
.map(|(subcommand, sc_ident)| subcommand.as_tokens(sc_ident))
|
||||
.fold(quote! {}, |mut a, b| {
|
||||
a.extend(b);
|
||||
a
|
||||
}),
|
||||
);
|
||||
|
||||
tokens.extend(
|
||||
cmd_args.iter_mut().zip(arg_idents.iter()).map(|(arg, ident)| arg.as_tokens(ident)).fold(
|
||||
quote! {},
|
||||
|mut a, b| {
|
||||
a.extend(b);
|
||||
a
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
arg_idents.append(&mut subcommand_group_idents);
|
||||
arg_idents.append(&mut subcommand_idents);
|
||||
|
||||
let args = fun.args;
|
||||
|
||||
let variant = if args.len() == 2 {
|
||||
quote!(crate::framework::CommandFnType::Multi)
|
||||
} else {
|
||||
@ -230,9 +249,8 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
};
|
||||
|
||||
tokens.extend(quote! {
|
||||
#(#cooked)*
|
||||
#[allow(missing_docs)]
|
||||
pub static #n: #command_path = #command_path {
|
||||
pub static #root_ident: #command_path = #command_path {
|
||||
fun: #variant(#name),
|
||||
names: &[#_name, #(#aliases),*],
|
||||
desc: #description,
|
||||
@ -243,10 +261,7 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
args: &[#(&#arg_idents),*],
|
||||
hooks: &[#(&#hooks),*],
|
||||
};
|
||||
});
|
||||
|
||||
tokens.extend(quote! {
|
||||
#(#cooked)*
|
||||
#[allow(missing_docs)]
|
||||
#visibility fn #name<'fut> (#(#args),*) -> ::serenity::futures::future::BoxFuture<'fut, ()> {
|
||||
use ::serenity::futures::future::FutureExt;
|
||||
@ -269,7 +284,6 @@ pub fn check(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
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);
|
||||
@ -279,7 +293,6 @@ pub fn check(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
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;
|
||||
@ -291,7 +304,6 @@ pub fn check(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
}.boxed()
|
||||
}
|
||||
|
||||
#(#cooked)*
|
||||
#[allow(missing_docs)]
|
||||
pub static #name: #hook_path = #hook_path {
|
||||
fun: #fn_name,
|
||||
|
@ -7,7 +7,10 @@ use syn::{
|
||||
Attribute, Block, FnArg, Ident, Pat, ReturnType, Stmt, Token, Type, Visibility,
|
||||
};
|
||||
|
||||
use crate::util::{Argument, Parenthesised};
|
||||
use crate::{
|
||||
consts::{ARG, SUBCOMMAND},
|
||||
util::{Argument, IdentExt2, Parenthesised},
|
||||
};
|
||||
|
||||
fn parse_argument(arg: FnArg) -> Result<Argument> {
|
||||
match arg {
|
||||
@ -38,43 +41,12 @@ fn parse_argument(arg: FnArg) -> Result<Argument> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Test if the attribute is cooked.
|
||||
fn is_cooked(attr: &Attribute) -> bool {
|
||||
const COOKED_ATTRIBUTE_NAMES: &[&str] =
|
||||
&["cfg", "cfg_attr", "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<Attribute>) -> Vec<Attribute> {
|
||||
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<Attribute>,
|
||||
/// 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<Attribute>,
|
||||
pub visibility: Visibility,
|
||||
pub name: Ident,
|
||||
pub args: Vec<Argument>,
|
||||
@ -84,9 +56,7 @@ pub struct CommandFun {
|
||||
|
||||
impl Parse for CommandFun {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let mut attributes = input.call(Attribute::parse_outer)?;
|
||||
|
||||
let cooked = remove_cooked(&mut attributes);
|
||||
let attributes = input.call(Attribute::parse_outer)?;
|
||||
|
||||
let visibility = input.parse::<Visibility>()?;
|
||||
|
||||
@ -110,16 +80,15 @@ impl Parse for CommandFun {
|
||||
|
||||
let args = args.into_iter().map(parse_argument).collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(Self { attributes, cooked, visibility, name, args, ret, body })
|
||||
Ok(Self { attributes, 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;
|
||||
let Self { attributes: _, visibility, name, args, ret, body } = self;
|
||||
|
||||
stream.extend(quote! {
|
||||
#(#cooked)*
|
||||
#visibility async fn #name (#(#args),*) -> #ret {
|
||||
#(#body)*
|
||||
}
|
||||
@ -193,6 +162,24 @@ pub(crate) struct Arg {
|
||||
pub required: bool,
|
||||
}
|
||||
|
||||
impl Arg {
|
||||
pub fn as_tokens(&self, ident: &Ident) -> TokenStream2 {
|
||||
let arg_path = quote!(crate::framework::Arg);
|
||||
let Arg { name, description, kind, required } = self;
|
||||
|
||||
quote! {
|
||||
#[allow(missing_docs)]
|
||||
pub static #ident: #arg_path = #arg_path {
|
||||
name: #name,
|
||||
description: #description,
|
||||
kind: #kind,
|
||||
required: #required,
|
||||
options: &[]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Arg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@ -211,6 +198,44 @@ pub(crate) struct Subcommand {
|
||||
pub cmd_args: Vec<Arg>,
|
||||
}
|
||||
|
||||
impl Subcommand {
|
||||
pub fn as_tokens(&mut self, ident: &Ident) -> TokenStream2 {
|
||||
let arg_path = quote!(crate::framework::Arg);
|
||||
let subcommand_path = ApplicationCommandOptionType::SubCommand;
|
||||
|
||||
let arg_idents = self
|
||||
.cmd_args
|
||||
.iter()
|
||||
.map(|arg| ident.with_suffix(arg.name.as_str()).with_suffix(ARG))
|
||||
.collect::<Vec<Ident>>();
|
||||
|
||||
let mut tokens = self
|
||||
.cmd_args
|
||||
.iter_mut()
|
||||
.zip(arg_idents.iter())
|
||||
.map(|(arg, ident)| arg.as_tokens(ident))
|
||||
.fold(quote! {}, |mut a, b| {
|
||||
a.extend(b);
|
||||
a
|
||||
});
|
||||
|
||||
let Subcommand { name, description, .. } = self;
|
||||
|
||||
tokens.extend(quote! {
|
||||
#[allow(missing_docs)]
|
||||
pub static #ident: #arg_path = #arg_path {
|
||||
name: #name,
|
||||
description: #description,
|
||||
kind: #subcommand_path,
|
||||
required: false,
|
||||
options: &[#(&#arg_idents),*],
|
||||
};
|
||||
});
|
||||
|
||||
tokens
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Subcommand {
|
||||
fn default() -> Self {
|
||||
Self { name: String::new(), description: String::new(), cmd_args: vec![] }
|
||||
@ -223,6 +248,68 @@ impl Subcommand {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SubcommandGroup {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub subcommands: Vec<Subcommand>,
|
||||
}
|
||||
|
||||
impl SubcommandGroup {
|
||||
pub fn as_tokens(&mut self, ident: &Ident) -> TokenStream2 {
|
||||
let arg_path = quote!(crate::framework::Arg);
|
||||
let subcommand_group_path = ApplicationCommandOptionType::SubCommandGroup;
|
||||
|
||||
let arg_idents = self
|
||||
.subcommands
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
ident
|
||||
.with_suffix(self.name.as_str())
|
||||
.with_suffix(arg.name.as_str())
|
||||
.with_suffix(SUBCOMMAND)
|
||||
})
|
||||
.collect::<Vec<Ident>>();
|
||||
|
||||
let mut tokens = self
|
||||
.subcommands
|
||||
.iter_mut()
|
||||
.zip(arg_idents.iter())
|
||||
.map(|(subcommand, ident)| subcommand.as_tokens(ident))
|
||||
.fold(quote! {}, |mut a, b| {
|
||||
a.extend(b);
|
||||
a
|
||||
});
|
||||
|
||||
let SubcommandGroup { name, description, .. } = self;
|
||||
|
||||
tokens.extend(quote! {
|
||||
#[allow(missing_docs)]
|
||||
pub static #ident: #arg_path = #arg_path {
|
||||
name: #name,
|
||||
description: #description,
|
||||
kind: #subcommand_group_path,
|
||||
required: false,
|
||||
options: &[#(&#arg_idents),*],
|
||||
};
|
||||
});
|
||||
|
||||
tokens
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SubcommandGroup {
|
||||
fn default() -> Self {
|
||||
Self { name: String::new(), description: String::new(), subcommands: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
impl SubcommandGroup {
|
||||
pub(crate) fn new(name: String) -> Self {
|
||||
Self { name, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Options {
|
||||
pub aliases: Vec<String>,
|
||||
@ -233,6 +320,7 @@ pub(crate) struct Options {
|
||||
pub supports_dm: bool,
|
||||
pub cmd_args: Vec<Arg>,
|
||||
pub subcommands: Vec<Subcommand>,
|
||||
pub subcommand_groups: Vec<SubcommandGroup>,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
|
Reference in New Issue
Block a user