aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/dispatcher.rs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-05-24 17:06:54 +0800
committer魏曹先生 <1992414357@qq.com>2026-05-24 17:06:54 +0800
commit60e70f5320b2abdb38a2349c18e5bffcfea37ca7 (patch)
tree3402af0a2822255c1c3f9c77affe6da81c9d1279 /mingling_macros/src/dispatcher.rs
parent11adad7db1b6202d5366527902c3f0a9fb90654f (diff)
Add implicit dispatcher macro with auto-derived names
Diffstat (limited to 'mingling_macros/src/dispatcher.rs')
-rw-r--r--mingling_macros/src/dispatcher.rs83
1 files changed, 80 insertions, 3 deletions
diff --git a/mingling_macros/src/dispatcher.rs b/mingling_macros/src/dispatcher.rs
index e327d6b..725597b 100644
--- a/mingling_macros/src/dispatcher.rs
+++ b/mingling_macros/src/dispatcher.rs
@@ -2,7 +2,7 @@ use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::parse::{Parse, ParseStream};
-use syn::{Ident, Result as SynResult, Token};
+use syn::{Ident, LitStr, Result as SynResult, Token};
#[cfg(feature = "dispatch_tree")]
use crate::COMPILE_TIME_DISPATCHERS;
@@ -19,6 +19,8 @@ enum DispatcherChainInput {
command_struct: Ident,
pack: Ident,
},
+ #[cfg(feature = "extra_macros")]
+ Auto { command_name: syn::LitStr },
}
impl Parse for DispatcherChainInput {
@@ -41,8 +43,25 @@ impl Parse for DispatcherChainInput {
pack,
})
} else 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 = "extra_macros")]
+ {
+ return Ok(DispatcherChainInput::Auto { command_name });
+ }
+ #[cfg(not(feature = "extra_macros"))]
+ {
+ return Err(syn::Error::new(
+ command_name.span(),
+ "expected `, CommandStruct => EntryStruct` after command name",
+ ));
+ }
+ }
+
// Default format: "command_name", CommandStruct => ChainStruct
- let command_name = input.parse()?;
input.parse::<Token![,]>()?;
let command_struct = input.parse()?;
input.parse::<Token![=>]>()?;
@@ -70,7 +89,34 @@ pub fn dispatcher(input: TokenStream) -> TokenStream {
// Parse the input
let dispatcher_input = syn::parse_macro_input!(input as DispatcherChainInput);
- // Determine if we're using default or explicit group
+ #[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(),
+ ),
+ };
+
+ #[cfg(feature = "extra_macros")]
let (command_name, command_struct, pack, _use_default, group_path) = match dispatcher_input {
DispatcherChainInput::Explicit {
group_name,
@@ -95,6 +141,19 @@ pub fn dispatcher(input: TokenStream) -> TokenStream {
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());
+ (
+ command_name,
+ command_struct,
+ pack,
+ true,
+ crate::default_program_path(),
+ )
+ }
};
let command_name_str = command_name.value();
@@ -229,3 +288,21 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream {
pub fn register_dispatcher(_input: TokenStream) -> TokenStream {
quote! {}.into()
}
+
+/// Converts a dotted command name (e.g. "remote.add") to PascalCase (e.g. "RemoteAdd").
+///
+/// Each segment is split by `.`, the first character of each segment is uppercased,
+/// and the segments are joined. This is used by the abbreviated `dispatcher!` syntax
+/// (when `Command => Entry` is omitted) to auto-derive struct names.
+#[cfg(feature = "extra_macros")]
+fn dotted_to_pascal_case(s: &str) -> String {
+ s.split('.')
+ .map(|segment| {
+ let mut chars = segment.chars();
+ match chars.next() {
+ None => String::new(),
+ Some(c) => c.to_uppercase().to_string() + chars.as_str(),
+ }
+ })
+ .collect()
+}