diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-05-29 17:34:59 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-05-29 17:34:59 +0800 |
| commit | 7e9c77641a3dfb5df7c2218081ee625d0d069f4b (patch) | |
| tree | a45feffee981c0c46dc76462a254dbc017240374 /mingling_macros | |
| parent | c493af82436047871af91505d440da32518477cf (diff) | |
Support doc comments and attributes on `pack!` and `dispatcher!` macros
Diffstat (limited to 'mingling_macros')
| -rw-r--r-- | mingling_macros/src/dispatcher.rs | 167 | ||||
| -rw-r--r-- | mingling_macros/src/pack.rs | 31 |
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, |
