clapper_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{FnArg, ItemFn, parse_macro_input};
4
5/// Macro to wrap a `main(args: Parser, terminated: Arc<AtomicBool>)` that returns `impl Future<Output = Result<(), E>>`.
6///
7/// On error, it prints the error, then calls `std::process::exit(e.exit_code())`.
8#[proc_macro_attribute]
9pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
10    let ast = parse_macro_input!(item as ItemFn);
11    let sig = &ast.sig;
12    let FnArg::Typed(arg) = sig.inputs.first().expect("no arguments found") else {
13        panic!("first argument must be a clap::Parser");
14    };
15    let arg_ty = &arg.ty;
16    let out = &sig.output;
17    let block = &ast.block;
18    let fn_name = &sig.ident;
19
20    let inner_name = syn::Ident::new(&format!("{}_inner", fn_name), fn_name.span());
21
22    let output = quote! {
23        #[tokio::main]
24        async fn #fn_name() {
25            use ::clapper::prelude::*;
26
27            let args = #arg_ty::parse();
28
29            let terminated = ::std::sync::Arc::new(::std::sync::atomic::AtomicBool::new(false));
30
31            ::clapper::ctrlc::set_handler({
32                let terminated = terminated.clone();
33                move || {
34                    terminated.store(true, ::std::sync::atomic::Ordering::Relaxed);
35                }
36            }).expect("failed to set sigterm handler");
37
38            if let Err(err) = #inner_name(args, terminated).await {
39                eprintln!("Error: {err}");
40                ::std::process::exit(err.exit_code());
41            }
42        }
43
44        async fn #inner_name(args: #arg_ty, terminated: ::std::sync::Arc<::std::sync::atomic::AtomicBool>) #out #block
45    };
46
47    output.into()
48}