summaryrefslogtreecommitdiff
path: root/src/derives
diff options
context:
space:
mode:
Diffstat (limited to 'src/derives')
-rw-r--r--src/derives/args.rs735
-rw-r--r--src/derives/into_app.rs93
-rw-r--r--src/derives/mod.rs23
-rw-r--r--src/derives/parser.rs127
-rw-r--r--src/derives/subcommand.rs683
-rw-r--r--src/derives/value_enum.rs127
6 files changed, 1788 insertions, 0 deletions
diff --git a/src/derives/args.rs b/src/derives/args.rs
new file mode 100644
index 0000000..d3b3692
--- /dev/null
+++ b/src/derives/args.rs
@@ -0,0 +1,735 @@
+// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
+// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
+// Ana Hobden (@hoverbear) <operator@hoverbear.org>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 proc_macro_error::{abort, abort_call_site};
+use quote::{format_ident, quote, quote_spanned};
+use syn::ext::IdentExt;
+use syn::{
+ punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataStruct, DeriveInput, Field,
+ Fields, Generics,
+};
+
+use crate::dummies;
+use crate::item::{Item, Kind, Name};
+use crate::utils::{inner_type, sub_type, Sp, Ty};
+
+pub fn derive_args(input: &DeriveInput) -> TokenStream {
+ let ident = &input.ident;
+
+ dummies::args(ident);
+
+ match input.data {
+ Data::Struct(DataStruct {
+ fields: Fields::Named(ref fields),
+ ..
+ }) => {
+ let name = Name::Derived(ident.clone());
+ let item = Item::from_args_struct(input, name);
+ let fields = fields
+ .named
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_struct(&item, ident, &input.generics, &fields)
+ }
+ Data::Struct(DataStruct {
+ fields: Fields::Unit,
+ ..
+ }) => {
+ let name = Name::Derived(ident.clone());
+ let item = Item::from_args_struct(input, name);
+ let fields = Punctuated::<Field, Comma>::new();
+ let fields = fields
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_struct(&item, ident, &input.generics, &fields)
+ }
+ _ => abort_call_site!("`#[derive(Args)]` only supports non-tuple structs"),
+ }
+}
+
+pub fn gen_for_struct(
+ item: &Item,
+ item_name: &Ident,
+ generics: &Generics,
+ fields: &[(&Field, Item)],
+) -> TokenStream {
+ 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 constructor = gen_constructor(fields);
+ let updater = gen_updater(fields, true);
+ let raw_deprecated = raw_deprecated();
+
+ let app_var = Ident::new("__clap_app", Span::call_site());
+ let augmentation = gen_augment(fields, &app_var, item, false);
+ let augmentation_update = gen_augment(fields, &app_var, item, true);
+
+ let group_id = if item.skip_group() {
+ quote!(None)
+ } else {
+ let group_id = item.ident().unraw().to_string();
+ quote!(Some(clap::Id::from(#group_id)))
+ };
+
+ quote! {
+ #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+ #[allow(
+ clippy::style,
+ clippy::complexity,
+ clippy::pedantic,
+ clippy::restriction,
+ clippy::perf,
+ clippy::deprecated,
+ clippy::nursery,
+ clippy::cargo,
+ clippy::suspicious_else_formatting,
+ clippy::almost_swapped,
+ )]
+ 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, clap::Error> {
+ Self::from_arg_matches_mut(&mut __clap_arg_matches.clone())
+ }
+
+ fn from_arg_matches_mut(__clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
+ #raw_deprecated
+ let v = #item_name #constructor;
+ ::std::result::Result::Ok(v)
+ }
+
+ 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())
+ }
+
+ fn update_from_arg_matches_mut(&mut self, __clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<(), clap::Error> {
+ #raw_deprecated
+ #updater
+ ::std::result::Result::Ok(())
+ }
+ }
+
+ #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+ #[allow(
+ clippy::style,
+ clippy::complexity,
+ clippy::pedantic,
+ clippy::restriction,
+ clippy::perf,
+ clippy::deprecated,
+ clippy::nursery,
+ clippy::cargo,
+ clippy::suspicious_else_formatting,
+ clippy::almost_swapped,
+ )]
+ impl #impl_generics clap::Args for #item_name #ty_generics #where_clause {
+ fn group_id() -> Option<clap::Id> {
+ #group_id
+ }
+ fn augment_args<'b>(#app_var: clap::Command) -> clap::Command {
+ #augmentation
+ }
+ fn augment_args_for_update<'b>(#app_var: clap::Command) -> clap::Command {
+ #augmentation_update
+ }
+ }
+ }
+}
+
+/// Generate a block of code to add arguments/subcommands corresponding to
+/// the `fields` to an cmd.
+pub fn gen_augment(
+ fields: &[(&Field, Item)],
+ app_var: &Ident,
+ parent_item: &Item,
+ override_required: bool,
+) -> TokenStream {
+ let mut subcommand_specified = false;
+ let args = fields.iter().filter_map(|(field, item)| {
+ let kind = item.kind();
+ match &*kind {
+ Kind::Command(_)
+ | Kind::Value
+ | Kind::Skip(_, _)
+ | Kind::FromGlobal(_)
+ | Kind::ExternalSubcommand => None,
+ Kind::Subcommand(ty) => {
+ if subcommand_specified {
+ abort!(field.span(), "`#[command(subcommand)]` can only be used once per container");
+ }
+ subcommand_specified = true;
+
+ let subcmd_type = match (**ty, sub_type(&field.ty)) {
+ (Ty::Option, Some(sub_type)) => sub_type,
+ _ => &field.ty,
+ };
+ let implicit_methods = if **ty == Ty::Option {
+ quote!()
+ } else {
+ quote_spanned! { kind.span()=>
+ .subcommand_required(true)
+ .arg_required_else_help(true)
+ }
+ };
+
+ let override_methods = if override_required {
+ quote_spanned! { kind.span()=>
+ .subcommand_required(false)
+ .arg_required_else_help(false)
+ }
+ } else {
+ quote!()
+ };
+
+ Some(quote! {
+ let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands( #app_var );
+ let #app_var = #app_var
+ #implicit_methods
+ #override_methods;
+ })
+ }
+ Kind::Flatten(ty) => {
+ let inner_type = match (**ty, sub_type(&field.ty)) {
+ (Ty::Option, Some(sub_type)) => sub_type,
+ _ => &field.ty,
+ };
+
+ let next_help_heading = item.next_help_heading();
+ let next_display_order = item.next_display_order();
+ if override_required {
+ Some(quote_spanned! { kind.span()=>
+ let #app_var = #app_var
+ #next_help_heading
+ #next_display_order;
+ let #app_var = <#inner_type as clap::Args>::augment_args_for_update(#app_var);
+ })
+ } else {
+ Some(quote_spanned! { kind.span()=>
+ let #app_var = #app_var
+ #next_help_heading
+ #next_display_order;
+ let #app_var = <#inner_type as clap::Args>::augment_args(#app_var);
+ })
+ }
+ }
+ Kind::Arg(ty) => {
+ let value_parser = item.value_parser(&field.ty);
+ let action = item.action(&field.ty);
+ let value_name = item.value_name();
+
+ let implicit_methods = match **ty {
+ Ty::Unit => {
+ // Leaving out `value_parser` as it will always fail
+ quote_spanned! { ty.span()=>
+ .value_name(#value_name)
+ #action
+ }
+ }
+ Ty::Option => {
+ quote_spanned! { ty.span()=>
+ .value_name(#value_name)
+ #value_parser
+ #action
+ }
+ }
+
+ Ty::OptionOption => quote_spanned! { ty.span()=>
+ .value_name(#value_name)
+ .num_args(0..=1)
+ #value_parser
+ #action
+ },
+
+ Ty::OptionVec => {
+ if item.is_positional() {
+ quote_spanned! { ty.span()=>
+ .value_name(#value_name)
+ .num_args(1..) // action won't be sufficient for getting multiple
+ #value_parser
+ #action
+ }
+ } else {
+ quote_spanned! { ty.span()=>
+ .value_name(#value_name)
+ #value_parser
+ #action
+ }
+ }
+ }
+
+ Ty::Vec => {
+ if item.is_positional() {
+ quote_spanned! { ty.span()=>
+ .value_name(#value_name)
+ .num_args(1..) // action won't be sufficient for getting multiple
+ #value_parser
+ #action
+ }
+ } else {
+ quote_spanned! { ty.span()=>
+ .value_name(#value_name)
+ #value_parser
+ #action
+ }
+ }
+ }
+
+ Ty::VecVec | Ty::OptionVecVec => {
+ quote_spanned! { ty.span() =>
+ .value_name(#value_name)
+ #value_parser
+ #action
+ }
+ }
+
+ Ty::Other => {
+ let required = item.find_default_method().is_none();
+ // `ArgAction::takes_values` is assuming `ArgAction::default_value` will be
+ // set though that won't always be true but this should be good enough,
+ // otherwise we'll report an "arg required" error when unwrapping.
+ let action_value = action.args();
+ quote_spanned! { ty.span()=>
+ .value_name(#value_name)
+ .required(#required && #action_value.takes_values())
+ #value_parser
+ #action
+ }
+ }
+ };
+
+ let id = item.id();
+ let explicit_methods = item.field_methods();
+ let deprecations = if !override_required {
+ item.deprecations()
+ } else {
+ quote!()
+ };
+ let override_methods = if override_required {
+ quote_spanned! { kind.span()=>
+ .required(false)
+ }
+ } else {
+ quote!()
+ };
+
+ Some(quote_spanned! { field.span()=>
+ let #app_var = #app_var.arg({
+ #deprecations
+
+ #[allow(deprecated)]
+ let arg = clap::Arg::new(#id)
+ #implicit_methods;
+
+ let arg = arg
+ #explicit_methods;
+
+ let arg = arg
+ #override_methods;
+
+ arg
+ });
+ })
+ }
+ }
+ });
+
+ 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();
+ let group_app_methods = if parent_item.skip_group() {
+ quote!()
+ } else {
+ let group_id = parent_item.ident().unraw().to_string();
+ let literal_group_members = fields
+ .iter()
+ .filter_map(|(_field, item)| {
+ let kind = item.kind();
+ if matches!(*kind, Kind::Arg(_)) {
+ Some(item.id())
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+ let literal_group_members_len = literal_group_members.len();
+ let mut literal_group_members = quote! {{
+ let members: [clap::Id; #literal_group_members_len] = [#( clap::Id::from(#literal_group_members) ),* ];
+ members
+ }};
+ // HACK: Validation isn't ready yet for nested arg groups, so just don't populate the group in
+ // that situation
+ let possible_group_members_len = fields
+ .iter()
+ .filter(|(_field, item)| {
+ let kind = item.kind();
+ matches!(*kind, Kind::Flatten(_))
+ })
+ .count();
+ if 0 < possible_group_members_len {
+ literal_group_members = quote! {{
+ let members: [clap::Id; 0] = [];
+ members
+ }};
+ }
+
+ quote!(
+ .group(
+ clap::ArgGroup::new(#group_id)
+ .multiple(true)
+ .args(#literal_group_members)
+ )
+ )
+ };
+ quote! {{
+ #deprecations
+ let #app_var = #app_var
+ #initial_app_methods
+ #group_app_methods
+ ;
+ #( #args )*
+ #app_var #final_app_methods
+ }}
+}
+
+pub fn gen_constructor(fields: &[(&Field, Item)]) -> TokenStream {
+ let fields = fields.iter().map(|(field, item)| {
+ let field_name = field.ident.as_ref().unwrap();
+ let kind = item.kind();
+ let arg_matches = format_ident!("__clap_arg_matches");
+ match &*kind {
+ Kind::Command(_)
+ | Kind::Value
+ | Kind::ExternalSubcommand => {
+ abort! { kind.span(),
+ "`{}` cannot be used with `arg`",
+ kind.name(),
+ }
+ }
+ Kind::Subcommand(ty) => {
+ let subcmd_type = match (**ty, sub_type(&field.ty)) {
+ (Ty::Option, Some(sub_type)) => sub_type,
+ _ => &field.ty,
+ };
+ match **ty {
+ Ty::Option => {
+ quote_spanned! { kind.span()=>
+ #field_name: {
+ if #arg_matches.subcommand_name().map(<#subcmd_type as clap::Subcommand>::has_subcommand).unwrap_or(false) {
+ Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?)
+ } else {
+ None
+ }
+ }
+ }
+ },
+ Ty::Other => {
+ quote_spanned! { kind.span()=>
+ #field_name: {
+ <#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
+ }
+ }
+ },
+ Ty::Unit |
+ Ty::Vec |
+ Ty::OptionOption |
+ Ty::OptionVec |
+ Ty::VecVec |
+ Ty::OptionVecVec => {
+ abort!(
+ ty.span(),
+ "{} types are not supported for subcommand",
+ ty.as_str()
+ );
+ }
+ }
+ }
+
+ Kind::Flatten(ty) => {
+ let inner_type = match (**ty, sub_type(&field.ty)) {
+ (Ty::Option, Some(sub_type)) => sub_type,
+ _ => &field.ty,
+ };
+ match **ty {
+ Ty::Other => {
+ quote_spanned! { kind.span()=>
+ #field_name: <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
+ }
+ },
+ Ty::Option => {
+ quote_spanned! { kind.span()=>
+ #field_name: {
+ let group_id = <#inner_type as clap::Args>::group_id()
+ .expect("`#[arg(flatten)]`ed field type implements `Args::group_id`");
+ if #arg_matches.contains_id(group_id.as_str()) {
+ Some(
+ <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
+ )
+ } else {
+ None
+ }
+ }
+ }
+ },
+ Ty::Unit |
+ Ty::Vec |
+ Ty::OptionOption |
+ Ty::OptionVec |
+ Ty::VecVec |
+ Ty::OptionVecVec => {
+ abort!(
+ ty.span(),
+ "{} types are not supported for flatten",
+ ty.as_str()
+ );
+ }
+ }
+ },
+
+ Kind::Skip(val, _) => match val {
+ None => quote_spanned!(kind.span()=> #field_name: Default::default()),
+ Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()),
+ },
+
+ Kind::Arg(ty) | Kind::FromGlobal(ty) => {
+ gen_parsers(item, ty, field_name, field, None)
+ }
+ }
+ });
+
+ quote! {{
+ #( #fields ),*
+ }}
+}
+
+pub fn gen_updater(fields: &[(&Field, Item)], use_self: bool) -> TokenStream {
+ let fields = fields.iter().map(|(field, item)| {
+ let field_name = field.ident.as_ref().unwrap();
+ let kind = item.kind();
+
+ let access = if use_self {
+ quote! {
+ #[allow(non_snake_case)]
+ let #field_name = &mut self.#field_name;
+ }
+ } else {
+ quote!()
+ };
+ let arg_matches = format_ident!("__clap_arg_matches");
+
+ match &*kind {
+ Kind::Command(_)
+ | Kind::Value
+ | Kind::ExternalSubcommand => {
+ abort! { kind.span(),
+ "`{}` cannot be used with `arg`",
+ kind.name(),
+ }
+ }
+ Kind::Subcommand(ty) => {
+ let subcmd_type = match (**ty, sub_type(&field.ty)) {
+ (Ty::Option, Some(sub_type)) => sub_type,
+ _ => &field.ty,
+ };
+
+ let updater = quote_spanned! { ty.span()=>
+ <#subcmd_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?;
+ };
+
+ let updater = match **ty {
+ Ty::Option => quote_spanned! { kind.span()=>
+ if let Some(#field_name) = #field_name.as_mut() {
+ #updater
+ } else {
+ *#field_name = Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(
+ #arg_matches
+ )?);
+ }
+ },
+ _ => quote_spanned! { kind.span()=>
+ #updater
+ },
+ };
+
+ quote_spanned! { kind.span()=>
+ {
+ #access
+ #updater
+ }
+ }
+ }
+
+ Kind::Flatten(ty) => {
+ let inner_type = match (**ty, sub_type(&field.ty)) {
+ (Ty::Option, Some(sub_type)) => sub_type,
+ _ => &field.ty,
+ };
+
+ let updater = quote_spanned! { ty.span()=>
+ <#inner_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?;
+ };
+
+ let updater = match **ty {
+ Ty::Option => quote_spanned! { kind.span()=>
+ if let Some(#field_name) = #field_name.as_mut() {
+ #updater
+ } else {
+ *#field_name = Some(<#inner_type as clap::FromArgMatches>::from_arg_matches_mut(
+ #arg_matches
+ )?);
+ }
+ },
+ _ => quote_spanned! { kind.span()=>
+ #updater
+ },
+ };
+
+ quote_spanned! { kind.span()=>
+ {
+ #access
+ #updater
+ }
+ }
+ },
+
+ Kind::Skip(_, _) => quote!(),
+
+ Kind::Arg(ty) | Kind::FromGlobal(ty) => gen_parsers(item, ty, field_name, field, Some(&access)),
+ }
+ });
+
+ quote! {
+ #( #fields )*
+ }
+}
+
+fn gen_parsers(
+ item: &Item,
+ ty: &Sp<Ty>,
+ field_name: &Ident,
+ field: &Field,
+ update: Option<&TokenStream>,
+) -> TokenStream {
+ let span = ty.span();
+ let convert_type = inner_type(&field.ty);
+ let id = item.id();
+ let get_one = quote_spanned!(span=> remove_one::<#convert_type>);
+ let get_many = quote_spanned!(span=> remove_many::<#convert_type>);
+ let get_occurrences = quote_spanned!(span=> remove_occurrences::<#convert_type>);
+
+ // Give this identifier the same hygiene
+ // as the `arg_matches` parameter definition. This
+ // allows us to refer to `arg_matches` within a `quote_spanned` block
+ let arg_matches = format_ident!("__clap_arg_matches");
+
+ let field_value = match **ty {
+ Ty::Unit => {
+ quote_spanned! { ty.span()=>
+ ()
+ }
+ }
+
+ Ty::Option => {
+ quote_spanned! { ty.span()=>
+ #arg_matches.#get_one(#id)
+ }
+ }
+
+ Ty::OptionOption => quote_spanned! { ty.span()=>
+ if #arg_matches.contains_id(#id) {
+ Some(
+ #arg_matches.#get_one(#id)
+ )
+ } else {
+ None
+ }
+ },
+
+ Ty::OptionVec => quote_spanned! { ty.span()=>
+ if #arg_matches.contains_id(#id) {
+ Some(#arg_matches.#get_many(#id)
+ .map(|v| v.collect::<Vec<_>>())
+ .unwrap_or_else(Vec::new))
+ } else {
+ None
+ }
+ },
+
+ Ty::Vec => {
+ quote_spanned! { ty.span()=>
+ #arg_matches.#get_many(#id)
+ .map(|v| v.collect::<Vec<_>>())
+ .unwrap_or_else(Vec::new)
+ }
+ }
+
+ Ty::VecVec => quote_spanned! { ty.span()=>
+ #arg_matches.#get_occurrences(#id)
+ .map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
+ .unwrap_or_else(Vec::new)
+ },
+
+ Ty::OptionVecVec => quote_spanned! { ty.span()=>
+ #arg_matches.#get_occurrences(#id)
+ .map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
+ },
+
+ Ty::Other => {
+ quote_spanned! { ty.span()=>
+ #arg_matches.#get_one(#id)
+ .ok_or_else(|| clap::Error::raw(clap::error::ErrorKind::MissingRequiredArgument, format!("The following required argument was not provided: {}", #id)))?
+ }
+ }
+ };
+
+ if let Some(access) = update {
+ quote_spanned! { field.span()=>
+ if #arg_matches.contains_id(#id) {
+ #access
+ *#field_name = #field_value
+ }
+ }
+ } else {
+ quote_spanned!(field.span()=> #field_name: #field_value )
+ }
+}
+
+#[cfg(feature = "raw-deprecated")]
+pub fn raw_deprecated() -> TokenStream {
+ quote! {}
+}
+
+#[cfg(not(feature = "raw-deprecated"))]
+pub fn raw_deprecated() -> TokenStream {
+ quote! {
+ #![allow(deprecated)] // Assuming any deprecation in here will be related to a deprecation in `Args`
+
+ }
+}
diff --git a/src/derives/into_app.rs b/src/derives/into_app.rs
new file mode 100644
index 0000000..dfb676a
--- /dev/null
+++ b/src/derives/into_app.rs
@@ -0,0 +1,93 @@
+// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
+// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
+// Ana Hobden (@hoverbear) <operator@hoverbear.org>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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::{Span, TokenStream};
+use quote::quote;
+use syn::{Generics, Ident};
+
+use crate::item::Item;
+
+pub fn gen_for_struct(item: &Item, item_name: &Ident, generics: &Generics) -> TokenStream {
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ let name = item.cased_name();
+ let app_var = Ident::new("__clap_app", Span::call_site());
+
+ let tokens = quote! {
+ #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+ #[allow(
+ clippy::style,
+ clippy::complexity,
+ clippy::pedantic,
+ clippy::restriction,
+ clippy::perf,
+ clippy::deprecated,
+ clippy::nursery,
+ clippy::cargo,
+ clippy::suspicious_else_formatting,
+ clippy::almost_swapped,
+ )]
+ impl #impl_generics clap::CommandFactory for #item_name #ty_generics #where_clause {
+ fn command<'b>() -> clap::Command {
+ let #app_var = clap::Command::new(#name);
+ <Self as clap::Args>::augment_args(#app_var)
+ }
+
+ fn command_for_update<'b>() -> clap::Command {
+ let #app_var = clap::Command::new(#name);
+ <Self as clap::Args>::augment_args_for_update(#app_var)
+ }
+ }
+ };
+
+ tokens
+}
+
+pub fn gen_for_enum(item: &Item, item_name: &Ident, generics: &Generics) -> TokenStream {
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ let name = item.cased_name();
+ let app_var = Ident::new("__clap_app", Span::call_site());
+
+ quote! {
+ #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+ #[allow(
+ clippy::style,
+ clippy::complexity,
+ clippy::pedantic,
+ clippy::restriction,
+ clippy::perf,
+ clippy::deprecated,
+ clippy::nursery,
+ clippy::cargo,
+ clippy::suspicious_else_formatting,
+ clippy::almost_swapped,
+ )]
+ impl #impl_generics clap::CommandFactory for #item_name #ty_generics #where_clause {
+ fn command<'b>() -> clap::Command {
+ let #app_var = clap::Command::new(#name)
+ .subcommand_required(true)
+ .arg_required_else_help(true);
+ <Self as clap::Subcommand>::augment_subcommands(#app_var)
+ }
+
+ fn command_for_update<'b>() -> clap::Command {
+ let #app_var = clap::Command::new(#name);
+ <Self as clap::Subcommand>::augment_subcommands_for_update(#app_var)
+ .subcommand_required(false)
+ .arg_required_else_help(false)
+ }
+ }
+ }
+}
diff --git a/src/derives/mod.rs b/src/derives/mod.rs
new file mode 100644
index 0000000..3deeb91
--- /dev/null
+++ b/src/derives/mod.rs
@@ -0,0 +1,23 @@
+// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
+// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
+// Ana Hobden (@hoverbear) <operator@hoverbear.org>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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.
+mod args;
+mod into_app;
+mod parser;
+mod subcommand;
+mod value_enum;
+
+pub use self::parser::derive_parser;
+pub use args::derive_args;
+pub use subcommand::derive_subcommand;
+pub use value_enum::derive_value_enum;
diff --git a/src/derives/parser.rs b/src/derives/parser.rs
new file mode 100644
index 0000000..617857c
--- /dev/null
+++ b/src/derives/parser.rs
@@ -0,0 +1,127 @@
+// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
+// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
+// Ana Hobden (@hoverbear) <operator@hoverbear.org>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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::TokenStream;
+use proc_macro_error::abort_call_site;
+use quote::quote;
+use syn::Ident;
+use syn::Variant;
+use syn::{
+ self, punctuated::Punctuated, token::Comma, Data, DataStruct, DeriveInput, Field, Fields,
+ Generics,
+};
+
+use crate::derives::{args, into_app, subcommand};
+use crate::dummies;
+use crate::item::Item;
+use crate::item::Name;
+
+pub fn derive_parser(input: &DeriveInput) -> TokenStream {
+ let ident = &input.ident;
+ let pkg_name = std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
+
+ match input.data {
+ Data::Struct(DataStruct {
+ fields: Fields::Named(ref fields),
+ ..
+ }) => {
+ dummies::parser_struct(ident);
+
+ let name = Name::Assigned(quote!(#pkg_name));
+ let item = Item::from_args_struct(input, name);
+ let fields = fields
+ .named
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_struct(&item, ident, &input.generics, &fields)
+ }
+ Data::Struct(DataStruct {
+ fields: Fields::Unit,
+ ..
+ }) => {
+ dummies::parser_struct(ident);
+
+ let name = Name::Assigned(quote!(#pkg_name));
+ let item = Item::from_args_struct(input, name);
+ let fields = Punctuated::<Field, Comma>::new();
+ let fields = fields
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_struct(&item, ident, &input.generics, &fields)
+ }
+ Data::Enum(ref e) => {
+ dummies::parser_enum(ident);
+
+ let name = Name::Assigned(quote!(#pkg_name));
+ 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());
+ (variant, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_enum(&item, ident, &input.generics, &variants)
+ }
+ _ => abort_call_site!("`#[derive(Parser)]` only supports non-tuple structs and enums"),
+ }
+}
+
+fn gen_for_struct(
+ item: &Item,
+ item_name: &Ident,
+ generics: &Generics,
+ fields: &[(&Field, Item)],
+) -> TokenStream {
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ let into_app = into_app::gen_for_struct(item, item_name, generics);
+ let args = args::gen_for_struct(item, item_name, generics, fields);
+
+ quote! {
+ impl #impl_generics clap::Parser for #item_name #ty_generics #where_clause {}
+
+ #into_app
+ #args
+ }
+}
+
+fn gen_for_enum(
+ item: &Item,
+ item_name: &Ident,
+ generics: &Generics,
+ variants: &[(&Variant, Item)],
+) -> TokenStream {
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ let into_app = into_app::gen_for_enum(item, item_name, generics);
+ let subcommand = subcommand::gen_for_enum(item, item_name, generics, variants);
+
+ quote! {
+ impl #impl_generics clap::Parser for #item_name #ty_generics #where_clause {}
+
+ #into_app
+ #subcommand
+ }
+}
diff --git a/src/derives/subcommand.rs b/src/derives/subcommand.rs
new file mode 100644
index 0000000..ffe22ec
--- /dev/null
+++ b/src/derives/subcommand.rs
@@ -0,0 +1,683 @@
+// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
+// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
+// Ana Hobden (@hoverbear) <operator@hoverbear.org>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 proc_macro_error::{abort, abort_call_site};
+use quote::{format_ident, quote, quote_spanned};
+use syn::{spanned::Spanned, Data, DeriveInput, FieldsUnnamed, Generics, Variant};
+
+use crate::derives::args;
+use crate::dummies;
+use crate::item::{Item, Kind, Name};
+use crate::utils::{is_simple_ty, subty_if_name};
+
+pub fn derive_subcommand(input: &DeriveInput) -> TokenStream {
+ let ident = &input.ident;
+
+ dummies::subcommand(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());
+ (variant, item)
+ })
+ .collect::<Vec<_>>();
+ 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)],
+) -> TokenStream {
+ 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);
+
+ quote! {
+ #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+ #[allow(
+ clippy::style,
+ clippy::complexity,
+ clippy::pedantic,
+ clippy::restriction,
+ clippy::perf,
+ clippy::deprecated,
+ clippy::nursery,
+ clippy::cargo,
+ clippy::suspicious_else_formatting,
+ clippy::almost_swapped,
+ )]
+ 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, clap::Error> {
+ 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)]
+ #[allow(
+ clippy::style,
+ clippy::complexity,
+ clippy::pedantic,
+ clippy::restriction,
+ clippy::perf,
+ clippy::deprecated,
+ clippy::nursery,
+ clippy::cargo,
+ clippy::suspicious_else_formatting,
+ clippy::almost_swapped,
+ )]
+ 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,
+) -> TokenStream {
+ use syn::Fields::*;
+
+ let app_var = Ident::new("__clap_app", Span::call_site());
+
+ let subcommands: Vec<_> = variants
+ .iter()
+ .filter_map(|(variant, item)| {
+ let kind = item.kind();
+
+ 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<String>` \
+ or `Vec<OsString>`."
+ ),
+ };
+ let deprecations = if !override_required {
+ item.deprecations()
+ } else {
+ quote!()
+ };
+ let subty = subty_if_name(ty, "Vec").unwrap_or_else(|| {
+ abort!(
+ 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];
+ 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];
+ 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 = fields
+ .named
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ 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];
+ 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)
+ }
+ }
+ })
+ .collect();
+
+ 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();
+ quote! {
+ #deprecations;
+ let #app_var = #app_var #initial_app_methods;
+ #( #subcommands )*;
+ #app_var #final_app_methods
+ }
+}
+
+fn gen_has_subcommand(variants: &[(&Variant, Item)]) -> TokenStream {
+ 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];
+ quote! {
+ if <#ty as clap::Subcommand>::has_subcommand(__clap_name) {
+ return true;
+ }
+ }
+ }
+ _ => abort!(
+ variant,
+ "`flatten` is usable only with single-typed tuple variants"
+ ),
+ });
+
+ if ext_subcmd {
+ quote! { true }
+ } else {
+ quote! {
+ #( #subcommands )*
+
+ #( #child_subcommands )else*
+
+ false
+ }
+ }
+}
+
+fn gen_from_arg_matches(variants: &[(&Variant, Item)]) -> TokenStream {
+ use syn::Fields::*;
+
+ let mut ext_subcmd = None;
+
+ let subcommand_name_var = format_ident!("__clap_name");
+ let sub_arg_matches_var = format_ident!("__clap_arg_matches");
+ 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 => {
+ 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<String>` \
+ or `Vec<OsString>`."
+ ),
+ };
+
+ 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<String>` or `Vec<OsString>` \
+ to be used with `external_subcommand`."
+ );
+ }
+ }
+
+ None => abort!(
+ ty.span(),
+ "The type must be either `Vec<String>` or `Vec<OsString>` \
+ to be used with `external_subcommand`."
+ ),
+ };
+
+ ext_subcmd = Some((span, &variant.ident, str_ty));
+ 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 constructor_block = match variant.fields {
+ Named(ref fields) => {
+ let fields = fields
+ .named
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ args::gen_constructor(&fields)
+ },
+ Unit => quote!(),
+ Unnamed(ref fields) if fields.unnamed.len() == 1 => {
+ let ty = &fields.unnamed[0];
+ quote!( ( <#ty as clap::FromArgMatches>::from_arg_matches_mut(__clap_arg_matches)? ) )
+ }
+ Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
+ };
+
+ quote! {
+ if #subcommand_name_var == #sub_name && !#sub_arg_matches_var.contains_id("") {
+ return ::std::result::Result::Ok(Self :: #variant_name #constructor_block)
+ }
+ }
+ });
+ 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];
+ 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"
+ ),
+ }
+ });
+
+ 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();
+ quote! {
+ fn from_arg_matches_mut(__clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
+ #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)]) -> TokenStream {
+ 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::<Vec<_>>();
+ let fields = fields
+ .named
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ 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)
+ }
+ }
+ };
+
+ 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
+ }
+ }
+ });
+
+ 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];
+ 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"
+ ),
+ }
+ });
+
+ let raw_deprecated = args::raw_deprecated();
+ 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 = <Self as clap::FromArgMatches>::from_arg_matches_mut(__clap_arg_matches)?;
+ }
+ }
+ }
+ ::std::result::Result::Ok(())
+ }
+ }
+}
diff --git a/src/derives/value_enum.rs b/src/derives/value_enum.rs
new file mode 100644
index 0000000..7a9d870
--- /dev/null
+++ b/src/derives/value_enum.rs
@@ -0,0 +1,127 @@
+// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
+// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
+// Ana Hobden (@hoverbear) <operator@hoverbear.org>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use proc_macro2::TokenStream;
+use proc_macro_error::{abort, abort_call_site};
+use quote::quote;
+use quote::quote_spanned;
+use syn::{spanned::Spanned, Data, DeriveInput, Fields, Ident, Variant};
+
+use crate::dummies;
+use crate::item::{Item, Kind, Name};
+
+pub fn derive_value_enum(input: &DeriveInput) -> TokenStream {
+ let ident = &input.ident;
+
+ dummies::value_enum(ident);
+
+ match input.data {
+ Data::Enum(ref e) => {
+ let name = Name::Derived(ident.clone());
+ let item = Item::from_value_enum(input, name);
+ let variants = e
+ .variants
+ .iter()
+ .map(|variant| {
+ let item =
+ Item::from_value_enum_variant(variant, item.casing(), item.env_casing());
+ (variant, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_enum(&item, ident, &variants)
+ }
+ _ => abort_call_site!("`#[derive(ValueEnum)]` only supports enums"),
+ }
+}
+
+pub fn gen_for_enum(item: &Item, item_name: &Ident, variants: &[(&Variant, Item)]) -> TokenStream {
+ if !matches!(&*item.kind(), Kind::Value) {
+ abort! { item.kind().span(),
+ "`{}` cannot be used with `value`",
+ item.kind().name(),
+ }
+ }
+
+ let lits = lits(variants);
+ let value_variants = gen_value_variants(&lits);
+ let to_possible_value = gen_to_possible_value(item, &lits);
+
+ quote! {
+ #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+ #[allow(
+ clippy::style,
+ clippy::complexity,
+ clippy::pedantic,
+ clippy::restriction,
+ clippy::perf,
+ clippy::deprecated,
+ clippy::nursery,
+ clippy::cargo,
+ clippy::suspicious_else_formatting,
+ clippy::almost_swapped,
+ )]
+ impl clap::ValueEnum for #item_name {
+ #value_variants
+ #to_possible_value
+ }
+ }
+}
+
+fn lits(variants: &[(&Variant, Item)]) -> Vec<(TokenStream, Ident)> {
+ variants
+ .iter()
+ .filter_map(|(variant, item)| {
+ if let Kind::Skip(_, _) = &*item.kind() {
+ None
+ } else {
+ if !matches!(variant.fields, Fields::Unit) {
+ abort!(variant.span(), "`#[derive(ValueEnum)]` only supports unit variants. Non-unit variants must be skipped");
+ }
+ let fields = item.field_methods();
+ let deprecations = item.deprecations();
+ let name = item.cased_name();
+ Some((
+ quote_spanned! { variant.span()=> {
+ #deprecations
+ clap::builder::PossibleValue::new(#name)
+ #fields
+ }},
+ variant.ident.clone(),
+ ))
+ }
+ })
+ .collect::<Vec<_>>()
+}
+
+fn gen_value_variants(lits: &[(TokenStream, Ident)]) -> TokenStream {
+ let lit = lits.iter().map(|l| &l.1).collect::<Vec<_>>();
+
+ quote! {
+ fn value_variants<'a>() -> &'a [Self]{
+ &[#(Self::#lit),*]
+ }
+ }
+}
+
+fn gen_to_possible_value(item: &Item, lits: &[(TokenStream, Ident)]) -> TokenStream {
+ let (lit, variant): (Vec<TokenStream>, Vec<Ident>) = lits.iter().cloned().unzip();
+
+ let deprecations = item.deprecations();
+
+ quote! {
+ fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::builder::PossibleValue> {
+ #deprecations
+ match self {
+ #(Self::#variant => Some(#lit),)*
+ _ => None
+ }
+ }
+ }
+}