tusks_lib/codegen/cli/
module.rs

1use 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    /// Generate all code inside `pub mod cli`
15    pub fn build_cli(&self, path: Vec<&Ident>, debug: bool) -> TokenStream {
16        let mut items = Vec::new();
17        
18        // 1. If root (path empty): generate Cli struct
19        if path.is_empty() {
20            items.push(self.build_cli_struct(debug));
21        }
22        
23        // 2. Generate ExternalCommands enum if needed
24        if !self.external_modules.is_empty() {
25            items.push(self.build_external_commands_enum(&path, debug));
26        }
27        
28        // 3. Generate Commands enum if needed
29        if !self.tusks.is_empty() || !self.external_modules.is_empty() {
30            items.push(self.build_commands_enum(debug));
31        }
32        
33        // 4. Generate submodule modules and recurse
34        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    /// Generate the root Cli struct
53    fn build_cli_struct(&self, debug: bool) -> TokenStream {
54        // Extract fields from parameters struct
55        let fields = if let Some(ref params) = self.parameters {
56            self.build_cli_fields_from_parameters(params)
57        } else {
58            quote! {}
59        };
60        
61        // Add subcommand field if we have commands
62        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    /// Build fields for Cli struct from Parameters struct
91    fn build_cli_fields_from_parameters(&self, params: &TusksParameters) -> TokenStream {
92        let mut fields = Vec::new();
93
94        for field in &params.pstruct.fields {
95            let field_name = &field.ident;
96
97            // Skip the super_ field
98            if field_name.as_ref().map(|id| id == "super_").unwrap_or(false) {
99                continue;
100            }
101
102            // Skip the _phantom_lifetime_marker
103            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            // Filter and keep #[arg(...)] attributes with original spans
110            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    /// Convert a reference type to its dereferenced type
126    /// e.g., &Option<String> -> Option<String>
127    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    /// Generate the ExternalCommands enum
136    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            // Anzahl super:: Prefixe: path.len() + 2
141            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            // Originalpfad anhängen
146            for p in path {
147                full_path.push((*p).clone());
148            }
149
150            // Alias anhängen
151            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    /// Generate the Commands enum
179    fn build_commands_enum(&self, debug: bool) -> TokenStream {
180        let mut variants = Vec::new();
181        
182        // Add variants for tusks (command functions)
183        for tusk in &self.tusks {
184            variants.push(self.build_command_variant_from_tusk(tusk));
185        }
186        
187        // Add variants for submodules
188        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    /// Build a command variant from a Tusk (command function)
222    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        // Extract fields from function parameters (skip first parameter which is &Parameters)
227        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    /// Build fields from tusk function parameters
241    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        // Check if first parameter is &Parameters (matching our parameters struct)
247        let skip_first = if let Some(syn::FnArg::Typed(first_param)) = params_iter.next() {
248            if let Some(ref params) = self.parameters {
249                // Check if the type matches &ParametersStructName
250                Self::is_parameters_type(&first_param.ty, &params.pstruct.ident)
251            } else {
252                false
253            }
254        } else {
255            false
256        };
257
258        // If we didn't skip first, reset iterator
259        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                // Filter #[arg(...)] attributes
276                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    /// Check if a type is a reference to a parameters struct
300    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    /// Build a command variant from a submodule
312    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        // Extract fields from submodule's parameters
317        let fields = if let Some(ref params) = submodule.parameters {
318            self.build_enum_fields_from_parameters(params)
319        } else {
320            quote! {}
321        };
322
323        // Add subcommand field if submodule has commands
324        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    /// Build fields for enum variants from Parameters struct (without pub)
347    fn build_enum_fields_from_parameters(&self, params: &TusksParameters) -> TokenStream {
348        let mut fields = Vec::new();
349
350        for field in &params.pstruct.fields {
351            let field_name = &field.ident;
352
353            // Skip the super_ field
354            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            // Filter and keep #[arg(...)] attributes with original spans
365            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}