Skip to main content

tusks_lib/codegen/handle_matches/
module.rs

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