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 = ¶meters;
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 ¶ms.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 = ¶meters;
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 ¶ms.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 = ¶meters;
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}