1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Clap CLI argument generation for Config derive macro.
//!
//! Generates ClapArgs struct and CLI argument support.
use darling::FromField;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Fields, Ident};
use crate::parse::{FieldAttrs, StructAttrs};
/// Generate ClapArgs struct for CLI argument parsing.
pub fn generate_clap_impl(
struct_ident: &Ident,
attrs: &StructAttrs,
fields: &Fields,
) -> TokenStream {
let _env_prefix = attrs.effective_env_prefix();
let app_name = attrs.app_name.as_deref().unwrap_or("app");
// Generate field definitions for ClapArgs
let clap_field_defs: Vec<TokenStream> = fields
.iter()
.filter_map(|field| {
let ident = field.ident.as_ref()?;
let field_attrs = FieldAttrs::from_field(field).ok()?;
if field_attrs.skip {
return None;
}
let field_name = field_attrs.effective_name();
let cli_name = field_attrs
.name_clap_long
.clone()
.unwrap_or_else(|| field_name.replace('.', "-"));
// Build arg attributes
let mut arg_parts = vec![quote! { long = #cli_name }];
if let Some(short) = field_attrs.name_clap_short {
arg_parts.push(quote! { short = #short });
}
if let Some(desc) = &field_attrs.description {
arg_parts.push(quote! { help = #desc });
}
// Check if field has a default
let has_default = field_attrs.default.is_some();
let ty = &field.ty;
let type_str = quote!(#ty).to_string();
// Handle optional types - make them optional in CLI
if type_str.contains("Option") {
arg_parts.push(quote! { required = false });
} else if has_default {
// Fields with defaults are optional
arg_parts.push(quote! { required = false });
}
let arg_attr = quote! { #[arg(#(#arg_parts),*)] };
Some(quote! {
#arg_attr
pub #ident: #ty
})
})
.collect();
// Generate field names for to_config_map
let field_idents: Vec<TokenStream> = fields
.iter()
.filter_map(|field| {
let ident = field.ident.as_ref()?;
let attrs = FieldAttrs::from_field(field).ok()?;
if attrs.skip {
return None;
}
Some(quote! { #ident })
})
.collect();
// Create a unique type name based on struct name
let cli_args_ident = quote::format_ident!("{}CliArgs", struct_ident);
quote! {
/// CLI arguments generated from configuration struct.
///
/// # Example
///
/// ```ignore
/// use clap::Parser;
///
/// #[derive(ConfigClap)]
/// struct MyConfig {
/// #[config(name_clap_long = "host", name_clap_short = 'h')]
/// pub host: String,
/// }
///
/// fn main() {
/// let args = <MyConfig as ConfigClap>::clap_args();
/// // ... use args
/// }
/// ```
impl #struct_ident {
/// Generate clap Args struct by parsing command line arguments.
#[allow(dead_code)]
pub fn clap_args() -> #cli_args_ident {
<#cli_args_ident as clap::Parser>::parse()
}
/// Get clap app for custom configuration.
#[allow(dead_code)]
pub fn clap_app() -> clap::Command {
<#cli_args_ident as clap::CommandFactory>::command()
}
/// Create clap args from iterator of strings (for testing).
#[allow(dead_code)]
pub fn clap_args_from<I>(iter: I) -> #cli_args_ident
where
I: Iterator<Item = std::ffi::OsString>,
{
<#cli_args_ident as clap::FromArgMatches>::from_arg_matches(
&<#cli_args_ident as clap::CommandFactory>::command()
.try_get_matches_from(iter)
.unwrap()
)
.unwrap()
}
}
/// CLI arguments struct (use via ConfigClap trait).
#[derive(clap::Parser, Debug)]
#[command(name = #app_name)]
#[allow(dead_code)]
pub struct #cli_args_ident {
#(#clap_field_defs),*
}
impl #cli_args_ident {
/// Convert CLI arguments to a configuration map.
#[allow(dead_code)]
pub fn to_config_map(&self) -> std::collections::HashMap<String, confers::ConfigValue> {
let mut map = std::collections::HashMap::new();
#(
map.insert(
stringify!(#field_idents).to_string(),
confers::ConfigValue::from(self.#field_idents.clone())
);
)*
map
}
}
}
}