Skip to main content

tusks_lib/codegen/handle_matches/arms/
function.rs

1use proc_macro2::{Span, TokenStream};
2use quote::quote;
3
4use crate::codegen::module_path::ModulePath;
5use crate::codegen::util::enum_util::to_variant_ident;
6use crate::codegen::util::field_util::is_generated_field;
7use crate::{TusksModule, models::Tusk};
8
9/// Generates the expression for the `Ok(val)` branch of a Result return type.
10fn build_result_ok_handler(ok_ty: &syn::Type, val: TokenStream) -> TokenStream {
11    if Tusk::is_u8_type(ok_ty) {
12        quote! { Some(#val) }
13    } else if Tusk::is_option_u8_type(ok_ty) {
14        quote! { #val }
15    } else {
16        // () or unknown — treat as success
17        quote! { let _ = #val; None }
18    }
19}
20
21impl TusksModule {
22    /// Generates a match arm for a command function.
23    ///
24    /// For `fn my_func(arg1: String, arg2: i32)` this produces:
25    /// ```ignore
26    /// Some(cli::Commands::my_func { arg1: p1, arg2: p2 }) => {
27    ///     super::my_func(p1.clone(), p2.clone());
28    /// }
29    /// ```
30    pub fn build_function_match_arm(
31        &self,
32        tusk: &Tusk,
33        cli_path: &TokenStream,
34        path: &ModulePath,
35    ) -> TokenStream {
36        let variant_ident = to_variant_ident(&tusk.func.sig.ident);
37        let pattern_bindings = self.build_pattern_bindings(tusk);
38        let pattern_fields = self.build_pattern_fields(&pattern_bindings);
39        let function_call = self.build_function_call(tusk, &pattern_bindings, path, false, false);
40
41        quote! {
42            Some(#cli_path::Commands::#variant_ident { #(#pattern_fields),* }) => {
43                #function_call
44            }
45        }
46    }
47
48    pub fn build_default_function_match_arm(
49        &self,
50        tusk: &Tusk,
51        path: &ModulePath,
52        is_external_subcommand_case: bool,
53    ) -> TokenStream {
54        let pattern_bindings = self.build_pattern_bindings(tusk);
55        let function_call = self.build_function_call(
56            tusk, &pattern_bindings, path, true, is_external_subcommand_case,
57        );
58
59        quote! {
60            None => {
61                #function_call
62            }
63        }
64    }
65
66    pub fn build_external_subcommand_match_arm(
67        &self,
68        tusk: &Tusk,
69        path: &ModulePath,
70    ) -> TokenStream {
71        let pattern_bindings = self.build_pattern_bindings(tusk);
72        let function_call = self.build_function_call(
73            tusk, &pattern_bindings, path, false, true,
74        );
75
76        let cli_path = path.cli_path();
77
78        quote! {
79            Some(#cli_path::Commands::ClapExternalSubcommand(external_subcommand_args)) => {
80                #function_call
81            }
82        }
83    }
84
85    fn build_function_call(
86        &self,
87        tusk: &Tusk,
88        pattern_bindings: &[(syn::Ident, syn::Ident)],
89        path: &ModulePath,
90        is_default_case: bool,
91        is_external_subcommand_case: bool,
92    ) -> TokenStream {
93        let func_args = self.build_function_arguments(
94            tusk, pattern_bindings, is_default_case, is_external_subcommand_case,
95        );
96        let func_name = &tusk.func.sig.ident;
97        let func_path = path.super_path_to(func_name);
98        let maybe_await = if tusk.is_async { quote! { .await } } else { quote! {} };
99
100        let call = quote! { #func_path(#(#func_args),*)#maybe_await };
101
102        match &tusk.func.sig.output {
103            syn::ReturnType::Default => {
104                quote! { #call; None }
105            }
106            syn::ReturnType::Type(_, ty) => {
107                if let Some(ok_ty) = Tusk::result_ok_type(ty) {
108                    // Result<T, E> — unwrap with error handling
109                    let ok_handler = build_result_ok_handler(ok_ty, quote! { __ok_val });
110                    quote! {
111                        match #call {
112                            Ok(__ok_val) => { #ok_handler }
113                            Err(__err) => {
114                                eprintln!("Error: {}", __err);
115                                Some(1)
116                            }
117                        }
118                    }
119                } else if Tusk::is_u8_type(ty) {
120                    quote! { Some(#call) }
121                } else if Tusk::is_option_u8_type(ty) {
122                    quote! { #call }
123                } else {
124                    quote! { None }
125                }
126            }
127        }
128    }
129
130    /// Creates bindings `[(field_name, p1), (field_name, p2), ...]`
131    /// for function parameters, skipping the first if it's `&Parameters`.
132    fn build_pattern_bindings(&self, tusk: &Tusk) -> Vec<(syn::Ident, syn::Ident)> {
133        let skip = if self.tusk_has_parameters_arg(tusk) { 1 } else { 0 };
134        let mut bindings = Vec::new();
135        let mut counter = 1;
136
137        for param in tusk.func.sig.inputs.iter().skip(skip) {
138            if let syn::FnArg::Typed(pat_type) = param
139                && let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
140                    let binding = syn::Ident::new(&format!("p{}", counter), Span::call_site());
141                    bindings.push((pat_ident.ident.clone(), binding));
142                    counter += 1;
143                }
144        }
145
146        bindings
147    }
148
149    /// Converts bindings to pattern fields `[field: p1, field: p2, ...]`,
150    /// filtering out generated fields.
151    pub fn build_pattern_fields(
152        &self,
153        pattern_bindings: &[(syn::Ident, syn::Ident)],
154    ) -> Vec<TokenStream> {
155        pattern_bindings
156            .iter()
157            .filter(|(name, _)| !is_generated_field(&name.to_string()))
158            .map(|(name, binding)| quote! { #name: #binding })
159            .collect()
160    }
161
162    fn build_function_arguments(
163        &self,
164        tusk: &Tusk,
165        pattern_bindings: &[(syn::Ident, syn::Ident)],
166        is_default_case: bool,
167        is_external_subcommand_case: bool,
168    ) -> Vec<TokenStream> {
169        let has_params_arg = self.tusk_has_parameters_arg(tusk);
170        let mut func_args = Vec::new();
171
172        let mut number_of_non_params_args = tusk.func.sig.inputs.len();
173        if has_params_arg {
174            func_args.push(quote! { &parameters });
175            number_of_non_params_args -= 1;
176        }
177
178        if is_default_case {
179            if is_external_subcommand_case {
180                func_args.push(quote! { Vec::new() });
181            }
182            return func_args;
183        }
184
185        if is_external_subcommand_case && number_of_non_params_args > 0 {
186            func_args.push(quote! { external_subcommand_args.clone() });
187            return func_args;
188        }
189
190        for (_, binding_name) in pattern_bindings {
191            func_args.push(quote! { #binding_name.clone() });
192        }
193
194        func_args
195    }
196}