clawless_derive/lib.rs
1#![cfg_attr(not(doctest),doc = include_str!("../README.md"))]
2#![warn(missing_docs)]
3
4use proc_macro::TokenStream;
5use quote::quote;
6use syn::{ItemFn, parse_macro_input};
7
8use crate::command::CommandGenerator;
9use crate::inventory::InventoryGenerator;
10
11mod command;
12mod inventory;
13
14/// Set up the commands module for a Clawless application
15///
16/// This macro generates the root command for the command-line application and allows subcommands to
17/// be registered under it. It should be called inside `src/commands.rs` or `src/commands/mod.rs` to
18/// follow Clawless's convention.
19///
20/// # Example
21///
22/// ```rust,ignore
23/// // src/commands.rs
24/// mod greet;
25/// mod deploy;
26///
27/// clawless::commands!();
28/// ```
29#[proc_macro]
30pub fn commands(_input: TokenStream) -> TokenStream {
31 let output = quote! {
32 use clawless::prelude::*;
33 #[derive(Debug, clawless::clap::Args)]
34 struct ClawlessEntryPoint {}
35
36 #[clawless::command(require_subcommand, root = true)]
37 async fn clawless(_args: ClawlessEntryPoint, _context: clawless::context::Context) -> clawless::CommandResult {
38 Ok(())
39 }
40 };
41 output.into()
42}
43
44/// Initialize and run a Clawless application
45///
46/// This macro generates the `main` function for a Clawless application.
47/// It should be called in `src/main.rs` after declaring the `commands` module.
48///
49/// # Example
50///
51/// ```rust,ignore
52/// // src/main.rs
53/// mod commands;
54///
55/// clawless::main!();
56/// ```
57#[proc_macro]
58pub fn main(_input: TokenStream) -> TokenStream {
59 let output = quote! {
60 fn main() -> Result<(), Box<dyn std::error::Error>> {
61 let cancellation = clawless::cancellation::Cancellation::new();
62 let context = clawless::context::Context::try_new(cancellation.clone())?;
63
64 let rt = clawless::tokio::runtime::Runtime::new()?;
65 rt.block_on(async {
66 clawless::tokio::spawn(
67 clawless::signal::wait_for_shutdown(cancellation)
68 );
69
70 let app = commands::clawless_init();
71 commands::clawless_exec(app.get_matches(), context).await
72 })?;
73
74 Ok(())
75 }
76 };
77 output.into()
78}
79
80/// Add a command to a Clawless application
81///
82/// This macro attribute can be used to register a function as a (sub)command in
83/// a Clawless application. The name of the function will be used as the name of
84/// the command, and it will be automatically registered as a subcommand under
85/// its parent module.
86///
87/// Command functions must accept exactly two parameters:
88/// 1. An `args` parameter: a `clap::Args` struct with the command's arguments
89/// 2. A `context` parameter: the `Context` providing access to the application environment
90/// and the cancellation token for cooperative shutdown
91///
92/// # Attributes
93///
94/// - `alias = "name"` - Add a visible alias for the command. Can be repeated for multiple aliases.
95/// - `require_subcommand` - Require a subcommand; show help if the command is invoked without one.
96///
97/// # Requiring Subcommands
98///
99/// Use `require_subcommand` to create a command that serves as a container for subcommands. When
100/// this attribute is set, invoking the command without a subcommand will display help instead of
101/// running the command body. This is useful for organizing related commands under a common prefix.
102///
103/// For example, a CLI might have `db migrate`, `db seed`, and `db reset` commands, where `db`
104/// itself requires a subcommand and doesn't perform any action on its own.
105///
106/// # Examples
107///
108/// Basic command:
109///
110/// ```rust,ignore
111/// use clawless::prelude::*;
112///
113/// #[derive(Debug, Args)]
114/// pub struct GreetArgs {
115/// #[arg(short, long)]
116/// name: String,
117/// }
118///
119/// #[command]
120/// pub async fn greet(args: GreetArgs, context: Context) -> CommandResult {
121/// println!("Hello, {}!", args.name);
122/// Ok(())
123/// }
124/// ```
125///
126/// Command with alias:
127///
128/// ```rust,ignore
129/// use clawless::prelude::*;
130///
131/// #[derive(Debug, Args)]
132/// pub struct GenerateArgs {}
133///
134/// // Users can run `mycli generate` or `mycli g`
135/// #[command(alias = "g")]
136/// pub async fn generate(args: GenerateArgs, context: Context) -> CommandResult {
137/// Ok(())
138/// }
139/// ```
140///
141/// Command that requires a subcommand:
142///
143/// ```rust,ignore
144/// use clawless::prelude::*;
145///
146/// #[derive(Debug, Args)]
147/// pub struct DbArgs {}
148///
149/// // Running `mycli db` shows help; users must specify a subcommand like `mycli db migrate`
150/// #[command(require_subcommand, alias = "d")]
151/// pub async fn db(args: DbArgs, context: Context) -> CommandResult {
152/// Ok(())
153/// }
154/// ```
155#[proc_macro_attribute]
156pub fn command(attrs: TokenStream, input: TokenStream) -> TokenStream {
157 let input_function = parse_macro_input!(input as ItemFn);
158
159 let command_generator = match CommandGenerator::new(attrs.into(), input_function.clone()) {
160 Ok(generator) => generator,
161 Err(e) => return e.into_compile_error().into(),
162 };
163 let inventory_generator = InventoryGenerator::new(&command_generator);
164
165 let inventory_struct_for_subcommands = inventory_generator.inventory();
166 let submit_command_to_inventory = inventory_generator.submit_command();
167
168 let initialization_function_for_command = command_generator.initialization_function();
169 let wrapper_function_for_command = command_generator.wrapper_function();
170
171 let output = quote! {
172 #inventory_struct_for_subcommands
173
174 #input_function
175
176 #initialization_function_for_command
177
178 #wrapper_function_for_command
179
180 #submit_command_to_inventory
181 };
182
183 output.into()
184}