aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-05-29 17:34:59 +0800
committer魏曹先生 <1992414357@qq.com>2026-05-29 17:34:59 +0800
commit7e9c77641a3dfb5df7c2218081ee625d0d069f4b (patch)
treea45feffee981c0c46dc76462a254dbc017240374 /mingling_macros
parentc493af82436047871af91505d440da32518477cf (diff)
Support doc comments and attributes on `pack!` and `dispatcher!` macros
Diffstat (limited to 'mingling_macros')
-rw-r--r--mingling_macros/src/dispatcher.rs167
-rw-r--r--mingling_macros/src/pack.rs31
2 files changed, 127 insertions, 71 deletions
diff --git a/mingling_macros/src/dispatcher.rs b/mingling_macros/src/dispatcher.rs
index 725597b..b7952a1 100644
--- a/mingling_macros/src/dispatcher.rs
+++ b/mingling_macros/src/dispatcher.rs
@@ -2,29 +2,39 @@ use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::parse::{Parse, ParseStream};
-use syn::{Ident, LitStr, Result as SynResult, Token};
+use syn::{Attribute, Ident, LitStr, Result as SynResult, Token};
#[cfg(feature = "dispatch_tree")]
use crate::COMPILE_TIME_DISPATCHERS;
enum DispatcherChainInput {
Explicit {
+ cmd_attrs: Vec<Attribute>,
+ entry_attrs: Vec<Attribute>,
group_name: syn::Path,
command_name: syn::LitStr,
command_struct: Ident,
pack: Ident,
},
Default {
+ cmd_attrs: Vec<Attribute>,
+ entry_attrs: Vec<Attribute>,
command_name: syn::LitStr,
command_struct: Ident,
pack: Ident,
},
#[cfg(feature = "extra_macros")]
- Auto { command_name: syn::LitStr },
+ Auto {
+ cmd_attrs: Vec<Attribute>,
+ command_name: syn::LitStr,
+ },
}
impl Parse for DispatcherChainInput {
fn parse(input: ParseStream) -> SynResult<Self> {
+ // Collect outer attributes for the CMD struct
+ let cmd_attrs = input.call(Attribute::parse_outer)?;
+
if (input.peek(Ident) || input.peek(Token![crate]))
&& (input.peek2(Token![::]) || input.peek2(Token![,]))
{
@@ -34,9 +44,12 @@ impl Parse for DispatcherChainInput {
input.parse::<Token![,]>()?;
let command_struct = input.parse()?;
input.parse::<Token![=>]>()?;
+ let entry_attrs = input.call(Attribute::parse_outer)?;
let pack = input.parse()?;
Ok(DispatcherChainInput::Explicit {
+ cmd_attrs,
+ entry_attrs,
group_name,
command_name,
command_struct,
@@ -50,7 +63,10 @@ impl Parse for DispatcherChainInput {
if input.is_empty() {
#[cfg(feature = "extra_macros")]
{
- return Ok(DispatcherChainInput::Auto { command_name });
+ return Ok(DispatcherChainInput::Auto {
+ cmd_attrs,
+ command_name,
+ });
}
#[cfg(not(feature = "extra_macros"))]
{
@@ -65,9 +81,12 @@ impl Parse for DispatcherChainInput {
input.parse::<Token![,]>()?;
let command_struct = input.parse()?;
input.parse::<Token![=>]>()?;
+ let entry_attrs = input.call(Attribute::parse_outer)?;
let pack = input.parse()?;
Ok(DispatcherChainInput::Default {
+ cmd_attrs,
+ entry_attrs,
command_name,
command_struct,
pack,
@@ -90,71 +109,94 @@ pub fn dispatcher(input: TokenStream) -> TokenStream {
let dispatcher_input = syn::parse_macro_input!(input as DispatcherChainInput);
#[cfg(not(feature = "extra_macros"))]
- let (command_name, command_struct, pack, _use_default, group_path) = match dispatcher_input {
- DispatcherChainInput::Explicit {
- group_name,
- command_name,
- command_struct,
- pack,
- } => (
- command_name,
- command_struct,
- pack,
- false,
- quote! { #group_name },
- ),
- DispatcherChainInput::Default {
- command_name,
- command_struct,
- pack,
- } => (
- command_name,
- command_struct,
- pack,
- true,
- crate::default_program_path(),
- ),
- };
+ let (command_name, command_struct, pack, cmd_attrs, entry_attrs, _use_default, group_path) =
+ match dispatcher_input {
+ DispatcherChainInput::Explicit {
+ cmd_attrs,
+ entry_attrs,
+ group_name,
+ command_name,
+ command_struct,
+ pack,
+ } => (
+ command_name,
+ command_struct,
+ pack,
+ cmd_attrs,
+ entry_attrs,
+ false,
+ quote! { #group_name },
+ ),
+ DispatcherChainInput::Default {
+ cmd_attrs,
+ entry_attrs,
+ command_name,
+ command_struct,
+ pack,
+ } => (
+ command_name,
+ command_struct,
+ pack,
+ cmd_attrs,
+ entry_attrs,
+ true,
+ crate::default_program_path(),
+ ),
+ };
#[cfg(feature = "extra_macros")]
- let (command_name, command_struct, pack, _use_default, group_path) = match dispatcher_input {
- DispatcherChainInput::Explicit {
- group_name,
- command_name,
- command_struct,
- pack,
- } => (
- command_name,
- command_struct,
- pack,
- false,
- quote! { #group_name },
- ),
- DispatcherChainInput::Default {
- command_name,
- command_struct,
- pack,
- } => (
- command_name,
- command_struct,
- pack,
- true,
- crate::default_program_path(),
- ),
- DispatcherChainInput::Auto { command_name } => {
- let command_name_str = command_name.value();
- let pascal = dotted_to_pascal_case(&command_name_str);
- let command_struct = Ident::new(&format!("CMD{pascal}"), command_name.span());
- let pack = Ident::new(&format!("Entry{pascal}"), command_name.span());
- (
+ let (command_name, command_struct, pack, cmd_attrs, entry_attrs, _use_default, group_path) =
+ match dispatcher_input {
+ DispatcherChainInput::Explicit {
+ cmd_attrs,
+ entry_attrs,
+ group_name,
+ command_name,
+ command_struct,
+ pack,
+ } => (
+ command_name,
+ command_struct,
+ pack,
+ cmd_attrs,
+ entry_attrs,
+ false,
+ quote! { #group_name },
+ ),
+ DispatcherChainInput::Default {
+ cmd_attrs,
+ entry_attrs,
+ command_name,
+ command_struct,
+ pack,
+ } => (
command_name,
command_struct,
pack,
+ cmd_attrs,
+ entry_attrs,
true,
crate::default_program_path(),
- )
- }
- };
+ ),
+ DispatcherChainInput::Auto {
+ cmd_attrs,
+ command_name,
+ } => {
+ let command_name_str = command_name.value();
+ let pascal = dotted_to_pascal_case(&command_name_str);
+ let command_struct = Ident::new(&format!("CMD{pascal}"), command_name.span());
+ let pack = Ident::new(&format!("Entry{pascal}"), command_name.span());
+ (
+ command_name,
+ command_struct,
+ pack,
+ cmd_attrs,
+ Vec::new(),
+ true,
+ crate::default_program_path(),
+ )
+ }
+ };
let command_name_str = command_name.value();
@@ -166,10 +208,11 @@ pub fn dispatcher(input: TokenStream) -> TokenStream {
let program_path = group_path;
quote! {
+ #(#cmd_attrs)*
#[derive(Debug, Default)]
pub struct #command_struct;
- ::mingling::macros::pack!(#program_path, #pack = Vec<String>);
+ ::mingling::macros::pack!(#(#entry_attrs)* #program_path, #pack = Vec<String>);
#comp_entry
#dispatch_tree_entry
diff --git a/mingling_macros/src/pack.rs b/mingling_macros/src/pack.rs
index 657f1bb..954a052 100644
--- a/mingling_macros/src/pack.rs
+++ b/mingling_macros/src/pack.rs
@@ -1,15 +1,17 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
-use syn::{Ident, Result as SynResult, Token, Type};
+use syn::{Attribute, Ident, Result as SynResult, Token, Type};
enum PackInput {
Explicit {
+ attrs: Vec<Attribute>,
group_name: syn::Path,
type_name: Ident,
inner_type: Type,
},
Default {
+ attrs: Vec<Attribute>,
type_name: Ident,
inner_type: Type,
},
@@ -17,12 +19,12 @@ enum PackInput {
impl Parse for PackInput {
fn parse(input: ParseStream) -> SynResult<Self> {
- // Look ahead to determine format:
+ // First, collect any outer attributes (`#[...]` and `///...`) before the main syntax.
+ let attrs = input.call(Attribute::parse_outer)?;
+
+ // Now determine the format:
// - `Path, TypeName = InnerType` → Explicit
// - `TypeName = InnerType` → Default
- //
- // Both start with an ident. We peek at the second token:
- // if it's a `,` or `::`, it's explicit; if it's `=`, it's default.
if (input.peek(Ident) || input.peek(Token![crate]))
&& (input.peek2(Token![,]) || input.peek2(Token![::]))
@@ -35,6 +37,7 @@ impl Parse for PackInput {
let inner_type = input.parse()?;
Ok(PackInput::Explicit {
+ attrs,
group_name,
type_name,
inner_type,
@@ -46,6 +49,7 @@ impl Parse for PackInput {
let inner_type = input.parse()?;
Ok(PackInput::Default {
+ attrs,
type_name,
inner_type,
})
@@ -59,22 +63,30 @@ pub fn pack(input: TokenStream) -> TokenStream {
// Parse the input
let pack_input = syn::parse_macro_input!(input as PackInput);
- // Determine if we're using default or explicit group
- let (group_name, type_name, inner_type, use_default) = match pack_input {
+ let (group_name, type_name, inner_type, attrs, use_default) = match pack_input {
PackInput::Explicit {
+ attrs,
group_name,
type_name,
inner_type,
- } => (quote! { #group_name }, type_name, inner_type, false),
+ } => (quote! { #group_name }, type_name, inner_type, attrs, false),
PackInput::Default {
+ attrs,
+ type_name,
+ inner_type,
+ } => (
+ crate::default_program_path(),
type_name,
inner_type,
- } => (crate::default_program_path(), type_name, inner_type, true),
+ attrs,
+ true,
+ ),
};
// Generate the struct definition
#[cfg(not(feature = "general_renderer"))]
let struct_def = quote! {
+ #(#attrs)*
pub struct #type_name {
pub(crate) inner: #inner_type,
}
@@ -82,6 +94,7 @@ pub fn pack(input: TokenStream) -> TokenStream {
#[cfg(feature = "general_renderer")]
let struct_def = quote! {
+ #(#attrs)*
#[derive(serde::Serialize)]
pub struct #type_name {
pub(crate) inner: #inner_type,