diff options
Diffstat (limited to 'mingling_macros/src')
| -rw-r--r-- | mingling_macros/src/dispatcher.rs | 83 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 22 |
2 files changed, 102 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() +} diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 57f37a1..73f2fa5 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -297,6 +297,25 @@ pub fn empty_result(_input: TokenStream) -> TokenStream { /// dispatcher!(MyProgram, "command.path", CommandStruct => EntryStruct); /// ``` /// +/// ## Abbreviated syntax (requires `extra_macros` feature) +/// +/// When the `extra_macros` feature is enabled, the `CommandStruct => EntryStruct` +/// portion can be omitted. The struct names are auto-derived from the command path +/// using PascalCase conversion: +/// +/// ```rust,ignore +/// // Auto-derives: "remote.add" → CMDRemoteAdd ⇒ EntryRemoteAdd +/// dispatcher!("remote.add"); +/// +/// // Auto-derives: "cmd.sub.leaf" → CMDCmdSubLeaf ⇒ EntryCmdSubLeaf +/// dispatcher!("cmd.sub.leaf"); +/// ``` +/// +/// The generated code is equivalent to writing: +/// ```rust,ignore +/// dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd); +/// ``` +/// /// # Example /// /// ```rust,ignore @@ -310,6 +329,9 @@ pub fn empty_result(_input: TokenStream) -> TokenStream { /// /// // With explicit program: /// dispatcher!(MyApp, "status", StatusCommand => StatusEntry); +/// +/// // Abbreviated form (requires extra_macros): +/// // dispatcher!("remote.add"); /// ``` /// /// The generated `HelloCommand` implements `Dispatcher<ThisProgram>`: |
