tusks_lib/codegen/cli/
module.rs1use quote::quote;
2use proc_macro2::TokenStream;
3use syn::Ident;
4
5use crate::codegen::util::enum_util::{
6 convert_external_module_to_enum_variant,
7 convert_function_to_enum_variant,
8 convert_submodule_to_enum_variant
9};
10
11use crate::{TusksModule, models::{Tusk, TusksParameters}};
12
13impl TusksModule{
14 pub fn build_cli(&self, path: Vec<&Ident>, debug: bool) -> TokenStream {
16 let mut items = Vec::new();
17
18 if path.is_empty() {
20 items.push(self.build_cli_struct(debug));
21 }
22
23 if !self.external_modules.is_empty() {
25 items.push(self.build_external_commands_enum(&path, debug));
26 }
27
28 if !self.tusks.is_empty() || !self.external_modules.is_empty() {
30 items.push(self.build_commands_enum(debug));
31 }
32
33 for submodule in &self.submodules {
35 let mut sub_path = path.clone();
36 sub_path.push(&submodule.name);
37 let submod_name = &submodule.name;
38 let submod_content = submodule.build_cli(sub_path, debug);
39
40 items.push(quote! {
41 pub mod #submod_name {
42 #submod_content
43 }
44 });
45 }
46
47 quote! {
48 #(#items)*
49 }
50 }
51
52 fn build_cli_struct(&self, debug: bool) -> TokenStream {
54 let fields = if let Some(ref params) = self.parameters {
56 self.build_cli_fields_from_parameters(params)
57 } else {
58 quote! {}
59 };
60
61 let subcommand_field = if !self.tusks.is_empty() || !self.external_modules.is_empty() {
63 let subcommand_attr = self.generate_command_attribute_for_subcommands();
64 quote! {
65 #subcommand_attr
66 pub sub: Option<Commands>,
67 }
68 } else {
69 quote! {}
70 };
71
72 let derive_attr = if debug {
73 quote! {}
74 } else {
75 quote! { #[derive(::tusks::clap::Parser)] }
76 };
77
78 let command_attr = self.generate_command_attribute();
79
80 quote! {
81 #derive_attr
82 #command_attr
83 pub struct Cli {
84 #fields
85 #subcommand_field
86 }
87 }
88 }
89
90 fn build_cli_fields_from_parameters(&self, params: &TusksParameters) -> TokenStream {
92 let mut fields = Vec::new();
93
94 for field in ¶ms.pstruct.fields {
95 let field_name = &field.ident;
96
97 if field_name.as_ref().map(|id| id == "super_").unwrap_or(false) {
99 continue;
100 }
101
102 if field_name.as_ref().map(|id| id == "_phantom_lifetime_marker").unwrap_or(false) {
104 continue;
105 }
106
107 let field_type = Self::dereference_type(&field.ty);
108
109 let attrs: Vec<_> = field.attrs.iter()
111 .filter(|attr| attr.path().is_ident("arg"))
112 .collect();
113
114 fields.push(quote! {
115 #(#attrs)*
116 pub #field_name: #field_type,
117 });
118 }
119
120 quote! {
121 #(#fields)*
122 }
123 }
124
125 fn dereference_type(ty: &syn::Type) -> syn::Type {
128 if let syn::Type::Reference(type_ref) = ty {
129 (*type_ref.elem).clone()
130 } else {
131 ty.clone()
132 }
133 }
134
135 fn build_external_commands_enum(&self, path: &Vec<&Ident>, debug: bool) -> TokenStream {
137 let variants: Vec<_> = self.external_modules.iter().map(|ext_mod| {
138 let variant_ident = convert_external_module_to_enum_variant(&ext_mod.alias);
139
140 let mut full_path: Vec<syn::Ident> = (0..path.len() + 2)
142 .map(|_| syn::Ident::new("super", ext_mod.alias.span()))
143 .collect();
144
145 for p in path {
147 full_path.push((*p).clone());
148 }
149
150 full_path.push(ext_mod.alias.clone());
152
153 let command_attr = ext_mod.generate_command_attribute();
154
155 quote! {
156 #command_attr
157 #[allow(non_camel_case_types)]
158 #variant_ident(
159 #(#full_path)::*::__internal_tusks_module::cli::Cli
160 ),
161 }
162 }).collect();
163
164 let derive_attr = if debug {
165 quote! {}
166 } else {
167 quote! { #[derive(::tusks::clap::Subcommand)] }
168 };
169
170 quote! {
171 #derive_attr
172 pub enum ExternalCommands {
173 #(#variants)*
174 }
175 }
176 }
177
178 fn build_commands_enum(&self, debug: bool) -> TokenStream {
180 let mut variants = Vec::new();
181
182 for tusk in &self.tusks {
184 variants.push(self.build_command_variant_from_tusk(tusk));
185 }
186
187 for submodule in &self.submodules {
189 variants.push(self.build_command_variant_from_submodule(submodule));
190 }
191
192 if !self.external_modules.is_empty() {
193 let attr = self.generate_command_attribute_for_external_subcommands();
194 variants.push(quote! {
195 #attr
196 TuskExternalCommands(ExternalCommands),
197 });
198 }
199
200 if self.allow_external_subcommands {
201 variants.push(quote! {
202 #[command(external_subcommand)]
203 ClapExternalSubcommand(Vec<String>),
204 });
205 }
206
207 let derive_attr = if debug {
208 quote! {}
209 } else {
210 quote! { #[derive(::tusks::clap::Subcommand)] }
211 };
212
213 quote! {
214 #derive_attr
215 pub enum Commands {
216 #(#variants)*
217 }
218 }
219 }
220
221 fn build_command_variant_from_tusk(&self, tusk: &Tusk) -> TokenStream {
223 let func_name = &tusk.func.sig.ident;
224 let variant_ident = convert_function_to_enum_variant(func_name);
225
226 let fields = self.build_fields_from_tusk_params(tusk);
228
229 let command_attr = tusk.generate_command_attribute();
230
231 quote! {
232 #command_attr
233 #[allow(non_camel_case_types)]
234 #variant_ident {
235 #fields
236 },
237 }
238 }
239
240 fn build_fields_from_tusk_params(&self, tusk: &Tusk) -> TokenStream {
242 let mut fields = Vec::new();
243
244 let mut params_iter = tusk.func.sig.inputs.iter();
245
246 let skip_first = if let Some(syn::FnArg::Typed(first_param)) = params_iter.next() {
248 if let Some(ref params) = self.parameters {
249 Self::is_parameters_type(&first_param.ty, ¶ms.pstruct.ident)
251 } else {
252 false
253 }
254 } else {
255 false
256 };
257
258 let params_to_process: Vec<_> = if skip_first {
260 params_iter.collect()
261 } else {
262 tusk.func.sig.inputs.iter().collect()
263 };
264
265 for param in params_to_process {
266 if let syn::FnArg::Typed(pat_type) = param {
267 let param_name = if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
268 &pat_ident.ident
269 } else {
270 continue;
271 };
272
273 let param_type = &pat_type.ty;
274
275 let attrs: Vec<_> = pat_type.attrs.iter()
277 .filter(|attr| attr.path().is_ident("arg"))
278 .collect();
279
280 if !attrs.is_empty() {
281 fields.push(quote! {
282 #(#attrs)*
283 #param_name: #param_type,
284 });
285 } else {
286 fields.push(quote! {
287 #[arg(long)]
288 #param_name: #param_type,
289 });
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 if let syn::Type::Path(type_path) = &*type_ref.elem {
303 if let Some(segment) = type_path.path.segments.last() {
304 return segment.ident == *params_ident;
305 }
306 }
307 }
308 false
309 }
310
311 fn build_command_variant_from_submodule(&self, submodule: &TusksModule) -> TokenStream {
313 let submod_name = &submodule.name;
314 let variant_ident = convert_submodule_to_enum_variant(submod_name);
315
316 let fields = if let Some(ref params) = submodule.parameters {
318 self.build_enum_fields_from_parameters(params)
319 } else {
320 quote! {}
321 };
322
323 let subcommand_field = if !submodule.tusks.is_empty() || !submodule.external_modules.is_empty() {
325 let subcommand_attr = submodule.generate_command_attribute_for_subcommands();
326 quote! {
327 #subcommand_attr
328 sub: Option<#submod_name::Commands>,
329 }
330 } else {
331 quote! {}
332 };
333
334 let command_attr = submodule.generate_command_attribute();
335
336 quote! {
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| id == "super_").unwrap_or(false) {
355 continue;
356 }
357
358 if field_name.as_ref().map(|id| id == "_phantom_lifetime_marker").unwrap_or(false) {
359 continue;
360 }
361
362 let field_type = Self::dereference_type(&field.ty);
363
364 let attrs: Vec<_> = field.attrs.iter()
366 .filter(|attr| attr.path().is_ident("arg"))
367 .collect();
368
369 fields.push(quote! {
370 #(#attrs)*
371 #field_name: #field_type,
372 });
373 }
374
375 quote! {
376 #(#fields)*
377 }
378 }
379
380
381}