// Copyright 2018 Guillaume Pinot (@TeXitoi) , // Kevin Knapp (@kbknapp) , and // Ana Hobden (@hoverbear) // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. // // This work was derived from Structopt (https://github.com/TeXitoi/structopt) // commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the // MIT/Apache 2.0 license. use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; use syn::{spanned::Spanned, Data, DeriveInput, FieldsUnnamed, Generics, Variant}; use crate::derives::args; use crate::derives::args::collect_args_fields; use crate::item::{Item, Kind, Name}; use crate::utils::{is_simple_ty, subty_if_name}; pub fn derive_subcommand(input: &DeriveInput) -> Result { let ident = &input.ident; match input.data { Data::Enum(ref e) => { let name = Name::Derived(ident.clone()); let item = Item::from_subcommand_enum(input, name)?; let variants = e .variants .iter() .map(|variant| { let item = Item::from_subcommand_variant(variant, item.casing(), item.env_casing())?; Ok((variant, item)) }) .collect::, syn::Error>>()?; gen_for_enum(&item, ident, &input.generics, &variants) } _ => abort_call_site!("`#[derive(Subcommand)]` only supports enums"), } } pub fn gen_for_enum( item: &Item, item_name: &Ident, generics: &Generics, variants: &[(&Variant, Item)], ) -> Result { if !matches!(&*item.kind(), Kind::Command(_)) { abort! { item.kind().span(), "`{}` cannot be used with `command`", item.kind().name(), } } let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let from_arg_matches = gen_from_arg_matches(variants)?; let update_from_arg_matches = gen_update_from_arg_matches(variants)?; let augmentation = gen_augment(variants, item, false)?; let augmentation_update = gen_augment(variants, item, true)?; let has_subcommand = gen_has_subcommand(variants)?; Ok(quote! { #[allow( dead_code, unreachable_code, unused_variables, unused_braces, unused_qualifications, )] #[allow( clippy::style, clippy::complexity, clippy::pedantic, clippy::restriction, clippy::perf, clippy::deprecated, clippy::nursery, clippy::cargo, clippy::suspicious_else_formatting, clippy::almost_swapped, )] #[automatically_derived] impl #impl_generics clap::FromArgMatches for #item_name #ty_generics #where_clause { fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result { Self::from_arg_matches_mut(&mut __clap_arg_matches.clone()) } #from_arg_matches fn update_from_arg_matches(&mut self, __clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error> { self.update_from_arg_matches_mut(&mut __clap_arg_matches.clone()) } #update_from_arg_matches } #[allow( dead_code, unreachable_code, unused_variables, unused_braces, unused_qualifications, )] #[allow( clippy::style, clippy::complexity, clippy::pedantic, clippy::restriction, clippy::perf, clippy::deprecated, clippy::nursery, clippy::cargo, clippy::suspicious_else_formatting, clippy::almost_swapped, )] #[automatically_derived] impl #impl_generics clap::Subcommand for #item_name #ty_generics #where_clause { fn augment_subcommands <'b>(__clap_app: clap::Command) -> clap::Command { #augmentation } fn augment_subcommands_for_update <'b>(__clap_app: clap::Command) -> clap::Command { #augmentation_update } fn has_subcommand(__clap_name: &str) -> bool { #has_subcommand } } }) } fn gen_augment( variants: &[(&Variant, Item)], parent_item: &Item, override_required: bool, ) -> Result { use syn::Fields::*; let app_var = Ident::new("__clap_app", Span::call_site()); let mut subcommands = Vec::new(); for (variant, item) in variants { let kind = item.kind(); let genned = match &*kind { Kind::Skip(_, _) | Kind::Arg(_) | Kind::FromGlobal(_) | Kind::Value => None, Kind::ExternalSubcommand => { let ty = match variant.fields { Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, _ => abort!( variant, "The enum variant marked with `external_subcommand` must be \ a single-typed tuple, and the type must be either `Vec` \ or `Vec`." ), }; let deprecations = if !override_required { item.deprecations() } else { quote!() }; let subty = subty_if_name(ty, "Vec").ok_or_else(|| { format_err!( ty.span(), "The type must be `Vec<_>` \ to be used with `external_subcommand`." ) })?; let subcommand = quote_spanned! { kind.span()=> #deprecations let #app_var = #app_var .external_subcommand_value_parser(clap::value_parser!(#subty)); }; Some(subcommand) } Kind::Flatten(_) => match variant.fields { Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0].ty; let deprecations = if !override_required { item.deprecations() } else { quote!() }; let next_help_heading = item.next_help_heading(); let next_display_order = item.next_display_order(); let subcommand = if override_required { quote! { #deprecations let #app_var = #app_var #next_help_heading #next_display_order; let #app_var = <#ty as clap::Subcommand>::augment_subcommands_for_update(#app_var); } } else { quote! { #deprecations let #app_var = #app_var #next_help_heading #next_display_order; let #app_var = <#ty as clap::Subcommand>::augment_subcommands(#app_var); } }; Some(subcommand) } _ => abort!( variant, "`flatten` is usable only with single-typed tuple variants" ), }, Kind::Subcommand(_) => { let subcommand_var = Ident::new("__clap_subcommand", Span::call_site()); let arg_block = match variant.fields { Named(_) => { abort!(variant, "non single-typed tuple enums are not supported") } Unit => quote!( #subcommand_var ), Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0].ty; if override_required { quote_spanned! { ty.span()=> { <#ty as clap::Subcommand>::augment_subcommands_for_update(#subcommand_var) } } } else { quote_spanned! { ty.span()=> { <#ty as clap::Subcommand>::augment_subcommands(#subcommand_var) } } } } Unnamed(..) => { abort!(variant, "non single-typed tuple enums are not supported") } }; let name = item.cased_name(); let deprecations = if !override_required { item.deprecations() } else { quote!() }; let initial_app_methods = item.initial_top_level_methods(); let final_from_attrs = item.final_top_level_methods(); let override_methods = if override_required { quote_spanned! { kind.span()=> .subcommand_required(false) .arg_required_else_help(false) } } else { quote!() }; let subcommand = quote! { let #app_var = #app_var.subcommand({ #deprecations; let #subcommand_var = clap::Command::new(#name); let #subcommand_var = #subcommand_var .subcommand_required(true) .arg_required_else_help(true); let #subcommand_var = #subcommand_var #initial_app_methods; let #subcommand_var = #arg_block; #subcommand_var #final_from_attrs #override_methods }); }; Some(subcommand) } Kind::Command(_) => { let subcommand_var = Ident::new("__clap_subcommand", Span::call_site()); let sub_augment = match variant.fields { Named(ref fields) => { // Defer to `gen_augment` for adding cmd methods let fields = collect_args_fields(item, fields)?; args::gen_augment(&fields, &subcommand_var, item, override_required)? } Unit => { let arg_block = quote!( #subcommand_var ); let initial_app_methods = item.initial_top_level_methods(); let final_from_attrs = item.final_top_level_methods(); quote! { let #subcommand_var = #subcommand_var #initial_app_methods; let #subcommand_var = #arg_block; #subcommand_var #final_from_attrs } } Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0].ty; let arg_block = if override_required { quote_spanned! { ty.span()=> { <#ty as clap::Args>::augment_args_for_update(#subcommand_var) } } } else { quote_spanned! { ty.span()=> { <#ty as clap::Args>::augment_args(#subcommand_var) } } }; let initial_app_methods = item.initial_top_level_methods(); let final_from_attrs = item.final_top_level_methods(); quote! { let #subcommand_var = #subcommand_var #initial_app_methods; let #subcommand_var = #arg_block; #subcommand_var #final_from_attrs } } Unnamed(..) => { abort!(variant, "non single-typed tuple enums are not supported") } }; let deprecations = if !override_required { item.deprecations() } else { quote!() }; let name = item.cased_name(); let subcommand = quote! { let #app_var = #app_var.subcommand({ #deprecations let #subcommand_var = clap::Command::new(#name); #sub_augment }); }; Some(subcommand) } }; subcommands.push(genned); } let deprecations = if !override_required { parent_item.deprecations() } else { quote!() }; let initial_app_methods = parent_item.initial_top_level_methods(); let final_app_methods = parent_item.final_top_level_methods(); Ok(quote! { #deprecations; let #app_var = #app_var #initial_app_methods; #( #subcommands )*; #app_var #final_app_methods }) } fn gen_has_subcommand(variants: &[(&Variant, Item)]) -> Result { use syn::Fields::*; let mut ext_subcmd = false; let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants .iter() .filter_map(|(variant, item)| { let kind = item.kind(); match &*kind { Kind::Skip(_, _) | Kind::Arg(_) | Kind::FromGlobal(_) | Kind::Value => None, Kind::ExternalSubcommand => { ext_subcmd = true; None } Kind::Flatten(_) | Kind::Subcommand(_) | Kind::Command(_) => Some((variant, item)), } }) .partition(|(_, item)| { let kind = item.kind(); matches!(&*kind, Kind::Flatten(_)) }); let subcommands = variants.iter().map(|(_variant, item)| { let sub_name = item.cased_name(); quote! { if #sub_name == __clap_name { return true } } }); let child_subcommands = flatten_variants .iter() .map(|(variant, _attrs)| match variant.fields { Unnamed(ref fields) if fields.unnamed.len() == 1 => { let ty = &fields.unnamed[0].ty; Ok(quote! { if <#ty as clap::Subcommand>::has_subcommand(__clap_name) { return true; } }) } _ => abort!( variant, "`flatten` is usable only with single-typed tuple variants" ), }) .collect::, syn::Error>>()?; let genned = if ext_subcmd { quote! { true } } else { quote! { #( #subcommands )* #( #child_subcommands )else* false } }; Ok(genned) } fn gen_from_arg_matches(variants: &[(&Variant, Item)]) -> Result { use syn::Fields::*; let subcommand_name_var = format_ident!("__clap_name"); let sub_arg_matches_var = format_ident!("__clap_arg_matches"); let mut ext_subcmd = None; let mut flatten_variants = Vec::new(); let mut unflatten_variants = Vec::new(); for (variant, item) in variants { let kind = item.kind(); match &*kind { Kind::Skip(_, _) | Kind::Arg(_) | Kind::FromGlobal(_) | Kind::Value => {} Kind::ExternalSubcommand => { if ext_subcmd.is_some() { abort!( item.kind().span(), "Only one variant can be marked with `external_subcommand`, \ this is the second" ); } let ty = match variant.fields { Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, _ => abort!( variant, "The enum variant marked with `external_subcommand` must be \ a single-typed tuple, and the type must be either `Vec` \ or `Vec`." ), }; let (span, str_ty) = match subty_if_name(ty, "Vec") { Some(subty) => { if is_simple_ty(subty, "String") { (subty.span(), quote!(::std::string::String)) } else if is_simple_ty(subty, "OsString") { (subty.span(), quote!(::std::ffi::OsString)) } else { abort!( ty.span(), "The type must be either `Vec` or `Vec` \ to be used with `external_subcommand`." ); } } None => abort!( ty.span(), "The type must be either `Vec` or `Vec` \ to be used with `external_subcommand`." ), }; ext_subcmd = Some((span, &variant.ident, str_ty)); } Kind::Flatten(_) | Kind::Subcommand(_) | Kind::Command(_) => { if matches!(&*item.kind(), Kind::Flatten(_)) { flatten_variants.push((variant, item)); } else { unflatten_variants.push((variant, item)); } } } } let subcommands = unflatten_variants.iter().map(|(variant, item)| { let sub_name = item.cased_name(); let variant_name = &variant.ident; let constructor_block = match variant.fields { Named(ref fields) => { let fields = collect_args_fields(item, fields)?; args::gen_constructor(&fields)? }, Unit => quote!(), Unnamed(ref fields) if fields.unnamed.len() == 1 => { let ty = &fields.unnamed[0].ty; quote!( ( <#ty as clap::FromArgMatches>::from_arg_matches_mut(__clap_arg_matches)? ) ) } Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident), }; Ok(quote! { if #subcommand_name_var == #sub_name && !#sub_arg_matches_var.contains_id("") { return ::std::result::Result::Ok(Self :: #variant_name #constructor_block) } }) }).collect::, syn::Error>>()?; let child_subcommands = flatten_variants.iter().map(|(variant, _attrs)| { let variant_name = &variant.ident; match variant.fields { Unnamed(ref fields) if fields.unnamed.len() == 1 => { let ty = &fields.unnamed[0].ty; Ok(quote! { if __clap_arg_matches .subcommand_name() .map(|__clap_name| <#ty as clap::Subcommand>::has_subcommand(__clap_name)) .unwrap_or_default() { let __clap_res = <#ty as clap::FromArgMatches>::from_arg_matches_mut(__clap_arg_matches)?; return ::std::result::Result::Ok(Self :: #variant_name (__clap_res)); } }) } _ => abort!( variant, "`flatten` is usable only with single-typed tuple variants" ), } }).collect::, syn::Error>>()?; let wildcard = match ext_subcmd { Some((span, var_name, str_ty)) => quote_spanned! { span=> ::std::result::Result::Ok(Self::#var_name( ::std::iter::once(#str_ty::from(#subcommand_name_var)) .chain( #sub_arg_matches_var .remove_many::<#str_ty>("") .unwrap() .map(#str_ty::from) ) .collect::<::std::vec::Vec<_>>() )) }, None => quote! { ::std::result::Result::Err(clap::Error::raw(clap::error::ErrorKind::InvalidSubcommand, format!("The subcommand '{}' wasn't recognized", #subcommand_name_var))) }, }; let raw_deprecated = args::raw_deprecated(); Ok(quote! { fn from_arg_matches_mut(__clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result { #raw_deprecated #( #child_subcommands )else* if let Some((#subcommand_name_var, mut __clap_arg_sub_matches)) = __clap_arg_matches.remove_subcommand() { let #sub_arg_matches_var = &mut __clap_arg_sub_matches; #( #subcommands )* #wildcard } else { ::std::result::Result::Err(clap::Error::raw(clap::error::ErrorKind::MissingSubcommand, "A subcommand is required but one was not provided.")) } } }) } fn gen_update_from_arg_matches(variants: &[(&Variant, Item)]) -> Result { use syn::Fields::*; let (flatten, variants): (Vec<_>, Vec<_>) = variants .iter() .filter_map(|(variant, item)| { let kind = item.kind(); match &*kind { // Fallback to `from_arg_matches_mut` Kind::Skip(_, _) | Kind::Arg(_) | Kind::FromGlobal(_) | Kind::Value | Kind::ExternalSubcommand => None, Kind::Flatten(_) | Kind::Subcommand(_) | Kind::Command(_) => Some((variant, item)), } }) .partition(|(_, item)| { let kind = item.kind(); matches!(&*kind, Kind::Flatten(_)) }); let subcommands = variants.iter().map(|(variant, item)| { let sub_name = item.cased_name(); let variant_name = &variant.ident; let (pattern, updater) = match variant.fields { Named(ref fields) => { let field_names = fields.named.iter().map(|field| { field.ident.as_ref().unwrap() }).collect::>(); let fields = collect_args_fields(item, fields)?; let update = args::gen_updater(&fields, false)?; (quote!( { #( #field_names, )* }), quote!( { #update } )) } Unit => (quote!(), quote!({})), Unnamed(ref fields) => { if fields.unnamed.len() == 1 { ( quote!((ref mut __clap_arg)), quote!(clap::FromArgMatches::update_from_arg_matches_mut( __clap_arg, __clap_arg_matches )?), ) } else { abort_call_site!("{}: tuple enums are not supported", variant.ident) } } }; Ok(quote! { Self :: #variant_name #pattern if #sub_name == __clap_name => { let (_, mut __clap_arg_sub_matches) = __clap_arg_matches.remove_subcommand().unwrap(); let __clap_arg_matches = &mut __clap_arg_sub_matches; #updater } }) }).collect::, _>>()?; let child_subcommands = flatten.iter().map(|(variant, _attrs)| { let variant_name = &variant.ident; match variant.fields { Unnamed(ref fields) if fields.unnamed.len() == 1 => { let ty = &fields.unnamed[0].ty; Ok(quote! { if <#ty as clap::Subcommand>::has_subcommand(__clap_name) { if let Self :: #variant_name (child) = s { <#ty as clap::FromArgMatches>::update_from_arg_matches_mut(child, __clap_arg_matches)?; return ::std::result::Result::Ok(()); } } }) } _ => abort!( variant, "`flatten` is usable only with single-typed tuple variants" ), } }).collect::, _>>()?; let raw_deprecated = args::raw_deprecated(); Ok(quote! { fn update_from_arg_matches_mut<'b>( &mut self, __clap_arg_matches: &mut clap::ArgMatches, ) -> ::std::result::Result<(), clap::Error> { #raw_deprecated if let Some(__clap_name) = __clap_arg_matches.subcommand_name() { match self { #( #subcommands ),* s => { #( #child_subcommands )* *s = ::from_arg_matches_mut(__clap_arg_matches)?; } } } ::std::result::Result::Ok(()) } }) }