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