1use proc_macro::TokenStream;
4use quote::{format_ident, quote};
5use syn::{parse_macro_input, ItemFn};
6
7#[proc_macro_attribute]
8pub fn auto_cli(_attr: TokenStream, item: TokenStream) -> TokenStream {
9 let input = parse_macro_input!(item as ItemFn);
10 let vis = &input.vis;
11 let sig = &input.sig;
12 let fn_name = &sig.ident;
13
14 let doc_attrs: Vec<String> = input
16 .attrs
17 .iter()
18 .filter_map(|a| {
19 if !a.path().is_ident("doc") {
20 return None;
21 }
22 match &a.meta {
23 syn::Meta::NameValue(nv) => {
24 if let syn::Expr::Lit(expr_lit) = &nv.value {
25 if let syn::Lit::Str(s) = &expr_lit.lit {
26 return Some(s.value());
27 }
28 }
29 None
30 }
31 _ => None,
32 }
33 })
34 .collect();
35 let doc_comment = doc_attrs.join("\n");
36
37 let struct_name = format_ident!("{}Args", fn_name);
39
40 let mut fields = Vec::new();
41 let mut call_args = Vec::new();
42
43 for arg in sig.inputs.iter() {
44 let syn::FnArg::Typed(pat_type) = arg else {
45 return syn::Error::new_spanned(arg, "methods with self not supported")
46 .to_compile_error()
47 .into();
48 };
49
50 let ident = match &*pat_type.pat {
51 syn::Pat::Ident(p) => &p.ident,
52 _ => {
53 return syn::Error::new_spanned(&pat_type.pat, "only ident patterns supported")
54 .to_compile_error()
55 .into();
56 }
57 };
58 let ty = &*pat_type.ty;
59
60 let mut default_lit: Option<String> = None;
62 for attr in &pat_type.attrs {
63 if !attr.path().is_ident("arg") {
64 continue;
65 }
66 let _ = attr.parse_nested_meta(|meta| {
67 if meta.path.is_ident("default") {
68 let value = meta.value()?;
69 let lit: syn::LitStr = value.parse()?;
70 default_lit = Some(lit.value());
71 }
72 Ok(())
73 });
74 }
75
76 let clap_attr = if let Some(d) = default_lit {
77 quote! { #[arg(long, default_value = #d)] }
78 } else {
79 quote! { #[arg(long)] }
80 };
81
82 fields.push(quote! {
83 #clap_attr
84 pub #ident: #ty
85 });
86
87 call_args.push(quote! { args.#ident });
88 }
89
90 let fn_name = &sig.ident;
91 let wrapper_name = format_ident!("{}_cli", fn_name);
92
93 let expanded = quote! {
94 #input
95
96 #[derive(Debug, clap::Parser)]
97 #[command(about = #doc_comment)]
98 #vis struct #struct_name {
99 #(#fields,)*
100 }
101
102 #vis fn #wrapper_name() {
103 use clap::Parser;
104 let args = #struct_name::parse();
105 let out = #fn_name(#(#call_args),*);
106 println!("{:?}", out);
107 }
108 };
109
110 expanded.into()
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn it_works() {
119 let result = 4;
120 assert_eq!(result, 4);
121 }
122}