Skip to main content

tusks_lib/codegen/handle_matches/
module.rs

1use proc_macro2::{Span, TokenStream};
2use quote::quote;
3
4use crate::AttributeCheck;
5use crate::codegen::util::enum_util::convert_external_module_to_enum_variant;
6
7use crate::{TusksModule, models::Tusk};
8
9impl TusksModule {
10    /// Generate the handle_matches function
11    pub fn build_handle_matches(&self, is_tusks_root: bool) -> TokenStream {
12        let signature = if is_tusks_root {
13            quote! {
14                pub fn handle_matches(cli: &cli::Cli) -> Option<u8>
15            }
16        } else {
17            quote! {
18                pub fn handle_matches(
19                    cli: &cli::Cli,
20                    super_parameters: &super::parent_::Parameters
21                ) -> Option<u8>
22            }
23        };
24        
25        let params_init = self.build_parameters_initialization();
26        let match_arms = self.build_match_arms_recursive(&[]);
27        
28        quote! {
29            #signature {
30                #params_init
31                
32                let commands = &cli.sub;
33                match commands {
34                    #(#match_arms)*
35                }
36            }
37        }
38    }
39    
40    fn build_parameters_initialization(&self) -> TokenStream {
41        if let Some(ref params) = self.parameters {
42            let mut field_inits = Vec::new();
43            
44            for field in &params.pstruct.fields {
45                if let Some(field_name) = &field.ident {
46                    let field_init = match field_name.to_string().as_str() {
47                        "super_" => quote! { super_: super_parameters, },
48                        "_phantom_lifetime_marker" => quote! {
49                            _phantom_lifetime_marker: ::std::marker::PhantomData,
50                        },
51                        _ => quote! { #field_name: &cli.#field_name, },
52                    };
53                    field_inits.push(field_init);
54                }
55            }
56            
57            quote! {
58                let parameters = super::Parameters {
59                    #(#field_inits)*
60                };
61            }
62        } else {
63            quote! {}
64        }
65    }
66    
67    /// Build match arms recursively with path tracking
68    pub fn build_match_arms_recursive(&self, path: &[&str]) -> Vec<TokenStream> {
69        let mut arms = Vec::new();
70
71        // Build cli path
72        let cli_path = if path.is_empty() {
73            quote! { cli }
74        } else {
75            let path_idents: Vec<_> = path.iter()
76                .map(|p| syn::Ident::new(p, Span::call_site()))
77                .collect();
78            quote! { cli::#(#path_idents)::* }
79        };
80
81        // Arms for tusks
82        for tusk in &self.tusks {
83            arms.push(self.build_function_match_arm(tusk, &cli_path, path));
84        }
85
86        // Default fallback match arm for the module at the current path
87        let has_default_match_arm = false;
88        for tusk in &self.tusks {
89            if tusk.func.has_attr("default") {
90                arms.push(self.build_default_function_match_arm(
91                    tusk,
92                    path,
93                    self.allow_external_subcommands
94                ));
95                if self.allow_external_subcommands {
96                    arms.push(self.build_external_subcommand_match_arm(tusk, path));
97                }
98                break;
99            }
100        }
101
102        if !has_default_match_arm {
103            arms.push(Self::build_no_command_error_arm(path));
104        }
105
106        // Arms for submodules
107        for submodule in &self.submodules {
108            arms.push(submodule.build_submodule_match_arm(&cli_path, path));
109        }
110
111        // Arm for external commands (at ANY level, not just root!)
112        if !self.external_modules.is_empty() {
113            arms.push(self.build_external_arm(&cli_path, path));
114        }
115
116        arms
117    }
118
119    // TODO: there is similar code elsewhere, deduplicate!
120    fn build_no_command_error_arm(path: &[&str]) -> TokenStream {
121        if let Some(&last) = path.last() {
122            quote! {
123                None => {
124                    eprintln!("Subcommand required! Please provide a subcommand for {}!", #last);
125                    Some(1)
126                }
127            }
128        } else {
129            quote! {
130                None => {
131                    eprintln!("Command required! Please provide a command!");
132                    Some(1)
133                }
134            }
135        }
136    }
137    
138    fn build_external_arm(&self, cli_path: &TokenStream, path: &[&str]) -> TokenStream {
139        let mut external_arms = Vec::new();
140
141        for ext_mod in &self.external_modules {
142            let alias = &ext_mod.alias;
143            let variant_ident = convert_external_module_to_enum_variant(alias);
144
145            // Build the correct path to the external module's handle_matches
146            // If we're at root: super::#alias
147            // If we're nested: super::#path[0]::#path[1]::...#alias
148            let external_path = if path.is_empty() {
149                // At root level
150                quote! { super::#alias }
151            } else {
152                // Nested level - need to build full path from root
153                let path_idents: Vec<_> = path.iter()
154                    .map(|p| syn::Ident::new(p, Span::call_site()))
155                    .collect();
156                quote! { super::#(#path_idents)::*::#alias }
157            };
158
159            external_arms.push(quote! {
160                #cli_path::ExternalCommands::#variant_ident(cli) => {
161                    #external_path::__internal_tusks_module::handle_matches(cli, &parameters)
162                }
163            });
164        }
165
166        quote! {
167            Some(#cli_path::Commands::TuskExternalCommands(commands)) => {
168                match commands {
169                    #(#external_arms)*
170                }
171            }
172        }
173    }
174    
175    pub fn tusk_has_parameters_arg(&self, tusk: &Tusk) -> bool {
176        if let Some(syn::FnArg::Typed(first_param)) = tusk.func.sig.inputs.first() {
177            if let Some(ref params) = self.parameters {
178                return Self::is_parameters_type(&first_param.ty, &params.pstruct.ident);
179            }
180        }
181        false
182    }
183}