From c23c590330af83afb6e146bcd9b0a274b3689d22 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Mon, 17 Aug 2026 03:27:46 +0800 Subject: 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. --- mingling_macros/src/func/dispatcher.rs | 123 +++++++++++++-------------- mingling_macros/src/func/node.rs | 60 ------------- mingling_macros/src/func/program_comp_gen.rs | 4 +- 3 files changed, 61 insertions(+), 126 deletions(-) delete mode 100644 mingling_macros/src/func/node.rs (limited to 'mingling_macros/src/func') diff --git a/mingling_macros/src/func/dispatcher.rs b/mingling_macros/src/func/dispatcher.rs index 6183993..c4d67a9 100644 --- a/mingling_macros/src/func/dispatcher.rs +++ b/mingling_macros/src/func/dispatcher.rs @@ -6,13 +6,14 @@ use syn::parse::{Parse, ParseStream}; use syn::{Attribute, Ident, LitStr, Token}; enum DispatcherChainInput { + /// `dispatcher!("name", EntryType)` — explicit entry type Default { cmd_attrs: Vec, entry_attrs: Vec, command_name: syn::LitStr, - command_struct: Ident, pack: Ident, }, + /// `dispatcher!("name")` — entry type derived from the command name #[cfg(feature = "extras")] Auto { cmd_attrs: Vec, @@ -22,106 +23,107 @@ enum DispatcherChainInput { impl Parse for DispatcherChainInput { fn parse(input: ParseStream) -> syn::Result { - // Collect outer attributes for the CMD struct + // Collect outer attributes for the hidden dispatcher struct let cmd_attrs = input.call(Attribute::parse_outer)?; - if input.peek(syn::LitStr) { - // Parse the command name string first - let command_name: LitStr = input.parse()?; - - // Check if this is the abbreviated form: just "command_name" without ", CMD => Entry" - if input.is_empty() { - #[cfg(feature = "extras")] - { - return Ok(Self::Auto { - cmd_attrs, - command_name, - }); - } - #[cfg(not(feature = "extras"))] - { - return Err(syn::Error::new( - command_name.span(), - "expected `, CommandStruct => EntryStruct` after command name", - )); - } + let command_name: LitStr = input.parse()?; + + if input.is_empty() { + // Abbreviated form: just "command_name" + #[cfg(feature = "extras")] + { + return Ok(Self::Auto { + cmd_attrs, + command_name, + }); + } + #[cfg(not(feature = "extras"))] + { + return Err(syn::Error::new( + command_name.span(), + "expected `, EntryType` after command name", + )); } + } - // Default format: "command_name", CommandStruct => ChainStruct - input.parse::()?; - let command_struct = input.parse()?; - input.parse::]>()?; - let entry_attrs = input.call(Attribute::parse_outer)?; - let pack = input.parse()?; - - Ok(Self::Default { - cmd_attrs, - entry_attrs, - command_name, - command_struct, - pack, - }) - } else { - Err(input.lookahead1().error()) + // Explicit form: "command_name", EntryType + input.parse::()?; + let entry_attrs = input.call(Attribute::parse_outer)?; + let pack: Ident = input.parse()?; + + // The old `"name", CMD => Entry` form was removed in 0.5.0. + if input.peek(Token![=>]) { + return Err(syn::Error::new( + pack.span(), + "the `dispatcher!(\"name\", CMD => Entry)` form was removed in 0.5.0; \ + use `dispatcher!(\"name\", Entry)` — the dispatcher struct is generated internally", + )); } + + Ok(Self::Default { + cmd_attrs, + entry_attrs, + command_name, + pack, + }) } } -// NOTICE: The token stream generation patterns in `dispatcher_chain` and `dispatcher_render` -// are nearly identical and could benefit from refactoring into common helper functions. - -#[allow(clippy::too_many_lines)] pub(crate) fn dispatcher(input: TokenStream) -> TokenStream { - // Parse the input let dispatcher_input = syn::parse_macro_input!(input as DispatcherChainInput); #[cfg(not(feature = "extras"))] - let (command_name, command_struct, pack, cmd_attrs, entry_attrs) = match dispatcher_input { + let (command_name, pack, cmd_attrs, entry_attrs) = match dispatcher_input { DispatcherChainInput::Default { cmd_attrs, entry_attrs, command_name, - command_struct, pack, - } => (command_name, command_struct, pack, cmd_attrs, entry_attrs), + } => (command_name, pack, cmd_attrs, entry_attrs), }; #[cfg(feature = "extras")] - let (command_name, command_struct, pack, cmd_attrs, entry_attrs) = match dispatcher_input { + let (command_name, pack, cmd_attrs, entry_attrs) = match dispatcher_input { DispatcherChainInput::Default { cmd_attrs, entry_attrs, command_name, - command_struct, pack, - } => (command_name, command_struct, pack, cmd_attrs, entry_attrs), + } => (command_name, pack, cmd_attrs, entry_attrs), DispatcherChainInput::Auto { cmd_attrs, command_name, } => { let command_name_str = command_name.value(); let pascal = just_fmt::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()) + (command_name, pack, cmd_attrs, Vec::new()) } }; let command_name_str = command_name.value(); + let hidden_dispatcher = Ident::new( + &format!("__Dispatcher{}", just_fmt::pascal_case!(&command_name_str)), + command_name.span(), + ); let comp_entry = get_comp_entry(&pack); let compile_time_registration = - get_compile_time_registration(&command_name_str, &command_struct, &pack); + get_compile_time_registration(&command_name_str, &hidden_dispatcher, &pack); let program_type = crate::default_program_path(); let expanded = quote! { + ::mingling::macros::pack!(#(#entry_attrs)* #pack = Vec); + #(#cmd_attrs)* + #[doc(hidden)] #[derive(Debug, Default)] - pub struct #command_struct; + #[allow(nonstandard_style)] + pub struct #hidden_dispatcher; - ::mingling::macros::pack!(#(#entry_attrs)* #pack = Vec); + #compile_time_registration impl From<#pack> for crate::Entry { fn from(value: #pack) -> Self { @@ -130,19 +132,12 @@ pub(crate) fn dispatcher(input: TokenStream) -> TokenStream { } #comp_entry - #compile_time_registration - impl ::mingling::Dispatcher<#program_type> for #command_struct { - fn node(&self) -> ::mingling::Node { - ::mingling::macros::node!(#command_name_str) - } + impl ::mingling::Dispatcher<#program_type> for #hidden_dispatcher { fn begin(&self, args: Vec) -> ::mingling::ChainProcess<#program_type> { use ::mingling::Grouped; ::mingling::Routable::to_chain(#pack::new(args)) } - fn clone_dispatcher(&self) -> Box> { - Box::new(#command_struct) - } } }; @@ -173,11 +168,11 @@ fn get_comp_entry(_entry_name: &Ident) -> TokenStream2 { /// (trie vs. linear list) is generated later by `gen_program!`. fn get_compile_time_registration( command_name_str: &str, - command_struct: &Ident, + dispatcher_struct: &Ident, entry_name: &Ident, ) -> TokenStream2 { let node_name_lit = syn::LitStr::new(command_name_str, proc_macro2::Span::call_site()); quote! { - ::mingling::macros::register_dispatcher!(#node_name_lit, #command_struct, #entry_name); + ::mingling::macros::register_dispatcher!(#node_name_lit, #dispatcher_struct, #entry_name); } } diff --git a/mingling_macros/src/func/node.rs b/mingling_macros/src/func/node.rs deleted file mode 100644 index 9963037..0000000 --- a/mingling_macros/src/func/node.rs +++ /dev/null @@ -1,60 +0,0 @@ -// Doc Not Optimize -use just_fmt::kebab_case; -use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use syn::parse::{Parse, ParseStream}; -use syn::{LitStr, Result as SynResult}; - -/// Parses a string literal input for the node macro -struct NodeInput { - path: LitStr, -} - -impl Parse for NodeInput { - fn parse(input: ParseStream) -> SynResult { - Ok(Self { - path: input.parse()?, - }) - } -} - -pub(crate) fn node(input: TokenStream) -> TokenStream { - // Parse the input as a string literal - let input_parsed = syn::parse_macro_input!(input as NodeInput); - let path_str = input_parsed.path.value(); - - // If the input string is empty, return an empty Node - if path_str.is_empty() { - return quote! { - mingling::Node::default() - } - .into(); - } - - // Split the path by dots - let parts: Vec = path_str - .split('.') - .map(|s| { - if s.starts_with('_') { - s.to_string() - } else { - kebab_case!(s) - } - }) - .collect(); - - // Build the expression starting from Node::default() - let mut expr: TokenStream2 = quote! { - mingling::Node::default() - }; - - // Add .join() calls for each part of the path - for part in parts { - expr = quote! { - #expr.join(#part) - }; - } - - expr.into() -} diff --git a/mingling_macros/src/func/program_comp_gen.rs b/mingling_macros/src/func/program_comp_gen.rs index 7f77d46..ed04379 100644 --- a/mingling_macros/src/func/program_comp_gen.rs +++ b/mingling_macros/src/func/program_comp_gen.rs @@ -42,13 +42,14 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream { let internal_dispatcher_comp = quote! { use __internal_completion_mod::__internal_dispatcher_comp; + use __internal_completion_mod::__DispatcherComp; }; let comp_dispatcher = quote! { #[doc(hidden)] mod __internal_completion_mod { use ::mingling::Grouped; - ::mingling::macros::dispatcher!("__comp", CMDCompletion => CompletionContext); + ::mingling::macros::dispatcher!("__comp", CompletionContext); ::mingling::macros::pack!( CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest) ); @@ -56,7 +57,6 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream { #internal_dispatcher_comp use __internal_completion_mod::CompletionContext; use __internal_completion_mod::CompletionSuggest; - pub use __internal_completion_mod::CMDCompletion; #fn_exec_comp -- cgit