Skip to main content

tusks_lib/codegen/handle_matches/arms/
submodule.rs

1use proc_macro2::{Span, TokenStream};
2use quote::quote;
3
4use crate::TusksModule;
5use crate::codegen::util::enum_util::convert_submodule_to_enum_variant;
6
7impl TusksModule {
8    /// Generates a match arm for a submodule in the CLI command enum.
9    /// 
10    /// This function creates a pattern match for a submodule, handling:
11    /// - Parameter pattern binding generation
12    /// - Parameter initialization
13    /// - Nested command matching (if submodule has commands)
14    /// 
15    /// # Example
16    /// Input: submodule "admin" with parameters and commands
17    /// Output: A match arm like:
18    /// ```rust
19    /// Some(Cli::Commands::Admin { user: p1, sub }) => {
20    ///     let super_parameters = &parameters;
21    ///     let parameters = super::admin::Parameters { user: p1, super_: super_parameters };
22    ///     match sub {
23    ///         Some(Cli::Commands::User { id }) => { /* handle user command */ }
24    ///         None => { println!("No function defined for this command!"); }
25    ///     }
26    /// }
27    /// ```
28    pub fn build_submodule_match_arm(&self, cli_path: &TokenStream, path: &[&str]) -> TokenStream {
29        let variant_ident = self.build_variant_ident();
30        let pattern_bindings = self.build_parameter_pattern_bindings();
31        let pattern_fields = self.build_pattern_fields(&pattern_bindings);
32        let has_commands = self.has_commands();
33        let pattern_fields = self.add_sub_field_if_needed(pattern_fields, has_commands);
34        let params_init = self.build_parameter_initialization(&pattern_bindings, path, has_commands);
35        let nested_match = self.build_nested_match_arms(path, has_commands);
36        
37        self.build_final_match_arm(
38            cli_path,
39            variant_ident,
40            pattern_fields,
41            params_init,
42            nested_match
43        )
44    }
45
46    /// Builds the enum variant identifier for the submodule.
47    /// 
48    /// # Example
49    /// Input: submodule name "admin"
50    /// Output: `Admin` (as syn::Ident)
51    fn build_variant_ident(&self) -> syn::Ident {
52        convert_submodule_to_enum_variant(&self.name)
53    }
54
55    /// Generates pattern bindings for submodule parameters.
56    /// 
57    /// Creates bindings like `p1`, `p2`, etc. for parameter fields,
58    /// excluding the special "super_" field.
59    /// 
60    /// # Example
61    /// Input: parameters with fields "user" and "super_"
62    /// Output: vec![("user", p1)]
63    fn build_parameter_pattern_bindings(&self) -> Vec<(syn::Ident, syn::Ident)> {
64        let mut bindings = Vec::new();
65        let mut param_counter = 1;
66
67        if let Some(ref params) = self.parameters {
68            for field in &params.pstruct.fields {
69                if let Some(field_name) = &field.ident {
70                    if field_name != "super_" {
71                        let binding_name = syn::Ident::new(&format!("p{}", param_counter), Span::call_site());
72                        bindings.push((field_name.clone(), binding_name.clone()));
73                        param_counter += 1;
74                    }
75                }
76            }
77        }
78
79        bindings
80    }
81
82    /// Checks if submodule has any commands (tusks, submodules, or external modules).
83    /// 
84    /// # Example
85    /// Input: submodule with tusks or submodules
86    /// Output: true
87    fn has_commands(&self) -> bool {
88        !self.tusks.is_empty() || 
89        !self.submodules.is_empty() || 
90        !self.external_modules.is_empty()
91    }
92
93    /// Adds the "sub" field to pattern if submodule has commands.
94    /// 
95    /// # Example
96    /// Input: pattern_fields = [quote! { user: p1 }], has_commands = true
97    /// Output: [quote! { user: p1 }, quote! { sub }]
98    fn add_sub_field_if_needed(&self, pattern_fields: Vec<TokenStream>, has_commands: bool) -> Vec<TokenStream> {
99        let mut fields = pattern_fields;
100        if has_commands {
101            fields.push(quote! { sub });
102        }
103        fields
104    }
105
106    /// Builds parameter initialization code for the submodule.
107    /// 
108    /// Handles both "super_" field and regular parameters, creating the
109    /// parameters struct with proper path resolution.
110    /// 
111    /// # Example
112    /// Input: parameters with "user" field, path = ["main"]
113    /// Output: quote! {
114    ///     let super_parameters = &parameters;
115    ///     let parameters = super::main::admin::Parameters { user: p1, super_: super_parameters };
116    /// }
117    fn build_parameter_initialization(
118        &self,
119        bindings: &[(syn::Ident, syn::Ident)],
120        path: &[&str],
121        has_commands: bool,
122    ) -> TokenStream {
123        if !has_commands || self.parameters.is_none() {
124            return quote! {};
125        }
126
127        let submod_name = &self.name;
128        let params = self.parameters.as_ref().unwrap();
129        let mut field_inits = Vec::new();
130
131        for field in &params.pstruct.fields {
132            if let Some(field_name) = &field.ident {
133                if field_name == "super_" {
134                    field_inits.push(quote! { super_: super_parameters, });
135                }
136                else if field_name == "_phantom_lifetime_marker" {
137                    field_inits.push(quote! {
138                        _phantom_lifetime_marker: ::std::marker::PhantomData,
139                    });
140                } else {
141                    // Find the binding for this field
142                    if let Some((_, binding_name)) = bindings.iter()
143                        .find(|(fname, _)| fname == field_name) {
144                        field_inits.push(quote! { #field_name: #binding_name, });
145                    }
146                }
147            }
148        }
149
150        // Build parameters path
151        let params_path = if path.is_empty() {
152            quote! { super::#submod_name::Parameters }
153        } else {
154            let path_idents: Vec<_> = path.iter()
155                .map(|p| syn::Ident::new(p, Span::call_site()))
156                .collect();
157            quote! { super::#(#path_idents)::*::#submod_name::Parameters }
158        };
159
160        quote! {
161            let super_parameters = &parameters;
162            let parameters = #params_path {
163                #(#field_inits)*
164            };
165        }
166    }
167
168    /// Builds nested match arms for submodule commands.
169    /// 
170    /// Generates recursive match arms for commands within the submodule.
171    /// 
172    /// # Example
173    /// Input: submodule with commands, path = ["main"]
174    /// Output: quote! {
175    ///     match sub {
176    ///         Some(Cli::Commands::User { id }) => { /* handle user command */ }
177    ///         None => { println!("No function defined for this command!"); }
178    ///     }
179    /// }
180    fn build_nested_match_arms(
181        &self,
182        path: &[&str],
183        has_commands: bool,
184    ) -> TokenStream {
185        if !has_commands {
186            let error_msg = if let Some(&last) = path.last() {
187                quote! { eprintln!("Subcommand required! Please provide a subcommand for {}!", #last); }
188            } else {
189                quote! { eprintln!("Command required! Please provide a command!"); }
190            };
191            return quote! {
192                #error_msg
193                Some(1)
194            };
195        }
196
197        let mut new_path = path.to_vec();
198        let submod_name_str = self.name.to_string();
199        new_path.push(&submod_name_str);
200
201        let nested_arms = self.build_match_arms_recursive(&new_path);
202
203        quote! {
204            match sub {
205                #(#nested_arms)*
206            }
207        }
208    }
209
210    /// Combines all components into the final match arm.
211    /// 
212    /// # Example
213    /// Input: variant_ident = Admin, pattern_fields = [user: p1, sub], params_init, nested_match
214    /// Output: quote! {
215    ///     Some(Cli::Commands::Admin { user: p1, sub }) => {
216    ///         #params_init
217    ///         #nested_match
218    ///     }
219    /// }
220    fn build_final_match_arm(
221        &self,
222        cli_path: &TokenStream,
223        variant_ident: syn::Ident,
224        pattern_fields: Vec<TokenStream>,
225        params_init: TokenStream,
226        nested_match: TokenStream,
227    ) -> TokenStream {
228        quote! {
229            Some(#cli_path::Commands::#variant_ident { #(#pattern_fields),* }) => {
230                #params_init
231                #nested_match
232            }
233        }
234    }
235}