aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/func
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-17 03:27:46 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-17 03:27:46 +0800
commitc23c590330af83afb6e146bcd9b0a274b3689d22 (patch)
tree34599cf2737ce4de22653c9ccae2d60a38a15842 /mingling_macros/src/func
parent6980fbf2f9fb4c599d8dc6ff549a8b3288eb24e9 (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/func')
-rw-r--r--mingling_macros/src/func/dispatcher.rs123
-rw-r--r--mingling_macros/src/func/node.rs60
-rw-r--r--mingling_macros/src/func/program_comp_gen.rs4
3 files changed, 61 insertions, 126 deletions
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<Attribute>,
entry_attrs: Vec<Attribute>,
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<Attribute>,
@@ -22,106 +23,107 @@ enum DispatcherChainInput {
impl Parse for DispatcherChainInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
- // 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::<Token![,]>()?;
- let command_struct = input.parse()?;
- input.parse::<Token![=>]>()?;
- 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::<Token![,]>()?;
+ 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<String>);
+
#(#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<String>);
+ #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<String>) -> ::mingling::ChainProcess<#program_type> {
use ::mingling::Grouped;
::mingling::Routable::to_chain(#pack::new(args))
}
- fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher<#program_type>> {
- 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<Self> {
- 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<String> = 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