aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/dispatcher_clap.rs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-04-25 22:12:49 +0800
committer魏曹先生 <1992414357@qq.com>2026-04-25 22:19:55 +0800
commit265c79a1e3b20ebf5b2026a55e85cff513eaf9f5 (patch)
tree288fe1136cd9360ff896796e8d5197d74f9533ea /mingling_macros/src/dispatcher_clap.rs
parent019b8def49d814bca44047d85c9ff27bbda36a66 (diff)
Add `dispatcher_clap` attribute macro behind `clap_parser` feature
Diffstat (limited to 'mingling_macros/src/dispatcher_clap.rs')
-rw-r--r--mingling_macros/src/dispatcher_clap.rs188
1 files changed, 188 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()
+}