tusks_lib/codegen/cli/
module.rs1use quote::quote;
2use proc_macro2::TokenStream;
3use syn::Ident;
4
5use crate::codegen::util::enum_util::{
6 convert_external_module_to_enum_variant,
7 convert_function_to_enum_variant,
8 convert_submodule_to_enum_variant
9};
10
11use crate::{TusksModule, models::{Tusk, TusksParameters}};
12
13impl TusksModule{
14 pub fn build_cli(&self, path: Vec<&Ident>, debug: bool) -> TokenStream {
16 let mut items = Vec::new();
17
18 items.push(quote! {use ::tusks::clap;});
19
20 if path.is_empty() {
22 items.push(self.build_cli_struct(debug));
23 }
24
25 if !self.external_modules.is_empty() {
27 items.push(self.build_external_commands_enum(&path, debug));
28 }
29
30 if !self.tusks.is_empty() || !self.external_modules.is_empty() {
32 items.push(self.build_commands_enum(debug));
33 }
34
35 for submodule in &self.submodules {
37 let mut sub_path = path.clone();
38 sub_path.push(&submodule.name);
39 let submod_name = &submodule.name;
40 let submod_content = submodule.build_cli(sub_path, debug);
41
42 items.push(quote! {
43 pub mod #submod_name {
44 #submod_content
45 }
46 });
47 }
48
49 quote! {
50 #(#items)*
51 }
52 }
53
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| id == "super_").unwrap_or(false) {
101 continue;
102 }
103
104 if field_name.as_ref().map(|id| id == "_phantom_lifetime_marker").unwrap_or(false) {
106 continue;
107 }
108
109 let field_type = Self::dereference_type(&field.ty);
110
111 let attrs: Vec<_> = field.attrs.iter()
113 .filter(|attr| attr.path().is_ident("arg"))
114 .collect();
115
116 fields.push(quote! {
117 #(#attrs)*
118 pub #field_name: #field_type,
119 });
120 }
121
122 quote! {
123 #(#fields)*
124 }
125 }
126
127 fn dereference_type(ty: &syn::Type) -> syn::Type {
130 if let syn::Type::Reference(type_ref) = ty {
131 (*type_ref.elem).clone()
132 } else {
133 ty.clone()
134 }
135 }
136
137 fn build_external_commands_enum(&self, path: &Vec<&Ident>, debug: bool) -> TokenStream {
139 let variants: Vec<_> = self.external_modules.iter().map(|ext_mod| {
140 let variant_ident = convert_external_module_to_enum_variant(&ext_mod.alias);
141
142 let mut full_path: Vec<syn::Ident> = (0..path.len() + 2)
144 .map(|_| syn::Ident::new("super", ext_mod.alias.span()))
145 .collect();
146
147 for p in path {
149 full_path.push((*p).clone());
150 }
151
152 full_path.push(ext_mod.alias.clone());
154
155 let command_attr = ext_mod.generate_command_attribute();
156
157 quote! {
158 #command_attr
159 #[allow(non_camel_case_types)]
160 #variant_ident(
161 #(#full_path)::*::__internal_tusks_module::cli::Cli
162 ),
163 }
164 }).collect();
165
166 let derive_attr = if debug {
167 quote! {}
168 } else {
169 quote! { #[derive(::tusks::clap::Subcommand)] }
170 };
171
172 quote! {
173 #derive_attr
174 pub enum ExternalCommands {
175 #(#variants)*
176 }
177 }
178 }
179
180 fn build_commands_enum(&self, debug: bool) -> TokenStream {
182 let mut variants = Vec::new();
183
184 for tusk in &self.tusks {
186 variants.push(self.build_command_variant_from_tusk(tusk));
187 }
188
189 for submodule in &self.submodules {
191 variants.push(self.build_command_variant_from_submodule(submodule));
192 }
193
194 if !self.external_modules.is_empty() {
195 let attr = self.generate_command_attribute_for_external_subcommands();
196 variants.push(quote! {
197 #attr
198 TuskExternalCommands(ExternalCommands),
199 });
200 }
201
202 if self.allow_external_subcommands {
203 variants.push(quote! {
204 #[command(external_subcommand)]
205 ClapExternalSubcommand(Vec<String>),
206 });
207 }
208
209 let derive_attr = if debug {
210 quote! {}
211 } else {
212 quote! { #[derive(::tusks::clap::Subcommand)] }
213 };
214
215 quote! {
216 #derive_attr
217 pub enum Commands {
218 #(#variants)*
219 }
220 }
221 }
222
223 fn build_command_variant_from_tusk(&self, tusk: &Tusk) -> TokenStream {
225 let func_name = &tusk.func.sig.ident;
226 let variant_ident = convert_function_to_enum_variant(func_name);
227
228 let fields = self.build_fields_from_tusk_params(tusk);
230
231 let command_attr = tusk.generate_command_attribute();
232
233 quote! {
234 #command_attr
235 #[allow(non_camel_case_types)]
236 #variant_ident {
237 #fields
238 },
239 }
240 }
241
242 fn build_fields_from_tusk_params(&self, tusk: &Tusk) -> TokenStream {
244 let mut fields = Vec::new();
245
246 let mut params_iter = tusk.func.sig.inputs.iter();
247
248 let skip_first = if let Some(syn::FnArg::Typed(first_param)) = params_iter.next() {
250 if let Some(ref params) = self.parameters {
251 Self::is_parameters_type(&first_param.ty, ¶ms.pstruct.ident)
253 } else {
254 false
255 }
256 } else {
257 false
258 };
259
260 let params_to_process: Vec<_> = if skip_first {
262 params_iter.collect()
263 } else {
264 tusk.func.sig.inputs.iter().collect()
265 };
266
267 for param in params_to_process {
268 if let syn::FnArg::Typed(pat_type) = param {
269 let param_name = if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
270 &pat_ident.ident
271 } else {
272 continue;
273 };
274
275 let param_type = &pat_type.ty;
276
277 let attrs: Vec<_> = pat_type.attrs.iter()
279 .filter(|attr| attr.path().is_ident("arg"))
280 .collect();
281
282 if !attrs.is_empty() {
283 fields.push(quote! {
284 #(#attrs)*
285 #param_name: #param_type,
286 });
287 } else {
288 fields.push(quote! {
289 #[arg(long)]
290 #param_name: #param_type,
291 });
292 }
293 }
294 }
295
296 quote! {
297 #(#fields)*
298 }
299 }
300
301 pub fn is_parameters_type(ty: &syn::Type, params_ident: &Ident) -> bool {
303 if let syn::Type::Reference(type_ref) = ty {
304 if let syn::Type::Path(type_path) = &*type_ref.elem {
305 if let Some(segment) = type_path.path.segments.last() {
306 return segment.ident == *params_ident;
307 }
308 }
309 }
310 false
311 }
312
313 fn build_command_variant_from_submodule(&self, submodule: &TusksModule) -> TokenStream {
315 let submod_name = &submodule.name;
316 let variant_ident = convert_submodule_to_enum_variant(submod_name);
317
318 let fields = if let Some(ref params) = submodule.parameters {
320 self.build_enum_fields_from_parameters(params)
321 } else {
322 quote! {}
323 };
324
325 let subcommand_field = if !submodule.tusks.is_empty() || !submodule.external_modules.is_empty() {
327 let subcommand_attr = submodule.generate_command_attribute_for_subcommands();
328 quote! {
329 #subcommand_attr
330 sub: Option<#submod_name::Commands>,
331 }
332 } else {
333 quote! {}
334 };
335
336 let command_attr = submodule.generate_command_attribute();
337
338 quote! {
339 #command_attr
340 #[allow(non_camel_case_types)]
341 #variant_ident {
342 #fields
343 #subcommand_field
344 },
345 }
346 }
347
348 fn build_enum_fields_from_parameters(&self, params: &TusksParameters) -> TokenStream {
350 let mut fields = Vec::new();
351
352 for field in ¶ms.pstruct.fields {
353 let field_name = &field.ident;
354
355 if field_name.as_ref().map(|id| id == "super_").unwrap_or(false) {
357 continue;
358 }
359
360 if field_name.as_ref().map(|id| id == "_phantom_lifetime_marker").unwrap_or(false) {
361 continue;
362 }
363
364 let field_type = Self::dereference_type(&field.ty);
365
366 let attrs: Vec<_> = field.attrs.iter()
368 .filter(|attr| attr.path().is_ident("arg"))
369 .collect();
370
371 fields.push(quote! {
372 #(#attrs)*
373 #field_name: #field_type,
374 });
375 }
376
377 quote! {
378 #(#fields)*
379 }
380 }
381
382
383}