1 // Copyright (c) 2023 Google LLC All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 
5 use crate::{
6     enum_only_single_field_unnamed_variants,
7     errors::Errors,
8     help::require_description,
9     parse_attrs::{check_enum_type_attrs, FieldAttrs, FieldKind, TypeAttrs, VariantAttrs},
10     Optionality, StructField,
11 };
12 use proc_macro2::{Span, TokenStream};
13 use quote::{quote, quote_spanned, ToTokens};
14 use syn::LitStr;
15 
16 /// Implement the derive macro for ArgsInfo.
impl_args_info(input: &syn::DeriveInput) -> TokenStream17 pub(crate) fn impl_args_info(input: &syn::DeriveInput) -> TokenStream {
18     let errors = &Errors::default();
19 
20     // parse the types
21     let type_attrs = &TypeAttrs::parse(errors, input);
22 
23     // Based on the type generate the appropriate code.
24     let mut output_tokens = match &input.data {
25         syn::Data::Struct(ds) => {
26             impl_arg_info_struct(errors, &input.ident, type_attrs, &input.generics, ds)
27         }
28         syn::Data::Enum(de) => {
29             impl_arg_info_enum(errors, &input.ident, type_attrs, &input.generics, de)
30         }
31         syn::Data::Union(_) => {
32             errors.err(input, "`#[derive(ArgsInfo)]` cannot be applied to unions");
33             TokenStream::new()
34         }
35     };
36     errors.to_tokens(&mut output_tokens);
37     output_tokens
38 }
39 
40 /// Implement the ArgsInfo trait for a struct annotated with argh attributes.
impl_arg_info_struct( errors: &Errors, name: &syn::Ident, type_attrs: &TypeAttrs, generic_args: &syn::Generics, ds: &syn::DataStruct, ) -> TokenStream41 fn impl_arg_info_struct(
42     errors: &Errors,
43     name: &syn::Ident,
44     type_attrs: &TypeAttrs,
45     generic_args: &syn::Generics,
46     ds: &syn::DataStruct,
47 ) -> TokenStream {
48     // Collect the fields, skipping fields that are not supported.
49     let fields = match &ds.fields {
50         syn::Fields::Named(fields) => fields,
51         syn::Fields::Unnamed(_) => {
52             errors.err(
53                 &ds.struct_token,
54                 "`#![derive(ArgsInfo)]` is not currently supported on tuple structs",
55             );
56             return TokenStream::new();
57         }
58         syn::Fields::Unit => {
59             errors.err(&ds.struct_token, "#![derive(ArgsInfo)]` cannot be applied to unit structs");
60             return TokenStream::new();
61         }
62     };
63 
64     // Map the fields into StructField objects.
65     let fields: Vec<_> = fields
66         .named
67         .iter()
68         .filter_map(|field| {
69             let attrs = FieldAttrs::parse(errors, field);
70             StructField::new(errors, field, attrs)
71         })
72         .collect();
73 
74     let impl_span = Span::call_site();
75 
76     // Generate the implementation of `get_args_info()` for this struct.
77     let args_info = impl_args_info_data(name, errors, type_attrs, &fields);
78 
79     // Split out the generics info for the impl declaration.
80     let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
81 
82     quote_spanned! { impl_span =>
83         #[automatically_derived]
84         impl #impl_generics argh::ArgsInfo for #name #ty_generics #where_clause {
85            fn get_args_info() -> argh::CommandInfoWithArgs {
86             #args_info
87            }
88         }
89     }
90 }
91 
92 /// Implement ArgsInfo for an enum. The enum is a collection of subcommands.
impl_arg_info_enum( errors: &Errors, name: &syn::Ident, type_attrs: &TypeAttrs, generic_args: &syn::Generics, de: &syn::DataEnum, ) -> TokenStream93 fn impl_arg_info_enum(
94     errors: &Errors,
95     name: &syn::Ident,
96     type_attrs: &TypeAttrs,
97     generic_args: &syn::Generics,
98     de: &syn::DataEnum,
99 ) -> TokenStream {
100     // Validate the enum is OK for argh.
101     check_enum_type_attrs(errors, type_attrs, &de.enum_token.span);
102 
103     // Ensure that `#[argh(subcommand)]` is present.
104     if type_attrs.is_subcommand.is_none() {
105         errors.err_span(
106             de.enum_token.span,
107             concat!(
108                 "`#![derive(ArgsInfo)]` on `enum`s can only be used to enumerate subcommands.\n",
109                 "Consider adding `#[argh(subcommand)]` to the `enum` declaration.",
110             ),
111         );
112     }
113 
114     // One of the variants can be annotated as providing dynamic subcommands.
115     // We treat this differently since we need to call a function at runtime
116     // to determine the subcommands provided.
117     let mut dynamic_type_and_variant = None;
118 
119     // An enum variant like `<name>(<ty>)`. This is used to collect
120     // the type of the variant for each subcommand.
121     struct ArgInfoVariant<'a> {
122         ty: &'a syn::Type,
123     }
124 
125     let variants: Vec<ArgInfoVariant<'_>> = de
126         .variants
127         .iter()
128         .filter_map(|variant| {
129             let name = &variant.ident;
130             let ty = enum_only_single_field_unnamed_variants(errors, &variant.fields)?;
131             if VariantAttrs::parse(errors, variant).is_dynamic.is_some() {
132                 if dynamic_type_and_variant.is_some() {
133                     errors.err(variant, "Only one variant can have the `dynamic` attribute");
134                 }
135                 dynamic_type_and_variant = Some((ty, name));
136                 None
137             } else {
138                 Some(ArgInfoVariant { ty })
139             }
140         })
141         .collect();
142 
143     let dynamic_subcommands = if let Some((dynamic_type, _)) = dynamic_type_and_variant {
144         quote! {
145             <#dynamic_type as argh::DynamicSubCommand>::commands().iter()
146             .map(|s|
147          SubCommandInfo {
148                 name: s.name,
149                 command: CommandInfoWithArgs {
150                     name: s.name,
151                     description: s.description,
152                     ..Default::default()
153                 }
154             }).collect()
155         }
156     } else {
157         quote! { vec![]}
158     };
159 
160     let variant_ty_info = variants.iter().map(|t| {
161         let ty = t.ty;
162         quote!(
163             argh::SubCommandInfo {
164                 name: #ty::get_args_info().name,
165                 command: #ty::get_args_info()
166             }
167         )
168     });
169 
170     let cmd_name = if let Some(id) = &type_attrs.name {
171         id.clone()
172     } else {
173         LitStr::new("", Span::call_site())
174     };
175 
176     let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
177 
178     quote! {
179         #[automatically_derived]
180         impl #impl_generics argh::ArgsInfo for #name #ty_generics #where_clause {
181            fn get_args_info() -> argh::CommandInfoWithArgs {
182 
183             let mut the_subcommands = vec![#(#variant_ty_info),*];
184             let mut dynamic_commands = #dynamic_subcommands;
185 
186             the_subcommands.append(&mut dynamic_commands);
187 
188 
189             argh::CommandInfoWithArgs {
190                 name: #cmd_name,
191                /// A short description of the command's functionality.
192                 description: " enum of subcommands",
193                 commands: the_subcommands,
194                 ..Default::default()
195                }
196            } // end of get_args_ifo
197         }  // end of impl ArgsInfo
198     }
199 }
200 
impl_args_info_data<'a>( name: &proc_macro2::Ident, errors: &Errors, type_attrs: &TypeAttrs, fields: &'a [StructField<'a>], ) -> TokenStream201 fn impl_args_info_data<'a>(
202     name: &proc_macro2::Ident,
203     errors: &Errors,
204     type_attrs: &TypeAttrs,
205     fields: &'a [StructField<'a>],
206 ) -> TokenStream {
207     let mut subcommands_iter =
208         fields.iter().filter(|field| field.kind == FieldKind::SubCommand).fuse();
209 
210     let subcommand: Option<&StructField<'_>> = subcommands_iter.next();
211     for dup_subcommand in subcommands_iter {
212         errors.duplicate_attrs("subcommand", subcommand.unwrap().field, dup_subcommand.field);
213     }
214 
215     let impl_span = Span::call_site();
216 
217     let mut positionals = vec![];
218     let mut flags = vec![];
219 
220     // Add the implicit --help flag
221     flags.push(quote! {
222         argh::FlagInfo {
223             short: None,
224             long: "--help",
225             description: "display usage information",
226             optionality: argh::Optionality::Optional,
227             kind: argh::FlagInfoKind::Switch,
228             hidden: false
229         }
230     });
231 
232     for field in fields {
233         let optionality = match field.optionality {
234             Optionality::None => quote! { argh::Optionality::Required },
235             Optionality::Defaulted(_) => quote! { argh::Optionality::Optional },
236             Optionality::Optional => quote! { argh::Optionality::Optional },
237             Optionality::Repeating if field.attrs.greedy.is_some() => {
238                 quote! { argh::Optionality::Greedy }
239             }
240             Optionality::Repeating => quote! { argh::Optionality::Repeating },
241         };
242 
243         match field.kind {
244             FieldKind::Positional => {
245                 let name = field.positional_arg_name();
246 
247                 let description = if let Some(desc) = &field.attrs.description {
248                     desc.content.value().trim().to_owned()
249                 } else {
250                     String::new()
251                 };
252                 let hidden = field.attrs.hidden_help;
253 
254                 positionals.push(quote! {
255                     argh::PositionalInfo {
256                         name: #name,
257                         description: #description,
258                         optionality: #optionality,
259                         hidden: #hidden,
260                     }
261                 });
262             }
263             FieldKind::Switch | FieldKind::Option => {
264                 let short = if let Some(short) = &field.attrs.short {
265                     quote! { Some(#short) }
266                 } else {
267                     quote! { None }
268                 };
269 
270                 let long = field.long_name.as_ref().expect("missing long name for option");
271 
272                 let description = require_description(
273                     errors,
274                     field.name.span(),
275                     &field.attrs.description,
276                     "field",
277                 );
278 
279                 let kind = if field.kind == FieldKind::Switch {
280                     quote! {
281                         argh::FlagInfoKind::Switch
282                     }
283                 } else {
284                     let arg_name = if let Some(arg_name) = &field.attrs.arg_name {
285                         quote! { #arg_name }
286                     } else {
287                         let arg_name = long.trim_start_matches("--");
288                         quote! { #arg_name }
289                     };
290 
291                     quote! {
292                         argh::FlagInfoKind::Option {
293                             arg_name: #arg_name,
294                         }
295                     }
296                 };
297 
298                 let hidden = field.attrs.hidden_help;
299 
300                 flags.push(quote! {
301                     argh::FlagInfo {
302                         short: #short,
303                         long: #long,
304                         description: #description,
305                         optionality: #optionality,
306                         kind: #kind,
307                         hidden: #hidden,
308                     }
309                 });
310             }
311             FieldKind::SubCommand => {}
312         }
313     }
314 
315     let empty_str = syn::LitStr::new("", Span::call_site());
316     let type_name = LitStr::new(&name.to_string(), Span::call_site());
317     let subcommand_name = if type_attrs.is_subcommand.is_some() {
318         type_attrs.name.as_ref().unwrap_or_else(|| {
319             errors.err(name, "`#[argh(name = \"...\")]` attribute is required for subcommands");
320             &empty_str
321         })
322     } else {
323         &type_name
324     };
325 
326     let subcommand = if let Some(subcommand) = subcommand {
327         let subcommand_ty = subcommand.ty_without_wrapper;
328         quote! {
329             #subcommand_ty::get_subcommands()
330         }
331     } else {
332         quote! {vec![]}
333     };
334 
335     let description =
336         require_description(errors, Span::call_site(), &type_attrs.description, "type");
337     let examples = type_attrs.examples.iter().map(|e| quote! { #e });
338     let notes = type_attrs.notes.iter().map(|e| quote! { #e });
339 
340     let error_codes = type_attrs.error_codes.iter().map(|(code, text)| {
341         quote! { argh::ErrorCodeInfo{code:#code, description: #text} }
342     });
343 
344     quote_spanned! { impl_span =>
345         argh::CommandInfoWithArgs {
346             name: #subcommand_name,
347             description: #description,
348             examples: &[#( #examples, )*],
349             notes: &[#( #notes, )*],
350             positionals: &[#( #positionals, )*],
351             flags: &[#( #flags, )*],
352             commands: #subcommand,
353             error_codes: &[#( #error_codes, )*],
354         }
355     }
356 }
357