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