tusks_lib/codegen/cli/
module.rs1use quote::quote;
2use proc_macro2::TokenStream;
3use syn::Ident;
4
5use crate::codegen::util::enum_util::to_variant_ident;
6use crate::codegen::util::field_util::is_generated_field;
7
8use crate::{TusksModule, models::{Tusk, TusksParameters}};
9use super::CliCodegen;
10
11fn extract_doc_attrs(attrs: &[syn::Attribute]) -> Vec<&syn::Attribute> {
14 attrs.iter().filter(|a| a.path().is_ident("doc")).collect()
15}
16
17impl CliCodegen for TusksModule {
18 fn build_cli(&self, path: Vec<&Ident>, debug: bool) -> TokenStream {
19 let mut items = Vec::new();
20
21 items.push(quote! {use ::tusks::clap;});
22
23 for item_enum in &self.value_enums {
25 let enum_name = &item_enum.ident;
26 items.push(quote! { use super::super::#enum_name; });
27 }
28
29 if path.is_empty() {
31 items.push(self.build_cli_struct(debug));
32 }
33
34 if !self.external_modules.is_empty() {
36 items.push(self.build_external_commands_enum(&path, debug));
37 }
38
39 if !self.tusks.is_empty() || !self.external_modules.is_empty() {
41 items.push(self.build_commands_enum(debug));
42 }
43
44 for submodule in &self.submodules {
46 let mut sub_path = path.clone();
47 sub_path.push(&submodule.name);
48 let submod_name = &submodule.name;
49 let submod_content = submodule.build_cli(sub_path, debug);
50
51 items.push(quote! {
52 pub mod #submod_name {
53 #submod_content
54 }
55 });
56 }
57
58 quote! {
59 #(#items)*
60 }
61 }
62}
63
64impl TusksModule {
66 fn build_cli_struct(&self, debug: bool) -> TokenStream {
68 let fields = if let Some(ref params) = self.parameters {
70 self.build_cli_fields_from_parameters(params)
71 } else {
72 quote! {}
73 };
74
75 let subcommand_field = if !self.tusks.is_empty() || !self.external_modules.is_empty() {
77 let subcommand_attr = self.generate_command_attribute_for_subcommands();
78 quote! {
79 #subcommand_attr
80 pub sub: Option<Commands>,
81 }
82 } else {
83 quote! {}
84 };
85
86 let derive_attr = if debug {
87 quote! {}
88 } else {
89 quote! { #[derive(::tusks::clap::Parser)] }
90 };
91
92 let command_attr = self.generate_command_attribute();
93
94 quote! {
95 #derive_attr
96 #command_attr
97 pub struct Cli {
98 #fields
99 #subcommand_field
100 }
101 }
102 }
103
104 fn build_cli_fields_from_parameters(&self, params: &TusksParameters) -> TokenStream {
106 let mut fields = Vec::new();
107
108 for field in ¶ms.pstruct.fields {
109 let field_name = &field.ident;
110
111 if field_name.as_ref().map(|id| is_generated_field(&id.to_string())).unwrap_or(false) {
112 continue;
113 }
114
115 let field_type = Self::dereference_type(&field.ty);
116
117 let attrs: Vec<_> = field.attrs.iter()
119 .filter(|attr| attr.path().is_ident("arg"))
120 .collect();
121
122 fields.push(quote! {
123 #(#attrs)*
124 pub #field_name: #field_type,
125 });
126 }
127
128 quote! {
129 #(#fields)*
130 }
131 }
132
133 fn dereference_type(ty: &syn::Type) -> syn::Type {
136 if let syn::Type::Reference(type_ref) = ty {
137 (*type_ref.elem).clone()
138 } else {
139 ty.clone()
140 }
141 }
142
143 fn build_external_commands_enum(&self, path: &Vec<&Ident>, debug: bool) -> TokenStream {
145 let variants: Vec<_> = self.external_modules.iter().map(|ext_mod| {
146 let variant_ident = to_variant_ident(&ext_mod.alias);
147
148 let mut full_path: Vec<syn::Ident> = (0..path.len() + 2)
150 .map(|_| syn::Ident::new("super", ext_mod.alias.span()))
151 .collect();
152
153 for p in path {
155 full_path.push((*p).clone());
156 }
157
158 full_path.push(ext_mod.alias.clone());
160
161 let command_attr = ext_mod.generate_command_attribute();
162
163 quote! {
164 #command_attr
165 #[allow(non_camel_case_types)]
166 #variant_ident(
167 #(#full_path)::*::__internal_tusks_module::cli::Cli
168 ),
169 }
170 }).collect();
171
172 let derive_attr = if debug {
173 quote! {}
174 } else {
175 quote! { #[derive(::tusks::clap::Subcommand)] }
176 };
177
178 quote! {
179 #derive_attr
180 pub enum ExternalCommands {
181 #(#variants)*
182 }
183 }
184 }
185
186 fn build_commands_enum(&self, debug: bool) -> TokenStream {
188 let mut variants = Vec::new();
189
190 for tusk in &self.tusks {
192 variants.push(self.build_command_variant_from_tusk(tusk));
193 }
194
195 for submodule in &self.submodules {
197 variants.push(self.build_command_variant_from_submodule(submodule));
198 }
199
200 if !self.external_modules.is_empty() {
201 let attr = self.generate_command_attribute_for_external_subcommands();
202 variants.push(quote! {
203 #attr
204 TuskExternalCommands(ExternalCommands),
205 });
206 }
207
208 if self.allow_external_subcommands {
209 variants.push(quote! {
210 #[command(external_subcommand)]
211 ClapExternalSubcommand(Vec<String>),
212 });
213 }
214
215 let derive_attr = if debug {
216 quote! {}
217 } else {
218 quote! { #[derive(::tusks::clap::Subcommand)] }
219 };
220
221 quote! {
222 #derive_attr
223 pub enum Commands {
224 #(#variants)*
225 }
226 }
227 }
228
229 fn build_command_variant_from_tusk(&self, tusk: &Tusk) -> TokenStream {
231 let func_name = &tusk.func.sig.ident;
232 let variant_ident = to_variant_ident(func_name);
233 let fields = self.build_fields_from_tusk_params(tusk);
234 let command_attr = tusk.generate_command_attribute();
235 let doc_attrs = extract_doc_attrs(&tusk.func.attrs);
236
237 quote! {
238 #(#doc_attrs)*
239 #command_attr
240 #[allow(non_camel_case_types)]
241 #variant_ident {
242 #fields
243 },
244 }
245 }
246
247 fn build_fields_from_tusk_params(&self, tusk: &Tusk) -> TokenStream {
249 let mut fields = Vec::new();
250
251 let mut params_iter = tusk.func.sig.inputs.iter();
252
253 let skip_first = if let Some(syn::FnArg::Typed(first_param)) = params_iter.next() {
255 if let Some(ref params) = self.parameters {
256 Self::is_parameters_type(&first_param.ty, ¶ms.pstruct.ident)
258 } else {
259 false
260 }
261 } else {
262 false
263 };
264
265 let params_to_process: Vec<_> = if skip_first {
267 params_iter.collect()
268 } else {
269 tusk.func.sig.inputs.iter().collect()
270 };
271
272 for param in params_to_process {
273 if let syn::FnArg::Typed(pat_type) = param {
274 let param_name = if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
275 &pat_ident.ident
276 } else {
277 continue;
278 };
279
280 let param_type = &pat_type.ty;
281
282 let attrs: Vec<_> = pat_type.attrs.iter()
284 .filter(|attr| attr.path().is_ident("arg"))
285 .collect();
286
287 fields.push(quote! {
288 #(#attrs)*
289 #param_name: #param_type,
290 });
291 }
292 }
293
294 quote! {
295 #(#fields)*
296 }
297 }
298
299 pub fn is_parameters_type(ty: &syn::Type, params_ident: &Ident) -> bool {
301 if let syn::Type::Reference(type_ref) = ty
302 && let syn::Type::Path(type_path) = &*type_ref.elem
303 && let Some(segment) = type_path.path.segments.last() {
304 return segment.ident == *params_ident;
305 }
306 false
307 }
308
309 fn build_command_variant_from_submodule(&self, submodule: &TusksModule) -> TokenStream {
311 let submod_name = &submodule.name;
312 let variant_ident = to_variant_ident(submod_name);
313
314 let fields = if let Some(ref params) = submodule.parameters {
316 self.build_enum_fields_from_parameters(params)
317 } else {
318 quote! {}
319 };
320
321 let subcommand_field = if !submodule.tusks.is_empty() || !submodule.external_modules.is_empty() {
323 let subcommand_attr = submodule.generate_command_attribute_for_subcommands();
324 quote! {
325 #subcommand_attr
326 sub: Option<#submod_name::Commands>,
327 }
328 } else {
329 quote! {}
330 };
331
332 let command_attr = submodule.generate_command_attribute();
333 let doc_attrs = extract_doc_attrs(&submodule.attrs.0);
334
335 quote! {
336 #(#doc_attrs)*
337 #command_attr
338 #[allow(non_camel_case_types)]
339 #variant_ident {
340 #fields
341 #subcommand_field
342 },
343 }
344 }
345
346 fn build_enum_fields_from_parameters(&self, params: &TusksParameters) -> TokenStream {
348 let mut fields = Vec::new();
349
350 for field in ¶ms.pstruct.fields {
351 let field_name = &field.ident;
352
353 if field_name.as_ref().map(|id| is_generated_field(&id.to_string())).unwrap_or(false) {
354 continue;
355 }
356
357 let field_type = Self::dereference_type(&field.ty);
358
359 let attrs: Vec<_> = field.attrs.iter()
361 .filter(|attr| attr.path().is_ident("arg"))
362 .collect();
363
364 fields.push(quote! {
365 #(#attrs)*
366 #field_name: #field_type,
367 });
368 }
369
370 quote! {
371 #(#fields)*
372 }
373 }
374
375
376}