tusks_lib/codegen/cli/
module.rs1use quote::quote;
2use proc_macro2::TokenStream;
3use syn::Ident;
4
5use crate::codegen::util::enum_util::to_variant_ident;
6use crate::codegen::util::field_util::is_generated_field;
7
8use crate::{TusksModule, models::{Tusk, TusksParameters}};
9use super::CliCodegen;
10
11impl CliCodegen for TusksModule {
12 fn build_cli(&self, path: Vec<&Ident>, debug: bool) -> TokenStream {
13 let mut items = Vec::new();
14
15 items.push(quote! {use ::tusks::clap;});
16
17 if path.is_empty() {
19 items.push(self.build_cli_struct(debug));
20 }
21
22 if !self.external_modules.is_empty() {
24 items.push(self.build_external_commands_enum(&path, debug));
25 }
26
27 if !self.tusks.is_empty() || !self.external_modules.is_empty() {
29 items.push(self.build_commands_enum(debug));
30 }
31
32 for submodule in &self.submodules {
34 let mut sub_path = path.clone();
35 sub_path.push(&submodule.name);
36 let submod_name = &submodule.name;
37 let submod_content = submodule.build_cli(sub_path, debug);
38
39 items.push(quote! {
40 pub mod #submod_name {
41 #submod_content
42 }
43 });
44 }
45
46 quote! {
47 #(#items)*
48 }
49 }
50}
51
52impl TusksModule {
54 fn build_cli_struct(&self, debug: bool) -> TokenStream {
56 let fields = if let Some(ref params) = self.parameters {
58 self.build_cli_fields_from_parameters(params)
59 } else {
60 quote! {}
61 };
62
63 let subcommand_field = if !self.tusks.is_empty() || !self.external_modules.is_empty() {
65 let subcommand_attr = self.generate_command_attribute_for_subcommands();
66 quote! {
67 #subcommand_attr
68 pub sub: Option<Commands>,
69 }
70 } else {
71 quote! {}
72 };
73
74 let derive_attr = if debug {
75 quote! {}
76 } else {
77 quote! { #[derive(::tusks::clap::Parser)] }
78 };
79
80 let command_attr = self.generate_command_attribute();
81
82 quote! {
83 #derive_attr
84 #command_attr
85 pub struct Cli {
86 #fields
87 #subcommand_field
88 }
89 }
90 }
91
92 fn build_cli_fields_from_parameters(&self, params: &TusksParameters) -> TokenStream {
94 let mut fields = Vec::new();
95
96 for field in ¶ms.pstruct.fields {
97 let field_name = &field.ident;
98
99 if field_name.as_ref().map(|id| is_generated_field(&id.to_string())).unwrap_or(false) {
100 continue;
101 }
102
103 let field_type = Self::dereference_type(&field.ty);
104
105 let attrs: Vec<_> = field.attrs.iter()
107 .filter(|attr| attr.path().is_ident("arg"))
108 .collect();
109
110 fields.push(quote! {
111 #(#attrs)*
112 pub #field_name: #field_type,
113 });
114 }
115
116 quote! {
117 #(#fields)*
118 }
119 }
120
121 fn dereference_type(ty: &syn::Type) -> syn::Type {
124 if let syn::Type::Reference(type_ref) = ty {
125 (*type_ref.elem).clone()
126 } else {
127 ty.clone()
128 }
129 }
130
131 fn build_external_commands_enum(&self, path: &Vec<&Ident>, debug: bool) -> TokenStream {
133 let variants: Vec<_> = self.external_modules.iter().map(|ext_mod| {
134 let variant_ident = to_variant_ident(&ext_mod.alias);
135
136 let mut full_path: Vec<syn::Ident> = (0..path.len() + 2)
138 .map(|_| syn::Ident::new("super", ext_mod.alias.span()))
139 .collect();
140
141 for p in path {
143 full_path.push((*p).clone());
144 }
145
146 full_path.push(ext_mod.alias.clone());
148
149 let command_attr = ext_mod.generate_command_attribute();
150
151 quote! {
152 #command_attr
153 #[allow(non_camel_case_types)]
154 #variant_ident(
155 #(#full_path)::*::__internal_tusks_module::cli::Cli
156 ),
157 }
158 }).collect();
159
160 let derive_attr = if debug {
161 quote! {}
162 } else {
163 quote! { #[derive(::tusks::clap::Subcommand)] }
164 };
165
166 quote! {
167 #derive_attr
168 pub enum ExternalCommands {
169 #(#variants)*
170 }
171 }
172 }
173
174 fn build_commands_enum(&self, debug: bool) -> TokenStream {
176 let mut variants = Vec::new();
177
178 for tusk in &self.tusks {
180 variants.push(self.build_command_variant_from_tusk(tusk));
181 }
182
183 for submodule in &self.submodules {
185 variants.push(self.build_command_variant_from_submodule(submodule));
186 }
187
188 if !self.external_modules.is_empty() {
189 let attr = self.generate_command_attribute_for_external_subcommands();
190 variants.push(quote! {
191 #attr
192 TuskExternalCommands(ExternalCommands),
193 });
194 }
195
196 if self.allow_external_subcommands {
197 variants.push(quote! {
198 #[command(external_subcommand)]
199 ClapExternalSubcommand(Vec<String>),
200 });
201 }
202
203 let derive_attr = if debug {
204 quote! {}
205 } else {
206 quote! { #[derive(::tusks::clap::Subcommand)] }
207 };
208
209 quote! {
210 #derive_attr
211 pub enum Commands {
212 #(#variants)*
213 }
214 }
215 }
216
217 fn build_command_variant_from_tusk(&self, tusk: &Tusk) -> TokenStream {
219 let func_name = &tusk.func.sig.ident;
220 let variant_ident = to_variant_ident(func_name);
221
222 let fields = self.build_fields_from_tusk_params(tusk);
224
225 let command_attr = tusk.generate_command_attribute();
226
227 quote! {
228 #command_attr
229 #[allow(non_camel_case_types)]
230 #variant_ident {
231 #fields
232 },
233 }
234 }
235
236 fn build_fields_from_tusk_params(&self, tusk: &Tusk) -> TokenStream {
238 let mut fields = Vec::new();
239
240 let mut params_iter = tusk.func.sig.inputs.iter();
241
242 let skip_first = if let Some(syn::FnArg::Typed(first_param)) = params_iter.next() {
244 if let Some(ref params) = self.parameters {
245 Self::is_parameters_type(&first_param.ty, ¶ms.pstruct.ident)
247 } else {
248 false
249 }
250 } else {
251 false
252 };
253
254 let params_to_process: Vec<_> = if skip_first {
256 params_iter.collect()
257 } else {
258 tusk.func.sig.inputs.iter().collect()
259 };
260
261 for param in params_to_process {
262 if let syn::FnArg::Typed(pat_type) = param {
263 let param_name = if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
264 &pat_ident.ident
265 } else {
266 continue;
267 };
268
269 let param_type = &pat_type.ty;
270
271 let attrs: Vec<_> = pat_type.attrs.iter()
273 .filter(|attr| attr.path().is_ident("arg"))
274 .collect();
275
276 fields.push(quote! {
277 #(#attrs)*
278 #param_name: #param_type,
279 });
280 }
281 }
282
283 quote! {
284 #(#fields)*
285 }
286 }
287
288 pub fn is_parameters_type(ty: &syn::Type, params_ident: &Ident) -> bool {
290 if let syn::Type::Reference(type_ref) = ty {
291 if let syn::Type::Path(type_path) = &*type_ref.elem {
292 if let Some(segment) = type_path.path.segments.last() {
293 return segment.ident == *params_ident;
294 }
295 }
296 }
297 false
298 }
299
300 fn build_command_variant_from_submodule(&self, submodule: &TusksModule) -> TokenStream {
302 let submod_name = &submodule.name;
303 let variant_ident = to_variant_ident(submod_name);
304
305 let fields = if let Some(ref params) = submodule.parameters {
307 self.build_enum_fields_from_parameters(params)
308 } else {
309 quote! {}
310 };
311
312 let subcommand_field = if !submodule.tusks.is_empty() || !submodule.external_modules.is_empty() {
314 let subcommand_attr = submodule.generate_command_attribute_for_subcommands();
315 quote! {
316 #subcommand_attr
317 sub: Option<#submod_name::Commands>,
318 }
319 } else {
320 quote! {}
321 };
322
323 let command_attr = submodule.generate_command_attribute();
324
325 quote! {
326 #command_attr
327 #[allow(non_camel_case_types)]
328 #variant_ident {
329 #fields
330 #subcommand_field
331 },
332 }
333 }
334
335 fn build_enum_fields_from_parameters(&self, params: &TusksParameters) -> TokenStream {
337 let mut fields = Vec::new();
338
339 for field in ¶ms.pstruct.fields {
340 let field_name = &field.ident;
341
342 if field_name.as_ref().map(|id| is_generated_field(&id.to_string())).unwrap_or(false) {
343 continue;
344 }
345
346 let field_type = Self::dereference_type(&field.ty);
347
348 let attrs: Vec<_> = field.attrs.iter()
350 .filter(|attr| attr.path().is_ident("arg"))
351 .collect();
352
353 fields.push(quote! {
354 #(#attrs)*
355 #field_name: #field_type,
356 });
357 }
358
359 quote! {
360 #(#fields)*
361 }
362 }
363
364
365}