aboutsummaryrefslogtreecommitdiff
path: root/src/args_info.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/args_info.rs')
-rw-r--r--src/args_info.rs356
1 files changed, 356 insertions, 0 deletions
diff --git a/src/args_info.rs b/src/args_info.rs
new file mode 100644
index 0000000..ae640a7
--- /dev/null
+++ b/src/args_info.rs
@@ -0,0 +1,356 @@
+// Copyright (c) 2023 Google LLC All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use crate::{
+ enum_only_single_field_unnamed_variants,
+ errors::Errors,
+ help::require_description,
+ parse_attrs::{check_enum_type_attrs, FieldAttrs, FieldKind, TypeAttrs, VariantAttrs},
+ Optionality, StructField,
+};
+use proc_macro2::{Span, TokenStream};
+use quote::{quote, quote_spanned, ToTokens};
+use syn::LitStr;
+
+/// Implement the derive macro for ArgsInfo.
+pub(crate) fn impl_args_info(input: &syn::DeriveInput) -> TokenStream {
+ let errors = &Errors::default();
+
+ // parse the types
+ let type_attrs = &TypeAttrs::parse(errors, input);
+
+ // Based on the type generate the appropriate code.
+ let mut output_tokens = match &input.data {
+ syn::Data::Struct(ds) => {
+ impl_arg_info_struct(errors, &input.ident, type_attrs, &input.generics, ds)
+ }
+ syn::Data::Enum(de) => {
+ impl_arg_info_enum(errors, &input.ident, type_attrs, &input.generics, de)
+ }
+ syn::Data::Union(_) => {
+ errors.err(input, "`#[derive(ArgsInfo)]` cannot be applied to unions");
+ TokenStream::new()
+ }
+ };
+ errors.to_tokens(&mut output_tokens);
+ output_tokens
+}
+
+/// Implement the ArgsInfo trait for a struct annotated with argh attributes.
+fn impl_arg_info_struct(
+ errors: &Errors,
+ name: &syn::Ident,
+ type_attrs: &TypeAttrs,
+ generic_args: &syn::Generics,
+ ds: &syn::DataStruct,
+) -> TokenStream {
+ // Collect the fields, skipping fields that are not supported.
+ let fields = match &ds.fields {
+ syn::Fields::Named(fields) => fields,
+ syn::Fields::Unnamed(_) => {
+ errors.err(
+ &ds.struct_token,
+ "`#![derive(ArgsInfo)]` is not currently supported on tuple structs",
+ );
+ return TokenStream::new();
+ }
+ syn::Fields::Unit => {
+ errors.err(&ds.struct_token, "#![derive(ArgsInfo)]` cannot be applied to unit structs");
+ return TokenStream::new();
+ }
+ };
+
+ // Map the fields into StructField objects.
+ let fields: Vec<_> = fields
+ .named
+ .iter()
+ .filter_map(|field| {
+ let attrs = FieldAttrs::parse(errors, field);
+ StructField::new(errors, field, attrs)
+ })
+ .collect();
+
+ let impl_span = Span::call_site();
+
+ // Generate the implementation of `get_args_info()` for this struct.
+ let args_info = impl_args_info_data(name, errors, type_attrs, &fields);
+
+ // Split out the generics info for the impl declaration.
+ let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
+
+ quote_spanned! { impl_span =>
+ #[automatically_derived]
+ impl #impl_generics argh::ArgsInfo for #name #ty_generics #where_clause {
+ fn get_args_info() -> argh::CommandInfoWithArgs {
+ #args_info
+ }
+ }
+ }
+}
+
+/// Implement ArgsInfo for an enum. The enum is a collection of subcommands.
+fn impl_arg_info_enum(
+ errors: &Errors,
+ name: &syn::Ident,
+ type_attrs: &TypeAttrs,
+ generic_args: &syn::Generics,
+ de: &syn::DataEnum,
+) -> TokenStream {
+ // Validate the enum is OK for argh.
+ check_enum_type_attrs(errors, type_attrs, &de.enum_token.span);
+
+ // Ensure that `#[argh(subcommand)]` is present.
+ if type_attrs.is_subcommand.is_none() {
+ errors.err_span(
+ de.enum_token.span,
+ concat!(
+ "`#![derive(ArgsInfo)]` on `enum`s can only be used to enumerate subcommands.\n",
+ "Consider adding `#[argh(subcommand)]` to the `enum` declaration.",
+ ),
+ );
+ }
+
+ // One of the variants can be annotated as providing dynamic subcommands.
+ // We treat this differently since we need to call a function at runtime
+ // to determine the subcommands provided.
+ let mut dynamic_type_and_variant = None;
+
+ // An enum variant like `<name>(<ty>)`. This is used to collect
+ // the type of the variant for each subcommand.
+ struct ArgInfoVariant<'a> {
+ ty: &'a syn::Type,
+ }
+
+ let variants: Vec<ArgInfoVariant<'_>> = de
+ .variants
+ .iter()
+ .filter_map(|variant| {
+ let name = &variant.ident;
+ let ty = enum_only_single_field_unnamed_variants(errors, &variant.fields)?;
+ if VariantAttrs::parse(errors, variant).is_dynamic.is_some() {
+ if dynamic_type_and_variant.is_some() {
+ errors.err(variant, "Only one variant can have the `dynamic` attribute");
+ }
+ dynamic_type_and_variant = Some((ty, name));
+ None
+ } else {
+ Some(ArgInfoVariant { ty })
+ }
+ })
+ .collect();
+
+ let dynamic_subcommands = if let Some((dynamic_type, _)) = dynamic_type_and_variant {
+ quote! {
+ <#dynamic_type as argh::DynamicSubCommand>::commands().iter()
+ .map(|s|
+ SubCommandInfo {
+ name: s.name,
+ command: CommandInfoWithArgs {
+ name: s.name,
+ description: s.description,
+ ..Default::default()
+ }
+ }).collect()
+ }
+ } else {
+ quote! { vec![]}
+ };
+
+ let variant_ty_info = variants.iter().map(|t| {
+ let ty = t.ty;
+ quote!(
+ argh::SubCommandInfo {
+ name: #ty::get_args_info().name,
+ command: #ty::get_args_info()
+ }
+ )
+ });
+
+ let cmd_name = if let Some(id) = &type_attrs.name {
+ id.clone()
+ } else {
+ LitStr::new("", Span::call_site())
+ };
+
+ let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
+
+ quote! {
+ #[automatically_derived]
+ impl #impl_generics argh::ArgsInfo for #name #ty_generics #where_clause {
+ fn get_args_info() -> argh::CommandInfoWithArgs {
+
+ let mut the_subcommands = vec![#(#variant_ty_info),*];
+ let mut dynamic_commands = #dynamic_subcommands;
+
+ the_subcommands.append(&mut dynamic_commands);
+
+
+ argh::CommandInfoWithArgs {
+ name: #cmd_name,
+ /// A short description of the command's functionality.
+ description: " enum of subcommands",
+ commands: the_subcommands,
+ ..Default::default()
+ }
+ } // end of get_args_ifo
+ } // end of impl ArgsInfo
+ }
+}
+
+fn impl_args_info_data<'a>(
+ name: &proc_macro2::Ident,
+ errors: &Errors,
+ type_attrs: &TypeAttrs,
+ fields: &'a [StructField<'a>],
+) -> TokenStream {
+ let mut subcommands_iter =
+ fields.iter().filter(|field| field.kind == FieldKind::SubCommand).fuse();
+
+ let subcommand: Option<&StructField<'_>> = subcommands_iter.next();
+ for dup_subcommand in subcommands_iter {
+ errors.duplicate_attrs("subcommand", subcommand.unwrap().field, dup_subcommand.field);
+ }
+
+ let impl_span = Span::call_site();
+
+ let mut positionals = vec![];
+ let mut flags = vec![];
+
+ // Add the implicit --help flag
+ flags.push(quote! {
+ argh::FlagInfo {
+ short: None,
+ long: "--help",
+ description: "display usage information",
+ optionality: argh::Optionality::Optional,
+ kind: argh::FlagInfoKind::Switch,
+ hidden: false
+ }
+ });
+
+ for field in fields {
+ let optionality = match field.optionality {
+ Optionality::None => quote! { argh::Optionality::Required },
+ Optionality::Defaulted(_) => quote! { argh::Optionality::Optional },
+ Optionality::Optional => quote! { argh::Optionality::Optional },
+ Optionality::Repeating if field.attrs.greedy.is_some() => {
+ quote! { argh::Optionality::Greedy }
+ }
+ Optionality::Repeating => quote! { argh::Optionality::Repeating },
+ };
+
+ match field.kind {
+ FieldKind::Positional => {
+ let name = field.positional_arg_name();
+
+ let description = if let Some(desc) = &field.attrs.description {
+ desc.content.value().trim().to_owned()
+ } else {
+ String::new()
+ };
+ let hidden = field.attrs.hidden_help;
+
+ positionals.push(quote! {
+ argh::PositionalInfo {
+ name: #name,
+ description: #description,
+ optionality: #optionality,
+ hidden: #hidden,
+ }
+ });
+ }
+ FieldKind::Switch | FieldKind::Option => {
+ let short = if let Some(short) = &field.attrs.short {
+ quote! { Some(#short) }
+ } else {
+ quote! { None }
+ };
+
+ let long = field.long_name.as_ref().expect("missing long name for option");
+
+ let description = require_description(
+ errors,
+ field.name.span(),
+ &field.attrs.description,
+ "field",
+ );
+
+ let kind = if field.kind == FieldKind::Switch {
+ quote! {
+ argh::FlagInfoKind::Switch
+ }
+ } else {
+ let arg_name = if let Some(arg_name) = &field.attrs.arg_name {
+ quote! { #arg_name }
+ } else {
+ let arg_name = long.trim_start_matches("--");
+ quote! { #arg_name }
+ };
+
+ quote! {
+ argh::FlagInfoKind::Option {
+ arg_name: #arg_name,
+ }
+ }
+ };
+
+ let hidden = field.attrs.hidden_help;
+
+ flags.push(quote! {
+ argh::FlagInfo {
+ short: #short,
+ long: #long,
+ description: #description,
+ optionality: #optionality,
+ kind: #kind,
+ hidden: #hidden,
+ }
+ });
+ }
+ FieldKind::SubCommand => {}
+ }
+ }
+
+ let empty_str = syn::LitStr::new("", Span::call_site());
+ let type_name = LitStr::new(&name.to_string(), Span::call_site());
+ let subcommand_name = if type_attrs.is_subcommand.is_some() {
+ type_attrs.name.as_ref().unwrap_or_else(|| {
+ errors.err(name, "`#[argh(name = \"...\")]` attribute is required for subcommands");
+ &empty_str
+ })
+ } else {
+ &type_name
+ };
+
+ let subcommand = if let Some(subcommand) = subcommand {
+ let subcommand_ty = subcommand.ty_without_wrapper;
+ quote! {
+ #subcommand_ty::get_subcommands()
+ }
+ } else {
+ quote! {vec![]}
+ };
+
+ let description =
+ require_description(errors, Span::call_site(), &type_attrs.description, "type");
+ let examples = type_attrs.examples.iter().map(|e| quote! { #e });
+ let notes = type_attrs.notes.iter().map(|e| quote! { #e });
+
+ let error_codes = type_attrs.error_codes.iter().map(|(code, text)| {
+ quote! { argh::ErrorCodeInfo{code:#code, description: #text} }
+ });
+
+ quote_spanned! { impl_span =>
+ argh::CommandInfoWithArgs {
+ name: #subcommand_name,
+ description: #description,
+ examples: &[#( #examples, )*],
+ notes: &[#( #notes, )*],
+ positionals: &[#( #positionals, )*],
+ flags: &[#( #flags, )*],
+ commands: #subcommand,
+ error_codes: &[#( #error_codes, )*],
+ }
+ }
+}