diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-17 03:27:46 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-17 03:27:46 +0800 |
| commit | c23c590330af83afb6e146bcd9b0a274b3689d22 (patch) | |
| tree | 34599cf2737ce4de22653c9ccae2d60a38a15842 /mingling_macros/src/attr | |
| parent | 6980fbf2f9fb4c599d8dc6ff549a8b3288eb24e9 (diff) | |
refactor!: remove Node type and simplify dispatcher macro syntax
The `dispatcher!` macro no longer requires a `CMD*` dispatcher type
argument; the dispatcher struct is now generated internally as
`__Dispatcher{Pascal}`. The `Node` type, `node!` macro, and
`Dispatcher::node()` / `clone_dispatcher()` methods are removed.
Diffstat (limited to 'mingling_macros/src/attr')
| -rw-r--r-- | mingling_macros/src/attr/command.rs | 53 | ||||
| -rw-r--r-- | mingling_macros/src/attr/dispatcher_clap.rs | 39 |
2 files changed, 38 insertions, 54 deletions
diff --git a/mingling_macros/src/attr/command.rs b/mingling_macros/src/attr/command.rs index 4542bd7..6f2409c 100644 --- a/mingling_macros/src/attr/command.rs +++ b/mingling_macros/src/attr/command.rs @@ -12,12 +12,10 @@ use syn::{FnArg, Ident, ItemFn, LitStr, PatType, Token, Type}; /// /// Supports: /// - `node = "dot.separated.path"` — explicit command path -/// - `name = CMDName` — explicit CMD struct name /// - `entry = EntryName` — explicit Entry struct name /// - bare paths like `routeify`, `::mingling::macros::routeify` — extension attrs for the original fn struct CommandArgs { node: Option<LitStr>, - name: Option<Ident>, entry: Option<Ident>, exts: Vec<syn::Path>, } @@ -25,7 +23,6 @@ struct CommandArgs { impl Parse for CommandArgs { fn parse(input: ParseStream) -> syn::Result<Self> { let mut node = None; - let mut name = None; let mut entry = None; let mut exts = Vec::new(); @@ -41,19 +38,18 @@ impl Parse for CommandArgs { } node = Some(input.parse()?); } else if key == "name" { - if name.is_some() { - return Err(input.error("duplicate `name` argument")); - } - name = Some(input.parse()?); + return Err(input.error( + "`name = ...` was removed in 0.5.0; the dispatcher struct is generated internally", + )); } else if key == "entry" { if entry.is_some() { return Err(input.error("duplicate `entry` argument")); } entry = Some(input.parse()?); } else { - return Err(input.error(format!( - "unknown key `{key}`; expected `node`, `name`, or `entry`" - ))); + return Err( + input.error(format!("unknown key `{key}`; expected `node` or `entry`")) + ); } } else { // Extension path (e.g. `routeify` or `::mingling::macros::routeify`) @@ -67,12 +63,7 @@ impl Parse for CommandArgs { } } - Ok(Self { - node, - name, - entry, - exts, - }) + Ok(Self { node, entry, exts }) } } @@ -127,17 +118,15 @@ fn handle_async(f: &ItemFn) -> Result<(TokenStream2, TokenStream2), TokenStream2 struct ResolvedNames { /// `node_str` as a string literal token node_lit: LitStr, - /// Whether the user supplied any explicit override (node/name/entry) + /// Whether the user supplied any explicit override (node/entry) has_overrides: bool, - /// CMD struct name (e.g. `CMDGreet`) - cmd_name: Ident, /// Entry struct name (e.g. `EntryGreet`) entry_type: Ident, /// Chain wrapper function name (e.g. `__command_chain_greet`) chain_fn_name: Ident, } -/// Resolves `node`, `cmd_name`, `entry_type`, and `chain_fn_name` from +/// Resolves `node`, `entry_type`, and `chain_fn_name` from /// the attribute args and the original function name. fn resolve_names(fn_name: &Ident, args: &CommandArgs) -> ResolvedNames { let fn_name_str = fn_name.to_string(); @@ -148,12 +137,7 @@ fn resolve_names(fn_name: &Ident, args: &CommandArgs) -> ResolvedNames { .map_or_else(|| default_node_from_fn(fn_name), syn::LitStr::value); let node_lit = syn::LitStr::new(&node_str, fn_name.span()); - let has_overrides = args.node.is_some() || args.name.is_some() || args.entry.is_some(); - - let cmd_name = args.name.clone().unwrap_or_else(|| { - let pascal = just_fmt::pascal_case!(&node_str); - Ident::new(&format!("CMD{pascal}"), fn_name.span()) - }); + let has_overrides = args.node.is_some() || args.entry.is_some(); let entry_type = args.entry.clone().unwrap_or_else(|| { let pascal = just_fmt::pascal_case!(&node_str); @@ -165,7 +149,6 @@ fn resolve_names(fn_name: &Ident, args: &CommandArgs) -> ResolvedNames { ResolvedNames { node_lit, has_overrides, - cmd_name, entry_type, chain_fn_name, } @@ -247,13 +230,12 @@ fn build_call_args(sig: &syn::Signature) -> Vec<TokenStream2> { /// Generates the `dispatcher!(...)` call. /// /// - No overrides → abbreviated form: `dispatcher!("node")` -/// - Any override → explicit form: `dispatcher!("node", CMDName => EntryName)` +/// - Any override → explicit form: `dispatcher!("node", EntryType)` fn build_dispatcher_invoke(names: &ResolvedNames) -> TokenStream2 { let node_lit = &names.node_lit; if names.has_overrides { - let cmd_name = &names.cmd_name; let entry_type = &names.entry_type; - quote! { ::mingling::macros::dispatcher!(#node_lit, #cmd_name => #entry_type); } + quote! { ::mingling::macros::dispatcher!(#node_lit, #entry_type); } } else { quote! { ::mingling::macros::dispatcher!(#node_lit); } } @@ -271,7 +253,6 @@ pub(crate) fn command_attr(attr: TokenStream, item: TokenStream) -> TokenStream let args: CommandArgs = if attr.is_empty() { CommandArgs { node: None, - name: None, entry: None, exts: Vec::new(), } @@ -332,7 +313,13 @@ pub(crate) fn command_attr(attr: TokenStream, item: TokenStream) -> TokenStream quote! { #vis use super::#ident; } }; - let cmd_name = &names.cmd_name; + // hidden dispatcher struct generated by the internal `dispatcher!` call + let hidden_dispatcher = { + let node_str = names.node_lit.value(); + let pascal = just_fmt::pascal_case!(&node_str); + Ident::new(&format!("__Dispatcher{pascal}"), fn_name.span()) + }; + let entry_type = &names.entry_type; // assemble output @@ -351,7 +338,7 @@ pub(crate) fn command_attr(attr: TokenStream, item: TokenStream) -> TokenStream // hidden module gathering all generated types for pathf / external access #[doc(hidden)] #vis mod #mod_name { - #vis use super::#cmd_name; + #vis use super::#hidden_dispatcher; #vis use super::#entry_type; #vis use super::#chain_internal; #dispatcher_internal diff --git a/mingling_macros/src/attr/dispatcher_clap.rs b/mingling_macros/src/attr/dispatcher_clap.rs index 2f45b14..46238d6 100644 --- a/mingling_macros/src/attr/dispatcher_clap.rs +++ b/mingling_macros/src/attr/dispatcher_clap.rs @@ -30,7 +30,13 @@ impl Parse for ClapOptions { } let key: Ident = input.parse()?; - input.parse::<Token![=]>()?; + if input.parse::<Token![=]>().is_err() { + return Err(syn::Error::new( + key.span(), + "expected `key = value`; note: the explicit CMD struct argument \ + was removed in 0.5.0, use `dispatcher_clap!(\"name\", help = ..., error = ...)`", + )); + } if key == "error" { let value: Ident = input.parse()?; @@ -63,18 +69,15 @@ impl Parse for ClapOptions { /// Input for the `dispatcher_clap` attribute struct DispatcherClapInput { - /// `("cmd", Disp, ...)` + /// `("cmd", options...)` command_name: LitStr, - dispatcher_struct: Ident, options: ClapOptions, } impl Parse for DispatcherClapInput { fn parse(input: ParseStream) -> syn::Result<Self> { - // Format: "cmd", Disp, ... + // Format: "cmd", options... let command_name: LitStr = input.parse()?; - input.parse::<Token![,]>()?; - let dispatcher_struct: Ident = input.parse()?; let options = if input.is_empty() { ClapOptions { @@ -87,13 +90,13 @@ impl Parse for DispatcherClapInput { Ok(Self { command_name, - dispatcher_struct, options, }) } } #[cfg(feature = "clap")] +#[allow(clippy::too_many_lines)] pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream { let attr_input = parse_macro_input!(attr as DispatcherClapInput); let input_struct = parse_macro_input!(item as ItemStruct); @@ -102,7 +105,12 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke let program_path = crate::default_program_path(); let command_name_str = attr_input.command_name.value(); - let dispatcher_struct = &attr_input.dispatcher_struct; + + // The dispatcher struct is now generated internally. + let dispatcher_struct = Ident::new( + &format!("__Dispatcher{}", just_fmt::pascal_case!(&command_name_str)), + attr_input.command_name.span(), + ); let options = &attr_input.options; // Generate the `begin` method body @@ -141,8 +149,7 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke // Generate the #[help] block if help = true let help_gen = if options.help_enabled { - let dispatcher_name_str = dispatcher_struct.to_string(); - let help_fn_name_str = format!("__{}_help", just_fmt::snake_case!(&dispatcher_name_str)); + let help_fn_name_str = format!("__{}_help", just_fmt::snake_case!(&command_name_str)); let help_fn_name = Ident::new(&help_fn_name_str, proc_macro2::Span::call_site()); Some(quote! { @@ -175,7 +182,7 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke }; let compile_time_registration = - get_compile_time_registration(&command_name_str, dispatcher_struct, struct_name); + get_compile_time_registration(&command_name_str, &dispatcher_struct, struct_name); let expanded = quote! { // Keep the original struct definition @@ -196,10 +203,6 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke pub(crate) struct #dispatcher_struct; impl ::mingling::Dispatcher<#program_path> for #dispatcher_struct { - fn node(&self) -> ::mingling::Node { - ::mingling::macros::node!(#command_name_str) - } - fn begin( &self, args: Vec<String>, @@ -211,12 +214,6 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke #begin_body } - - fn clone_dispatcher( - &self, - ) -> Box<dyn ::mingling::Dispatcher<#program_path>> { - Box::new(#dispatcher_struct) - } } }; |
