diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-04-25 22:12:49 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-04-25 22:19:55 +0800 |
| commit | 265c79a1e3b20ebf5b2026a55e85cff513eaf9f5 (patch) | |
| tree | 288fe1136cd9360ff896796e8d5197d74f9533ea /mingling_macros/src | |
| parent | 019b8def49d814bca44047d85c9ff27bbda36a66 (diff) | |
Add `dispatcher_clap` attribute macro behind `clap_parser` feature
Diffstat (limited to 'mingling_macros/src')
| -rw-r--r-- | mingling_macros/src/dispatcher_clap.rs | 188 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 8 |
2 files changed, 196 insertions, 0 deletions
diff --git a/mingling_macros/src/dispatcher_clap.rs b/mingling_macros/src/dispatcher_clap.rs new file mode 100644 index 0000000..58d30fc --- /dev/null +++ b/mingling_macros/src/dispatcher_clap.rs @@ -0,0 +1,188 @@ +//! Dispatcher Clap Attribute Macro +//! +//! This module provides the `#[dispatcher_clap(...)]` attribute macro for +//! automatically generating a `Dispatcher` implementation that uses `clap::Parser` +//! to parse command arguments into the annotated struct. +//! +//! This macro is only available when the `clap_parser` feature is enabled. +//! +//! # Syntax +//! +//! ## Two-argument form (parse failure calls `e.exit()`): +//! +//! ```rust,ignore +//! #[derive(Groupped, clap::Parser)] +//! #[dispatcher_clap("command_name", DispatcherName)] +//! struct MyEntry { +//! #[arg(long, short)] +//! name: String, +//! } +//! ``` +//! +//! ## Three-argument form (parse failure routes to error struct): +//! +//! ```rust,ignore +//! #[derive(Groupped, clap::Parser)] +//! #[dispatcher_clap("command_name", DispatcherName, ParseError)] +//! struct MyEntry { +//! #[arg(long, short)] +//! name: String, +//! } +//! ``` +//! +//! When three arguments are given, a pack type named `ParseError` is generated +//! that wraps the clap error message as a `String`. On parse failure, the error +//! message is routed to the renderer via `to_render()` instead of calling `e.exit()`. + +use proc_macro::TokenStream; +use quote::quote; +use syn::{ + Ident, ItemStruct, LitStr, Token, + parse::{Parse, ParseStream}, + parse_macro_input, +}; + +/// Input for the dispatcher_clap attribute +/// +/// Two forms: +/// - Two args: `("command_name", DispatcherStruct)` +/// - Three args: `("command_name", DispatcherStruct, ErrorStruct)` +enum DispatcherClapInput { + /// No error type: `("cmd", DispatcherStruct)` + Simple { + command_name: LitStr, + dispatcher_struct: Ident, + }, + /// With error type: `("cmd", DispatcherStruct, ErrorStruct)` + WithError { + command_name: LitStr, + dispatcher_struct: Ident, + error_struct: Ident, + }, +} + +impl Parse for DispatcherClapInput { + fn parse(input: ParseStream) -> syn::Result<Self> { + let command_name: LitStr = input.parse()?; + input.parse::<Token![,]>()?; + let dispatcher_struct: Ident = input.parse()?; + + // Check if there's a third argument (error struct) + if input.peek(Token![,]) { + input.parse::<Token![,]>()?; + let error_struct: Ident = input.parse()?; + Ok(DispatcherClapInput::WithError { + command_name, + dispatcher_struct, + error_struct, + }) + } else { + Ok(DispatcherClapInput::Simple { + command_name, + dispatcher_struct, + }) + } + } +} + +pub fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse the attribute arguments + let attr_input = parse_macro_input!(attr as DispatcherClapInput); + + // Parse the struct item to get the struct name + let input_struct = parse_macro_input!(item as ItemStruct); + let struct_name = &input_struct.ident; + + let expanded = match attr_input { + DispatcherClapInput::Simple { + command_name, + dispatcher_struct, + } => { + let command_name_str = command_name.value(); + quote! { + // Keep the original struct definition + #input_struct + + // Generate the dispatcher struct + #[doc(hidden)] + pub struct #dispatcher_struct; + + impl ::mingling::Dispatcher<ThisProgram> for #dispatcher_struct { + fn node(&self) -> ::mingling::Node { + ::mingling::macros::node!(#command_name_str) + } + + fn begin( + &self, + args: Vec<String>, + ) -> ::mingling::ChainProcess<ThisProgram> { + // Prepend a dummy program name for clap's parse_from + let clap_args = std::iter::once(String::new()) + .chain(args) + .collect::<Vec<_>>(); + + // Parse using clap's Parser, exit on error + let parsed = <#struct_name as ::clap::Parser>::try_parse_from(clap_args) + .unwrap_or_else(|e| e.exit()); + + parsed.to_chain() + } + + fn clone_dispatcher( + &self, + ) -> Box<dyn ::mingling::Dispatcher<ThisProgram>> { + Box::new(#dispatcher_struct) + } + } + } + } + DispatcherClapInput::WithError { + command_name, + dispatcher_struct, + error_struct, + } => { + let command_name_str = command_name.value(); + quote! { + // Keep the original struct definition + #input_struct + + // Generate the error wrapper type via pack! + ::mingling::macros::pack!(#error_struct = String); + + // Generate the dispatcher struct + #[doc(hidden)] + pub struct #dispatcher_struct; + + impl ::mingling::Dispatcher<ThisProgram> for #dispatcher_struct { + fn node(&self) -> ::mingling::Node { + ::mingling::macros::node!(#command_name_str) + } + + fn begin( + &self, + args: Vec<String>, + ) -> ::mingling::ChainProcess<ThisProgram> { + // Prepend a dummy program name for clap's parse_from + let clap_args = std::iter::once(String::new()) + .chain(args) + .collect::<Vec<_>>(); + + // Parse using clap's Parser, route error on failure + match <#struct_name as ::clap::Parser>::try_parse_from(clap_args) { + Ok(parsed) => parsed.to_chain(), + Err(e) => #error_struct::new(e.to_string()).to_render(), + } + } + + fn clone_dispatcher( + &self, + ) -> Box<dyn ::mingling::Dispatcher<ThisProgram>> { + Box::new(#dispatcher_struct) + } + } + } + } + }; + + expanded.into() +} diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 5736578..da3bf47 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -15,6 +15,8 @@ mod chain; #[cfg(feature = "comp")] mod completion; mod dispatcher; +#[cfg(feature = "clap_parser")] +mod dispatcher_clap; mod enum_tag; mod groupped; mod node; @@ -89,6 +91,12 @@ pub fn program_setup(attr: TokenStream, item: TokenStream) -> TokenStream { program_setup::setup_attr(attr, item) } +#[cfg(feature = "clap_parser")] +#[proc_macro_attribute] +pub fn dispatcher_clap(attr: TokenStream, item: TokenStream) -> TokenStream { + dispatcher_clap::dispatcher_clap_attr(attr, item) +} + #[proc_macro_derive(Groupped, attributes(group))] pub fn derive_groupped(input: TokenStream) -> TokenStream { groupped::derive_groupped(input) |
