Skip to main content

tusks_lib/codegen/cli/
module.rs

1use 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
11impl CliCodegen for TusksModule {
12    fn build_cli(&self, path: Vec<&Ident>, debug: bool) -> TokenStream {
13        let mut items = Vec::new();
14
15        items.push(quote! {use ::tusks::clap;});
16        
17        // 1. If root (path empty): generate Cli struct
18        if path.is_empty() {
19            items.push(self.build_cli_struct(debug));
20        }
21        
22        // 2. Generate ExternalCommands enum if needed
23        if !self.external_modules.is_empty() {
24            items.push(self.build_external_commands_enum(&path, debug));
25        }
26        
27        // 3. Generate Commands enum if needed
28        if !self.tusks.is_empty() || !self.external_modules.is_empty() {
29            items.push(self.build_commands_enum(debug));
30        }
31        
32        // 4. Generate submodule modules and recurse
33        for submodule in &self.submodules {
34            let mut sub_path = path.clone();
35            sub_path.push(&submodule.name);
36            let submod_name = &submodule.name;
37            let submod_content = submodule.build_cli(sub_path, debug);
38            
39            items.push(quote! {
40                pub mod #submod_name {
41                    #submod_content
42                }
43            });
44        }
45        
46        quote! {
47            #(#items)*
48        }
49    }
50}
51
52/// Internal helpers for CLI code generation.
53impl TusksModule {
54    /// Generate the root Cli struct
55    fn build_cli_struct(&self, debug: bool) -> TokenStream {
56        // Extract fields from parameters struct
57        let fields = if let Some(ref params) = self.parameters {
58            self.build_cli_fields_from_parameters(params)
59        } else {
60            quote! {}
61        };
62        
63        // Add subcommand field if we have commands
64        let subcommand_field = if !self.tusks.is_empty() || !self.external_modules.is_empty() {
65            let subcommand_attr = self.generate_command_attribute_for_subcommands();
66            quote! {
67                #subcommand_attr
68                pub sub: Option<Commands>,
69            }
70        } else {
71            quote! {}
72        };
73        
74        let derive_attr = if debug {
75            quote! {}
76        } else {
77            quote! { #[derive(::tusks::clap::Parser)] }
78        };
79        
80        let command_attr = self.generate_command_attribute();
81        
82        quote! {
83            #derive_attr
84            #command_attr
85            pub struct Cli {
86                #fields
87                #subcommand_field
88            }
89        }
90    }
91    
92    /// Build fields for Cli struct from Parameters struct
93    fn build_cli_fields_from_parameters(&self, params: &TusksParameters) -> TokenStream {
94        let mut fields = Vec::new();
95
96        for field in &params.pstruct.fields {
97            let field_name = &field.ident;
98
99            if field_name.as_ref().map(|id| is_generated_field(&id.to_string())).unwrap_or(false) {
100                continue;
101            }
102
103            let field_type = Self::dereference_type(&field.ty);
104
105            // Filter and keep #[arg(...)] attributes with original spans
106            let attrs: Vec<_> = field.attrs.iter()
107                .filter(|attr| attr.path().is_ident("arg"))
108                .collect();
109
110            fields.push(quote! {
111                #(#attrs)*
112                pub #field_name: #field_type,
113            });
114        }
115
116        quote! {
117            #(#fields)*
118        }
119    }
120    
121    /// Convert a reference type to its dereferenced type
122    /// e.g., &Option<String> -> Option<String>
123    fn dereference_type(ty: &syn::Type) -> syn::Type {
124        if let syn::Type::Reference(type_ref) = ty {
125            (*type_ref.elem).clone()
126        } else {
127            ty.clone()
128        }
129    }
130    
131    /// Generate the ExternalCommands enum
132    fn build_external_commands_enum(&self, path: &Vec<&Ident>, debug: bool) -> TokenStream {
133        let variants: Vec<_> = self.external_modules.iter().map(|ext_mod| {
134            let variant_ident = to_variant_ident(&ext_mod.alias);
135
136            // Anzahl super:: Prefixe: path.len() + 2
137            let mut full_path: Vec<syn::Ident> = (0..path.len() + 2)
138                .map(|_| syn::Ident::new("super", ext_mod.alias.span()))
139                .collect();
140
141            // Originalpfad anhängen
142            for p in path {
143                full_path.push((*p).clone());
144            }
145
146            // Alias anhängen
147            full_path.push(ext_mod.alias.clone());
148
149            let command_attr = ext_mod.generate_command_attribute();
150            
151            quote! {
152                #command_attr
153                #[allow(non_camel_case_types)]
154                #variant_ident(
155                    #(#full_path)::*::__internal_tusks_module::cli::Cli
156                ),
157            }
158        }).collect();
159
160        let derive_attr = if debug {
161            quote! {}
162        } else {
163            quote! { #[derive(::tusks::clap::Subcommand)] }
164        };
165
166        quote! {
167            #derive_attr
168            pub enum ExternalCommands {
169                #(#variants)*
170            }
171        }
172    }
173
174    /// Generate the Commands enum
175    fn build_commands_enum(&self, debug: bool) -> TokenStream {
176        let mut variants = Vec::new();
177        
178        // Add variants for tusks (command functions)
179        for tusk in &self.tusks {
180            variants.push(self.build_command_variant_from_tusk(tusk));
181        }
182        
183        // Add variants for submodules
184        for submodule in &self.submodules {
185            variants.push(self.build_command_variant_from_submodule(submodule));
186        }
187        
188        if !self.external_modules.is_empty() {
189            let attr = self.generate_command_attribute_for_external_subcommands();
190            variants.push(quote! {
191                #attr
192                TuskExternalCommands(ExternalCommands),
193            });
194        }
195
196        if self.allow_external_subcommands {
197            variants.push(quote! {
198                #[command(external_subcommand)]
199                ClapExternalSubcommand(Vec<String>),
200            });
201        }
202        
203        let derive_attr = if debug {
204            quote! {}
205        } else {
206            quote! { #[derive(::tusks::clap::Subcommand)] }
207        };
208        
209        quote! {
210            #derive_attr
211            pub enum Commands {
212                #(#variants)*
213            }
214        }
215    }
216    
217    /// Build a command variant from a Tusk (command function)
218    fn build_command_variant_from_tusk(&self, tusk: &Tusk) -> TokenStream {
219        let func_name = &tusk.func.sig.ident;
220        let variant_ident = to_variant_ident(func_name);
221
222        // Extract fields from function parameters (skip first parameter which is &Parameters)
223        let fields = self.build_fields_from_tusk_params(tusk);
224
225        let command_attr = tusk.generate_command_attribute();
226        
227        quote! {
228            #command_attr
229            #[allow(non_camel_case_types)]
230            #variant_ident {
231                #fields
232            },
233        }
234    }
235
236    /// Build fields from tusk function parameters
237    fn build_fields_from_tusk_params(&self, tusk: &Tusk) -> TokenStream {
238        let mut fields = Vec::new();
239
240        let mut params_iter = tusk.func.sig.inputs.iter();
241
242        // Check if first parameter is &Parameters (matching our parameters struct)
243        let skip_first = if let Some(syn::FnArg::Typed(first_param)) = params_iter.next() {
244            if let Some(ref params) = self.parameters {
245                // Check if the type matches &ParametersStructName
246                Self::is_parameters_type(&first_param.ty, &params.pstruct.ident)
247            } else {
248                false
249            }
250        } else {
251            false
252        };
253
254        // If we didn't skip first, reset iterator
255        let params_to_process: Vec<_> = if skip_first {
256            params_iter.collect()
257        } else {
258            tusk.func.sig.inputs.iter().collect()
259        };
260
261        for param in params_to_process {
262            if let syn::FnArg::Typed(pat_type) = param {
263                let param_name = if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
264                    &pat_ident.ident
265                } else {
266                    continue;
267                };
268
269                let param_type = &pat_type.ty;
270
271                // Filter #[arg(...)] attributes
272                let attrs: Vec<_> = pat_type.attrs.iter()
273                    .filter(|attr| attr.path().is_ident("arg"))
274                    .collect();
275
276                fields.push(quote! {
277                    #(#attrs)*
278                    #param_name: #param_type,
279                });
280            }
281        }
282
283        quote! {
284            #(#fields)*
285        }
286    }
287
288    /// Check if a type is a reference to a parameters struct
289    pub fn is_parameters_type(ty: &syn::Type, params_ident: &Ident) -> bool {
290        if let syn::Type::Reference(type_ref) = ty {
291            if let syn::Type::Path(type_path) = &*type_ref.elem {
292                if let Some(segment) = type_path.path.segments.last() {
293                    return segment.ident == *params_ident;
294                }
295            }
296        }
297        false
298    }
299    
300    /// Build a command variant from a submodule
301    fn build_command_variant_from_submodule(&self, submodule: &TusksModule) -> TokenStream {
302        let submod_name = &submodule.name;
303        let variant_ident = to_variant_ident(submod_name);
304
305        // Extract fields from submodule's parameters
306        let fields = if let Some(ref params) = submodule.parameters {
307            self.build_enum_fields_from_parameters(params)
308        } else {
309            quote! {}
310        };
311
312        // Add subcommand field if submodule has commands
313        let subcommand_field = if !submodule.tusks.is_empty() || !submodule.external_modules.is_empty() {
314            let subcommand_attr = submodule.generate_command_attribute_for_subcommands();
315            quote! {
316                #subcommand_attr
317                sub: Option<#submod_name::Commands>,
318            }
319        } else {
320            quote! {}
321        };
322
323        let command_attr = submodule.generate_command_attribute();
324        
325        quote! {
326            #command_attr
327            #[allow(non_camel_case_types)]
328            #variant_ident {
329                #fields
330                #subcommand_field
331            },
332        }
333    }
334
335    /// Build fields for enum variants from Parameters struct (without pub)
336    fn build_enum_fields_from_parameters(&self, params: &TusksParameters) -> TokenStream {
337        let mut fields = Vec::new();
338
339        for field in &params.pstruct.fields {
340            let field_name = &field.ident;
341
342            if field_name.as_ref().map(|id| is_generated_field(&id.to_string())).unwrap_or(false) {
343                continue;
344            }
345
346            let field_type = Self::dereference_type(&field.ty);
347
348            // Filter and keep #[arg(...)] attributes with original spans
349            let attrs: Vec<_> = field.attrs.iter()
350                .filter(|attr| attr.path().is_ident("arg"))
351                .collect();
352
353            fields.push(quote! {
354                #(#attrs)*
355                #field_name: #field_type,
356            });
357        }
358
359        quote! {
360            #(#fields)*
361        }
362    }
363    
364    
365}