diff --git a/clap_derive/src/attrs.rs b/clap_derive/src/attrs.rs index edfa262752a6..f8a79e528230 100644 --- a/clap_derive/src/attrs.rs +++ b/clap_derive/src/attrs.rs @@ -107,13 +107,6 @@ pub struct Attrs { kind: Sp, } -/// Output for the gen_xxx() methods were we need more than a simple stream of tokens. -/// -/// The output of a generation method is not only the stream of new tokens but also the attribute -/// information of the current element. These attribute information may contain valuable information -/// for any kind of child arguments. -pub type GenOutput = (TokenStream, Attrs); - impl Method { pub fn new(name: Ident, args: TokenStream) -> Self { Method { name, args } diff --git a/clap_derive/src/derives/args.rs b/clap_derive/src/derives/args.rs new file mode 100644 index 000000000000..4a662b858ddc --- /dev/null +++ b/clap_derive/src/derives/args.rs @@ -0,0 +1,606 @@ +use crate::{ + attrs::{Attrs, Kind, Name, ParserKind, DEFAULT_CASING, DEFAULT_ENV_CASING}, + dummies, + utils::{sub_type, subty_if_name, Sp, Ty}, +}; + +use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro_error::{abort, abort_call_site}; +use quote::{quote, quote_spanned}; +use syn::{ + punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DataStruct, + DeriveInput, Field, Fields, Type, +}; + +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), + .. + }) => gen_for_struct(ident, &fields.named, &input.attrs), + Data::Struct(DataStruct { + fields: Fields::Unit, + .. + }) => gen_for_struct(ident, &Punctuated::::new(), &input.attrs), + _ => abort_call_site!("`#[derive(Args)]` only supports non-tuple structs"), + } +} + +pub fn gen_for_struct( + struct_name: &Ident, + fields: &Punctuated, + attrs: &[Attribute], +) -> TokenStream { + let from_arg_matches = gen_from_arg_matches_for_struct(struct_name, fields, attrs); + + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Derived(struct_name.clone()), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + let app_var = Ident::new("app", Span::call_site()); + let augmentation = gen_augment(fields, &app_var, &attrs, false); + let augmentation_update = gen_augment(fields, &app_var, &attrs, true); + + quote! { + #from_arg_matches + + #[allow(dead_code, unreachable_code, unused_variables)] + #[allow( + clippy::style, + clippy::complexity, + clippy::pedantic, + clippy::restriction, + clippy::perf, + clippy::deprecated, + clippy::nursery, + clippy::cargo + )] + #[deny(clippy::correctness)] + impl clap::Args for #struct_name { + fn augment_args<'b>(#app_var: clap::App<'b>) -> clap::App<'b> { + #augmentation + } + fn augment_args_for_update<'b>(#app_var: clap::App<'b>) -> clap::App<'b> { + #augmentation_update + } + } + } +} + +pub fn gen_from_arg_matches_for_struct( + struct_name: &Ident, + fields: &Punctuated, + attrs: &[Attribute], +) -> TokenStream { + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Derived(struct_name.clone()), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + + let constructor = gen_constructor(fields, &attrs); + let updater = gen_updater(fields, &attrs, true); + + quote! { + #[allow(dead_code, unreachable_code, unused_variables)] + #[allow( + clippy::style, + clippy::complexity, + clippy::pedantic, + clippy::restriction, + clippy::perf, + clippy::deprecated, + clippy::nursery, + clippy::cargo + )] + #[deny(clippy::correctness)] + impl clap::FromArgMatches for #struct_name { + fn from_arg_matches(arg_matches: &clap::ArgMatches) -> Option { + let v = #struct_name #constructor; + Some(v) + } + + fn update_from_arg_matches(&mut self, arg_matches: &clap::ArgMatches) { + #updater + } + } + } +} + +/// Generate a block of code to add arguments/subcommands corresponding to +/// the `fields` to an app. +pub fn gen_augment( + fields: &Punctuated, + app_var: &Ident, + parent_attribute: &Attrs, + override_required: bool, +) -> TokenStream { + let mut subcmds = fields.iter().filter_map(|field| { + let attrs = Attrs::from_field( + field, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + let kind = attrs.kind(); + if let Kind::Subcommand(ty) = &*kind { + let subcmd_type = match (**ty, sub_type(&field.ty)) { + (Ty::Option, Some(sub_type)) => sub_type, + _ => &field.ty, + }; + let required = if **ty == Ty::Option { + quote!() + } else { + quote_spanned! { kind.span()=> + let #app_var = #app_var.setting( + clap::AppSettings::SubcommandRequiredElseHelp + ); + } + }; + + let span = field.span(); + let ts = if override_required { + quote! { + let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands_for_update( #app_var ); + } + } else{ + quote! { + let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands( #app_var ); + #required + } + }; + Some((span, ts)) + } else { + None + } + }); + let subcmd = subcmds.next().map(|(_, ts)| ts); + if let Some((span, _)) = subcmds.next() { + abort!( + span, + "multiple subcommand sets are not allowed, that's the second" + ); + } + + let args = fields.iter().filter_map(|field| { + let attrs = Attrs::from_field( + field, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + let kind = attrs.kind(); + match &*kind { + Kind::Subcommand(_) + | Kind::Skip(_) + | Kind::FromGlobal(_) + | Kind::ExternalSubcommand => None, + Kind::Flatten => { + let ty = &field.ty; + Some(quote_spanned! { kind.span()=> + let #app_var = <#ty as clap::Args>::augment_args(#app_var); + }) + } + Kind::Arg(ty) => { + let convert_type = match **ty { + Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), + Ty::OptionOption | Ty::OptionVec => { + sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty) + } + _ => &field.ty, + }; + + let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; + let flag = *attrs.parser().kind == ParserKind::FromFlag; + + let parser = attrs.parser(); + let func = &parser.func; + + let validator = match *parser.kind { + _ if attrs.is_enum() => quote!(), + ParserKind::TryFromStr => quote_spanned! { func.span()=> + .validator(|s| { + #func(s) + .map(|_: #convert_type| ()) + }) + }, + ParserKind::TryFromOsStr => quote_spanned! { func.span()=> + .validator_os(|s| #func(s).map(|_: #convert_type| ())) + }, + _ => quote!(), + }; + + let modifier = match **ty { + Ty::Bool => quote!(), + + Ty::Option => { + let mut possible_values = quote!(); + + if attrs.is_enum() { + if let Some(subty) = subty_if_name(&field.ty, "Option") { + possible_values = gen_arg_enum_possible_values(subty); + } + }; + + quote_spanned! { ty.span()=> + .takes_value(true) + #possible_values + #validator + } + } + + Ty::OptionOption => quote_spanned! { ty.span()=> + .takes_value(true) + .multiple_values(false) + .min_values(0) + .max_values(1) + #validator + }, + + Ty::OptionVec => quote_spanned! { ty.span()=> + .takes_value(true) + .multiple_values(true) + .min_values(0) + #validator + }, + + Ty::Vec => { + let mut possible_values = quote!(); + + if attrs.is_enum() { + if let Some(subty) = subty_if_name(&field.ty, "Vec") { + possible_values = gen_arg_enum_possible_values(subty); + } + }; + + quote_spanned! { ty.span()=> + .takes_value(true) + .multiple_values(true) + #possible_values + #validator + } + } + + Ty::Other if occurrences => quote_spanned! { ty.span()=> + .multiple_occurrences(true) + }, + + Ty::Other if flag => quote_spanned! { ty.span()=> + .takes_value(false) + .multiple_values(false) + }, + + Ty::Other => { + let required = !attrs.has_method("default_value") && !override_required; + let mut possible_values = quote!(); + + if attrs.is_enum() { + possible_values = gen_arg_enum_possible_values(&field.ty); + }; + + quote_spanned! { ty.span()=> + .takes_value(true) + .required(#required) + #possible_values + #validator + } + } + }; + + let name = attrs.cased_name(); + let methods = attrs.field_methods(); + + Some(quote_spanned! { field.span()=> + let #app_var = #app_var.arg( + clap::Arg::new(#name) + #modifier + #methods + ); + }) + } + } + }); + + let app_methods = parent_attribute.top_level_methods(); + let version = parent_attribute.version(); + quote! {{ + let #app_var = #app_var#app_methods; + #( #args )* + #subcmd + #app_var#version + }} +} + +fn gen_arg_enum_possible_values(ty: &Type) -> TokenStream { + quote_spanned! { ty.span()=> + .possible_values(&<#ty as clap::ArgEnum>::VARIANTS) + } +} + +pub fn gen_constructor(fields: &Punctuated, parent_attribute: &Attrs) -> TokenStream { + let fields = fields.iter().map(|field| { + let attrs = Attrs::from_field( + field, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + let field_name = field.ident.as_ref().unwrap(); + let kind = attrs.kind(); + let arg_matches = quote! { arg_matches }; + match &*kind { + Kind::ExternalSubcommand => { + abort! { kind.span(), + "`external_subcommand` can be used only on enum variants" + } + } + Kind::Subcommand(ty) => { + let subcmd_type = match (**ty, sub_type(&field.ty)) { + (Ty::Option, Some(sub_type)) => sub_type, + _ => &field.ty, + }; + let unwrapper = match **ty { + Ty::Option => quote!(), + _ => quote_spanned!( ty.span()=> .unwrap() ), + }; + quote_spanned! { kind.span()=> + #field_name: { + <#subcmd_type as clap::FromArgMatches>::from_arg_matches(#arg_matches) + #unwrapper + } + } + } + + Kind::Flatten => quote_spanned! { kind.span()=> + #field_name: clap::FromArgMatches::from_arg_matches(#arg_matches).unwrap() + }, + + 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(&attrs, ty, field_name, field, None) + } + } + }); + + quote! {{ + #( #fields ),* + }} +} + +pub fn gen_updater( + fields: &Punctuated, + parent_attribute: &Attrs, + use_self: bool, +) -> TokenStream { + let fields = fields.iter().map(|field| { + let attrs = Attrs::from_field( + field, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + let field_name = field.ident.as_ref().unwrap(); + let kind = attrs.kind(); + + let access = if use_self { + quote! { + #[allow(non_snake_case)] + let #field_name = &mut self.#field_name; + } + } else { + quote!() + }; + let arg_matches = quote! { arg_matches }; + + match &*kind { + Kind::ExternalSubcommand => { + abort! { kind.span(), + "`external_subcommand` can be used only on enum variants" + } + } + 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(#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 = <#subcmd_type as clap::FromArgMatches>::from_arg_matches( + #arg_matches + ) + } + }, + _ => quote_spanned! { kind.span()=> + #updater + }, + }; + + quote_spanned! { kind.span()=> + { + #access + #updater + } + } + } + + Kind::Flatten => quote_spanned! { kind.span()=> { + #access + clap::FromArgMatches::update_from_arg_matches(#field_name, #arg_matches); + } + }, + + Kind::Skip(_) => quote!(), + + Kind::Arg(ty) | Kind::FromGlobal(ty) => gen_parsers(&attrs, ty, field_name, field, Some(&access)), + } + }); + + quote! { + #( #fields )* + } +} + +fn gen_parsers( + attrs: &Attrs, + ty: &Sp, + field_name: &Ident, + field: &Field, + update: Option<&TokenStream>, +) -> TokenStream { + use self::ParserKind::*; + + let parser = attrs.parser(); + let func = &parser.func; + let span = parser.kind.span(); + let (value_of, values_of, mut parse) = match *parser.kind { + FromStr => ( + quote_spanned!(span=> value_of), + quote_spanned!(span=> values_of), + func.clone(), + ), + TryFromStr => ( + quote_spanned!(span=> value_of), + quote_spanned!(span=> values_of), + quote_spanned!(func.span()=> |s| #func(s).unwrap()), + ), + FromOsStr => ( + quote_spanned!(span=> value_of_os), + quote_spanned!(span=> values_of_os), + func.clone(), + ), + TryFromOsStr => ( + quote_spanned!(span=> value_of_os), + quote_spanned!(span=> values_of_os), + quote_spanned!(func.span()=> |s| #func(s).unwrap()), + ), + FromOccurrences => ( + quote_spanned!(span=> occurrences_of), + quote!(), + func.clone(), + ), + FromFlag => (quote!(), quote!(), func.clone()), + }; + + let flag = *attrs.parser().kind == ParserKind::FromFlag; + let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; + let name = attrs.cased_name(); + // Use `quote!` to 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 = quote! { arg_matches }; + + let field_value = match **ty { + Ty::Bool => { + if update.is_some() { + quote_spanned! { ty.span()=> + *#field_name || #arg_matches.is_present(#name) + } + } else { + quote_spanned! { ty.span()=> + #arg_matches.is_present(#name) + } + } + } + + Ty::Option => { + if attrs.is_enum() { + if let Some(subty) = subty_if_name(&field.ty, "Option") { + parse = gen_arg_enum_parse(subty, attrs); + } + } + + quote_spanned! { ty.span()=> + #arg_matches.#value_of(#name) + .map(#parse) + } + } + + Ty::OptionOption => quote_spanned! { ty.span()=> + if #arg_matches.is_present(#name) { + Some(#arg_matches.#value_of(#name).map(#parse)) + } else { + None + } + }, + + Ty::OptionVec => quote_spanned! { ty.span()=> + if #arg_matches.is_present(#name) { + Some(#arg_matches.#values_of(#name) + .map(|v| v.map(#parse).collect()) + .unwrap_or_else(Vec::new)) + } else { + None + } + }, + + Ty::Vec => { + if attrs.is_enum() { + if let Some(subty) = subty_if_name(&field.ty, "Vec") { + parse = gen_arg_enum_parse(subty, attrs); + } + } + + quote_spanned! { ty.span()=> + #arg_matches.#values_of(#name) + .map(|v| v.map(#parse).collect()) + .unwrap_or_else(Vec::new) + } + } + + Ty::Other if occurrences => quote_spanned! { ty.span()=> + #parse(#arg_matches.#value_of(#name)) + }, + + Ty::Other if flag => quote_spanned! { ty.span()=> + #parse(#arg_matches.is_present(#name)) + }, + + Ty::Other => { + if attrs.is_enum() { + parse = gen_arg_enum_parse(&field.ty, attrs); + } + + quote_spanned! { ty.span()=> + #arg_matches.#value_of(#name) + .map(#parse) + .unwrap() + } + } + }; + + if let Some(access) = update { + quote_spanned! { field.span()=> + if #arg_matches.is_present(#name) { + #access + *#field_name = #field_value + } + } + } else { + quote_spanned!(field.span()=> #field_name: #field_value ) + } +} + +fn gen_arg_enum_parse(ty: &Type, attrs: &Attrs) -> TokenStream { + let ci = attrs.case_insensitive(); + + quote_spanned! { ty.span()=> + |s| <#ty as clap::ArgEnum>::from_str(s, #ci).unwrap() + } +} diff --git a/clap_derive/src/derives/clap.rs b/clap_derive/src/derives/clap.rs index 1ed64c9d581c..39d4e769aa8a 100644 --- a/clap_derive/src/derives/clap.rs +++ b/clap_derive/src/derives/clap.rs @@ -13,7 +13,7 @@ // MIT/Apache 2.0 license. use crate::{ - derives::{from_arg_matches, into_app, subcommand}, + derives::{args, into_app, subcommand}, dummies, }; @@ -56,27 +56,25 @@ fn gen_for_struct( fields: &Punctuated, attrs: &[Attribute], ) -> TokenStream { - let (into_app, attrs) = into_app::gen_for_struct(name, fields, attrs); - let from_arg_matches = from_arg_matches::gen_for_struct(name, fields, &attrs); + let into_app = into_app::gen_for_struct(name, attrs); + let args = args::gen_for_struct(name, fields, attrs); quote! { impl clap::Clap for #name {} #into_app - #from_arg_matches + #args } } fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream { let into_app = into_app::gen_for_enum(name, attrs); - let from_arg_matches = from_arg_matches::gen_for_enum(name); let subcommand = subcommand::gen_for_enum(name, attrs, e); quote! { impl clap::Clap for #name {} #into_app - #from_arg_matches #subcommand } } diff --git a/clap_derive/src/derives/from_arg_matches.rs b/clap_derive/src/derives/from_arg_matches.rs index 49eba21aae4b..8b137891791f 100644 --- a/clap_derive/src/derives/from_arg_matches.rs +++ b/clap_derive/src/derives/from_arg_matches.rs @@ -1,359 +1 @@ -// Copyright 2018 Guillaume Pinot (@TeXitoi) , -// Kevin Knapp (@kbknapp) , and -// Andrew 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::TokenStream; -use proc_macro_error::abort; -use quote::{quote, quote_spanned}; -use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Field, Ident, Type}; -use crate::{ - attrs::{Attrs, Kind, ParserKind}, - utils::{sub_type, subty_if_name, Sp, Ty}, -}; - -pub fn gen_for_struct( - struct_name: &Ident, - fields: &Punctuated, - parent_attribute: &Attrs, -) -> TokenStream { - let constructor = gen_constructor(fields, parent_attribute); - let updater = gen_updater(fields, parent_attribute, true); - - quote! { - #[allow(dead_code, unreachable_code, unused_variables)] - #[allow( - clippy::style, - clippy::complexity, - clippy::pedantic, - clippy::restriction, - clippy::perf, - clippy::deprecated, - clippy::nursery, - clippy::cargo - )] - #[deny(clippy::correctness)] - impl clap::FromArgMatches for #struct_name { - fn from_arg_matches(arg_matches: &clap::ArgMatches) -> Self { - #struct_name #constructor - } - - fn update_from_arg_matches(&mut self, arg_matches: &clap::ArgMatches) { - #updater - } - } - } -} - -pub fn gen_for_enum(name: &Ident) -> TokenStream { - quote! { - #[allow(dead_code, unreachable_code, unused_variables)] - #[allow( - clippy::style, - clippy::complexity, - clippy::pedantic, - clippy::restriction, - clippy::perf, - clippy::deprecated, - clippy::nursery, - clippy::cargo - )] - #[deny(clippy::correctness)] - impl clap::FromArgMatches for #name { - fn from_arg_matches(arg_matches: &clap::ArgMatches) -> Self { - <#name as clap::Subcommand>::from_subcommand(arg_matches.subcommand()).unwrap() - } - fn update_from_arg_matches(&mut self, arg_matches: &clap::ArgMatches) { - <#name as clap::Subcommand>::update_from_subcommand(self, arg_matches.subcommand()); - } - } - } -} - -fn gen_arg_enum_parse(ty: &Type, attrs: &Attrs) -> TokenStream { - let ci = attrs.case_insensitive(); - - quote_spanned! { ty.span()=> - |s| <#ty as clap::ArgEnum>::from_str(s, #ci).unwrap() - } -} - -fn gen_parsers( - attrs: &Attrs, - ty: &Sp, - field_name: &Ident, - field: &Field, - update: Option<&TokenStream>, -) -> TokenStream { - use self::ParserKind::*; - - let parser = attrs.parser(); - let func = &parser.func; - let span = parser.kind.span(); - let (value_of, values_of, mut parse) = match *parser.kind { - FromStr => ( - quote_spanned!(span=> value_of), - quote_spanned!(span=> values_of), - func.clone(), - ), - TryFromStr => ( - quote_spanned!(span=> value_of), - quote_spanned!(span=> values_of), - quote_spanned!(func.span()=> |s| #func(s).unwrap()), - ), - FromOsStr => ( - quote_spanned!(span=> value_of_os), - quote_spanned!(span=> values_of_os), - func.clone(), - ), - TryFromOsStr => ( - quote_spanned!(span=> value_of_os), - quote_spanned!(span=> values_of_os), - quote_spanned!(func.span()=> |s| #func(s).unwrap()), - ), - FromOccurrences => ( - quote_spanned!(span=> occurrences_of), - quote!(), - func.clone(), - ), - FromFlag => (quote!(), quote!(), func.clone()), - }; - - let flag = *attrs.parser().kind == ParserKind::FromFlag; - let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; - let name = attrs.cased_name(); - // Use `quote!` to 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 = quote! { arg_matches }; - - let field_value = match **ty { - Ty::Bool => { - if update.is_some() { - quote_spanned! { ty.span()=> - *#field_name || #arg_matches.is_present(#name) - } - } else { - quote_spanned! { ty.span()=> - #arg_matches.is_present(#name) - } - } - } - - Ty::Option => { - if attrs.is_enum() { - if let Some(subty) = subty_if_name(&field.ty, "Option") { - parse = gen_arg_enum_parse(subty, attrs); - } - } - - quote_spanned! { ty.span()=> - #arg_matches.#value_of(#name) - .map(#parse) - } - } - - Ty::OptionOption => quote_spanned! { ty.span()=> - if #arg_matches.is_present(#name) { - Some(#arg_matches.#value_of(#name).map(#parse)) - } else { - None - } - }, - - Ty::OptionVec => quote_spanned! { ty.span()=> - if #arg_matches.is_present(#name) { - Some(#arg_matches.#values_of(#name) - .map(|v| v.map(#parse).collect()) - .unwrap_or_else(Vec::new)) - } else { - None - } - }, - - Ty::Vec => { - if attrs.is_enum() { - if let Some(subty) = subty_if_name(&field.ty, "Vec") { - parse = gen_arg_enum_parse(subty, attrs); - } - } - - quote_spanned! { ty.span()=> - #arg_matches.#values_of(#name) - .map(|v| v.map(#parse).collect()) - .unwrap_or_else(Vec::new) - } - } - - Ty::Other if occurrences => quote_spanned! { ty.span()=> - #parse(#arg_matches.#value_of(#name)) - }, - - Ty::Other if flag => quote_spanned! { ty.span()=> - #parse(#arg_matches.is_present(#name)) - }, - - Ty::Other => { - if attrs.is_enum() { - parse = gen_arg_enum_parse(&field.ty, attrs); - } - - quote_spanned! { ty.span()=> - #arg_matches.#value_of(#name) - .map(#parse) - .unwrap() - } - } - }; - - if let Some(access) = update { - quote_spanned! { field.span()=> - if #arg_matches.is_present(#name) { - #access - *#field_name = #field_value - } - } - } else { - quote_spanned!(field.span()=> #field_name: #field_value ) - } -} - -pub fn gen_constructor(fields: &Punctuated, parent_attribute: &Attrs) -> TokenStream { - let fields = fields.iter().map(|field| { - let attrs = Attrs::from_field( - field, - parent_attribute.casing(), - parent_attribute.env_casing(), - ); - let field_name = field.ident.as_ref().unwrap(); - let kind = attrs.kind(); - let arg_matches = quote! { arg_matches }; - match &*kind { - Kind::ExternalSubcommand => { - abort! { kind.span(), - "`external_subcommand` can be used only on enum variants" - } - } - Kind::Subcommand(ty) => { - let subcmd_type = match (**ty, sub_type(&field.ty)) { - (Ty::Option, Some(sub_type)) => sub_type, - _ => &field.ty, - }; - let unwrapper = match **ty { - Ty::Option => quote!(), - _ => quote_spanned!( ty.span()=> .unwrap() ), - }; - quote_spanned! { kind.span()=> - #field_name: { - <#subcmd_type as clap::Subcommand>::from_subcommand(#arg_matches.subcommand()) - #unwrapper - } - } - } - - Kind::Flatten => quote_spanned! { kind.span()=> - #field_name: clap::FromArgMatches::from_arg_matches(#arg_matches) - }, - - 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(&attrs, ty, field_name, field, None), - } - }); - - quote! {{ - #( #fields ),* - }} -} - -pub fn gen_updater( - fields: &Punctuated, - parent_attribute: &Attrs, - use_self: bool, -) -> TokenStream { - let fields = fields.iter().map(|field| { - let attrs = Attrs::from_field( - field, - parent_attribute.casing(), - parent_attribute.env_casing(), - ); - let field_name = field.ident.as_ref().unwrap(); - let kind = attrs.kind(); - - let access = if use_self { - quote! { - #[allow(non_snake_case)] - let #field_name = &mut self.#field_name; - } - } else { - quote!() - }; - let arg_matches = quote! { arg_matches }; - - match &*kind { - Kind::ExternalSubcommand => { - abort! { kind.span(), - "`external_subcommand` can be used only on enum variants" - } - } - 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::Subcommand>::update_from_subcommand(#field_name, #arg_matches.subcommand()); - }; - - let updater = match **ty { - Ty::Option => quote_spanned! { kind.span()=> - if let Some(#field_name) = #field_name.as_mut() { - #updater - } else { - *#field_name = <#subcmd_type as clap::Subcommand>::from_subcommand( - #arg_matches.subcommand() - ) - } - }, - _ => quote_spanned! { kind.span()=> - #updater - }, - }; - - quote_spanned! { kind.span()=> - { - #access - #updater - } - } - } - - Kind::Flatten => quote_spanned! { kind.span()=> { - #access - clap::FromArgMatches::update_from_arg_matches(#field_name, #arg_matches); - } - }, - - Kind::Skip(_) => quote!(), - - Kind::Arg(ty) | Kind::FromGlobal(ty) => gen_parsers(&attrs, ty, field_name, field, Some(&access)), - } - }); - - quote! { - #( #fields )* - } -} diff --git a/clap_derive/src/derives/into_app.rs b/clap_derive/src/derives/into_app.rs index 13736975065a..1a7b144b6125 100644 --- a/clap_derive/src/derives/into_app.rs +++ b/clap_derive/src/derives/into_app.rs @@ -15,17 +15,14 @@ use std::env; use proc_macro2::{Span, TokenStream}; -use proc_macro_error::{abort, abort_call_site}; -use quote::{quote, quote_spanned}; -use syn::{ - punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DataStruct, - DeriveInput, Field, Fields, Ident, Type, -}; +use proc_macro_error::abort_call_site; +use quote::quote; +use syn::{Attribute, Data, DataStruct, DeriveInput, Fields, Ident}; use crate::{ - attrs::{Attrs, GenOutput, Kind, Name, ParserKind, DEFAULT_CASING, DEFAULT_ENV_CASING}, + attrs::{Attrs, Name, DEFAULT_CASING, DEFAULT_ENV_CASING}, dummies, - utils::{sub_type, subty_if_name, Sp, Ty}, + utils::Sp, }; pub fn derive_into_app(input: &DeriveInput) -> TokenStream { @@ -35,25 +32,29 @@ pub fn derive_into_app(input: &DeriveInput) -> TokenStream { match input.data { Data::Struct(DataStruct { - fields: Fields::Named(ref fields), + fields: Fields::Named(_), .. - }) => gen_for_struct(ident, &fields.named, &input.attrs).0, + }) => gen_for_struct(ident, &input.attrs), Data::Struct(DataStruct { fields: Fields::Unit, .. - }) => gen_for_struct(ident, &Punctuated::::new(), &input.attrs).0, + }) => gen_for_struct(ident, &input.attrs), Data::Enum(_) => gen_for_enum(ident, &input.attrs), _ => abort_call_site!("`#[derive(IntoApp)]` only supports non-tuple structs and enums"), } } -pub fn gen_for_struct( - struct_name: &Ident, - fields: &Punctuated, - attrs: &[Attribute], -) -> GenOutput { - let (into_app, attrs) = gen_into_app_fn(attrs); - let augment_clap = gen_augment_clap_fn(fields, &attrs); +pub fn gen_for_struct(struct_name: &Ident, attrs: &[Attribute]) -> TokenStream { + let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default(); + + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Assigned(quote!(#app_name)), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + let name = attrs.cased_name(); let tokens = quote! { #[allow(dead_code, unreachable_code, unused_variables)] @@ -69,12 +70,19 @@ pub fn gen_for_struct( )] #[deny(clippy::correctness)] impl clap::IntoApp for #struct_name { - #into_app - #augment_clap + fn into_app<'b>() -> clap::App<'b> { + let app = clap::App::new(#name); + <#struct_name as clap::Args>::augment_args(app) + } + + fn into_app_for_update<'b>() -> clap::App<'b> { + let app = clap::App::new(#name); + <#struct_name as clap::Args>::augment_args_for_update(app) + } } }; - (tokens, attrs) + tokens } pub fn gen_for_enum(enum_name: &Ident, attrs: &[Attribute]) -> TokenStream { @@ -106,267 +114,13 @@ pub fn gen_for_enum(enum_name: &Ident, attrs: &[Attribute]) -> TokenStream { fn into_app<'b>() -> clap::App<'b> { let app = clap::App::new(#name) .setting(clap::AppSettings::SubcommandRequiredElseHelp); - <#enum_name as clap::IntoApp>::augment_clap(app) - } - - fn augment_clap<'b>(app: clap::App<'b>) -> clap::App<'b> { <#enum_name as clap::Subcommand>::augment_subcommands(app) } fn into_app_for_update<'b>() -> clap::App<'b> { let app = clap::App::new(#name); - <#enum_name as clap::IntoApp>::augment_clap_for_update(app) - } - - fn augment_clap_for_update<'b>(app: clap::App<'b>) -> clap::App<'b> { <#enum_name as clap::Subcommand>::augment_subcommands_for_update(app) } } } } - -fn gen_into_app_fn(attrs: &[Attribute]) -> GenOutput { - let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default(); - - let attrs = Attrs::from_struct( - Span::call_site(), - attrs, - Name::Assigned(quote!(#app_name)), - Sp::call_site(DEFAULT_CASING), - Sp::call_site(DEFAULT_ENV_CASING), - ); - let name = attrs.cased_name(); - - let tokens = quote! { - fn into_app<'b>() -> clap::App<'b> { - Self::augment_clap(clap::App::new(#name)) - } - fn into_app_for_update<'b>() -> clap::App<'b> { - Self::augment_clap_for_update(clap::App::new(#name)) - } - }; - - (tokens, attrs) -} - -fn gen_augment_clap_fn(fields: &Punctuated, parent_attribute: &Attrs) -> TokenStream { - let app_var = Ident::new("app", Span::call_site()); - let augmentation = gen_app_augmentation(fields, &app_var, parent_attribute, false); - let augmentation_update = gen_app_augmentation(fields, &app_var, parent_attribute, true); - quote! { - fn augment_clap<'b>(#app_var: clap::App<'b>) -> clap::App<'b> { - #augmentation - } - fn augment_clap_for_update<'b>(#app_var: clap::App<'b>) -> clap::App<'b> { - #augmentation_update - } - } -} - -fn gen_arg_enum_possible_values(ty: &Type) -> TokenStream { - quote_spanned! { ty.span()=> - .possible_values(&<#ty as clap::ArgEnum>::VARIANTS) - } -} - -/// Generate a block of code to add arguments/subcommands corresponding to -/// the `fields` to an app. -pub fn gen_app_augmentation( - fields: &Punctuated, - app_var: &Ident, - parent_attribute: &Attrs, - override_required: bool, -) -> TokenStream { - let mut subcmds = fields.iter().filter_map(|field| { - let attrs = Attrs::from_field( - field, - parent_attribute.casing(), - parent_attribute.env_casing(), - ); - let kind = attrs.kind(); - if let Kind::Subcommand(ty) = &*kind { - let subcmd_type = match (**ty, sub_type(&field.ty)) { - (Ty::Option, Some(sub_type)) => sub_type, - _ => &field.ty, - }; - let required = if **ty == Ty::Option { - quote!() - } else { - quote_spanned! { kind.span()=> - let #app_var = #app_var.setting( - clap::AppSettings::SubcommandRequiredElseHelp - ); - } - }; - - let span = field.span(); - let ts = if override_required { - quote! { - let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands_for_update( #app_var ); - } - } else{ - quote! { - let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands( #app_var ); - #required - } - }; - Some((span, ts)) - } else { - None - } - }); - let subcmd = subcmds.next().map(|(_, ts)| ts); - if let Some((span, _)) = subcmds.next() { - abort!( - span, - "multiple subcommand sets are not allowed, that's the second" - ); - } - - let args = fields.iter().filter_map(|field| { - let attrs = Attrs::from_field( - field, - parent_attribute.casing(), - parent_attribute.env_casing(), - ); - let kind = attrs.kind(); - match &*kind { - Kind::Subcommand(_) - | Kind::Skip(_) - | Kind::FromGlobal(_) - | Kind::ExternalSubcommand => None, - Kind::Flatten => { - let ty = &field.ty; - Some(quote_spanned! { kind.span()=> - let #app_var = <#ty as clap::IntoApp>::augment_clap(#app_var); - }) - } - Kind::Arg(ty) => { - let convert_type = match **ty { - Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), - Ty::OptionOption | Ty::OptionVec => { - sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty) - } - _ => &field.ty, - }; - - let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; - let flag = *attrs.parser().kind == ParserKind::FromFlag; - - let parser = attrs.parser(); - let func = &parser.func; - - let validator = match *parser.kind { - _ if attrs.is_enum() => quote!(), - ParserKind::TryFromStr => quote_spanned! { func.span()=> - .validator(|s| { - #func(s) - .map(|_: #convert_type| ()) - }) - }, - ParserKind::TryFromOsStr => quote_spanned! { func.span()=> - .validator_os(|s| #func(s).map(|_: #convert_type| ())) - }, - _ => quote!(), - }; - - let modifier = match **ty { - Ty::Bool => quote!(), - - Ty::Option => { - let mut possible_values = quote!(); - - if attrs.is_enum() { - if let Some(subty) = subty_if_name(&field.ty, "Option") { - possible_values = gen_arg_enum_possible_values(subty); - } - }; - - quote_spanned! { ty.span()=> - .takes_value(true) - #possible_values - #validator - } - } - - Ty::OptionOption => quote_spanned! { ty.span()=> - .takes_value(true) - .multiple_values(false) - .min_values(0) - .max_values(1) - #validator - }, - - Ty::OptionVec => quote_spanned! { ty.span()=> - .takes_value(true) - .multiple_values(true) - .min_values(0) - #validator - }, - - Ty::Vec => { - let mut possible_values = quote!(); - - if attrs.is_enum() { - if let Some(subty) = subty_if_name(&field.ty, "Vec") { - possible_values = gen_arg_enum_possible_values(subty); - } - }; - - quote_spanned! { ty.span()=> - .takes_value(true) - .multiple_values(true) - #possible_values - #validator - } - } - - Ty::Other if occurrences => quote_spanned! { ty.span()=> - .multiple_occurrences(true) - }, - - Ty::Other if flag => quote_spanned! { ty.span()=> - .takes_value(false) - .multiple_values(false) - }, - - Ty::Other => { - let required = !attrs.has_method("default_value") && !override_required; - let mut possible_values = quote!(); - - if attrs.is_enum() { - possible_values = gen_arg_enum_possible_values(&field.ty); - }; - - quote_spanned! { ty.span()=> - .takes_value(true) - .required(#required) - #possible_values - #validator - } - } - }; - - let name = attrs.cased_name(); - let methods = attrs.field_methods(); - - Some(quote_spanned! { field.span()=> - let #app_var = #app_var.arg( - clap::Arg::new(#name) - #modifier - #methods - ); - }) - } - } - }); - - let app_methods = parent_attribute.top_level_methods(); - let version = parent_attribute.version(); - quote! {{ - let #app_var = #app_var#app_methods; - #( #args )* - #subcmd - #app_var#version - }} -} diff --git a/clap_derive/src/derives/mod.rs b/clap_derive/src/derives/mod.rs index ea94eb7fbce4..f90b53d35682 100644 --- a/clap_derive/src/derives/mod.rs +++ b/clap_derive/src/derives/mod.rs @@ -12,13 +12,13 @@ // commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the // MIT/Apache 2.0 license. mod arg_enum; +mod args; mod clap; -mod from_arg_matches; mod into_app; mod subcommand; pub use self::clap::derive_clap; pub use arg_enum::derive_arg_enum; -// pub use from_arg_matches::derive_from_arg_matches; +pub use args::derive_args; pub use into_app::derive_into_app; pub use subcommand::derive_subcommand; diff --git a/clap_derive/src/derives/subcommand.rs b/clap_derive/src/derives/subcommand.rs index c4f8697fe35e..c3776b0ed5ff 100644 --- a/clap_derive/src/derives/subcommand.rs +++ b/clap_derive/src/derives/subcommand.rs @@ -1,6 +1,6 @@ use crate::{ attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING}, - derives::{from_arg_matches, into_app}, + derives::args, dummies, utils::{is_simple_ty, subty_if_name, Sp}, }; @@ -24,7 +24,46 @@ pub fn derive_subcommand(input: &DeriveInput) -> TokenStream { } } -pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream { +pub fn gen_for_enum(enum_name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream { + let from_arg_matches = gen_from_arg_matches_for_enum(enum_name, attrs, e); + + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Derived(enum_name.clone()), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + let augmentation = gen_augment(&e.variants, &attrs, false); + let augmentation_update = gen_augment(&e.variants, &attrs, true); + + quote! { + #from_arg_matches + + #[allow(dead_code, unreachable_code, unused_variables)] + #[allow( + clippy::style, + clippy::complexity, + clippy::pedantic, + clippy::restriction, + clippy::perf, + clippy::deprecated, + clippy::nursery, + clippy::cargo + )] + #[deny(clippy::correctness)] + impl clap::Subcommand for #enum_name { + fn augment_subcommands <'b>(app: clap::App<'b>) -> clap::App<'b> { + #augmentation + } + fn augment_subcommands_for_update <'b>(app: clap::App<'b>) -> clap::App<'b> { + #augmentation_update + } + } + } +} + +fn gen_from_arg_matches_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream { let attrs = Attrs::from_struct( Span::call_site(), attrs, @@ -33,11 +72,8 @@ pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStr Sp::call_site(DEFAULT_ENV_CASING), ); - let augment_subcommands = gen_augment("augment_subcommands", &e.variants, &attrs, false); - let augment_subcommands_for_update = - gen_augment("augment_subcommands_for_update", &e.variants, &attrs, true); - let from_subcommand = gen_from_subcommand(name, &e.variants, &attrs); - let update_from_subcommand = gen_update_from_subcommand(name, &e.variants, &attrs); + let from_arg_matches = gen_from_arg_matches(name, &e.variants, &attrs); + let update_from_arg_matches = gen_update_from_args(name, &e.variants, &attrs); quote! { #[allow(dead_code, unreachable_code, unused_variables)] @@ -52,17 +88,14 @@ pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStr clippy::cargo )] #[deny(clippy::correctness)] - impl clap::Subcommand for #name { - #augment_subcommands - #from_subcommand - #augment_subcommands_for_update - #update_from_subcommand + impl clap::FromArgMatches for #name { + #from_arg_matches + #update_from_arg_matches } } } fn gen_augment( - fn_name: &str, variants: &Punctuated, parent_attribute: &Attrs, override_required: bool, @@ -104,18 +137,15 @@ fn gen_augment( _ => { let app_var = Ident::new("subcommand", Span::call_site()); let arg_block = match variant.fields { - Named(ref fields) => into_app::gen_app_augmentation( - &fields.named, - &app_var, - &attrs, - override_required, - ), + Named(ref fields) => { + args::gen_augment(&fields.named, &app_var, &attrs, override_required) + } Unit => quote!( #app_var ), Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0]; quote_spanned! { ty.span()=> { - <#ty as clap::IntoApp>::augment_clap(#app_var) + <#ty as clap::Args>::augment_args(#app_var) } } } @@ -141,17 +171,14 @@ fn gen_augment( let app_methods = parent_attribute.top_level_methods(); let version = parent_attribute.version(); - let fn_name = Ident::new(fn_name, Span::call_site()); quote! { - fn #fn_name <'b>(app: clap::App<'b>) -> clap::App<'b> { let app = app #app_methods; #( #subcommands )*; app #version - } } } -fn gen_from_subcommand( +fn gen_from_arg_matches( name: &Ident, variants: &Punctuated, parent_attribute: &Attrs, @@ -236,11 +263,11 @@ fn gen_from_subcommand( let sub_name = attrs.cased_name(); let variant_name = &variant.ident; let constructor_block = match variant.fields { - Named(ref fields) => from_arg_matches::gen_constructor(&fields.named, attrs), + Named(ref fields) => args::gen_constructor(&fields.named, attrs), Unit => quote!(), Unnamed(ref fields) if fields.unnamed.len() == 1 => { let ty = &fields.unnamed[0]; - quote!( ( <#ty as clap::FromArgMatches>::from_arg_matches(arg_matches) ) ) + quote!( ( <#ty as clap::FromArgMatches>::from_arg_matches(arg_matches).unwrap() ) ) } Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident), }; @@ -257,7 +284,7 @@ fn gen_from_subcommand( Unnamed(ref fields) if fields.unnamed.len() == 1 => { let ty = &fields.unnamed[0]; quote! { - if let Some(res) = <#ty as clap::Subcommand>::from_subcommand(other) { + if let Some(res) = <#ty as clap::FromArgMatches>::from_arg_matches(arg_matches) { return Some(#name :: #variant_name (res)); } } @@ -271,39 +298,34 @@ fn gen_from_subcommand( let wildcard = match ext_subcmd { Some((span, var_name, str_ty, values_of)) => quote_spanned! { span=> - None => ::std::option::Option::None, - - Some((external, arg_matches)) => { ::std::option::Option::Some(#name::#var_name( - ::std::iter::once(#str_ty::from(external)) + ::std::iter::once(#str_ty::from(other)) .chain( - arg_matches.#values_of("").into_iter().flatten().map(#str_ty::from) + sub_arg_matches.#values_of("").into_iter().flatten().map(#str_ty::from) ) .collect::<::std::vec::Vec<_>>() )) - } }, - None => quote!(_ => None), + None => quote!(None), }; quote! { - fn from_subcommand(subcommand: Option<(&str, &clap::ArgMatches)>) -> Option { - match subcommand { + fn from_arg_matches(arg_matches: &clap::ArgMatches) -> Option { + match arg_matches.subcommand() { #( #match_arms, )* - other => { + ::std::option::Option::Some((other, sub_arg_matches)) => { #( #child_subcommands )else* - match other { - #wildcard - } + #wildcard } + ::std::option::Option::None => ::std::option::Option::None, } } } } -fn gen_update_from_subcommand( +fn gen_update_from_args( name: &Ident, variants: &Punctuated, parent_attribute: &Attrs, @@ -349,7 +371,7 @@ fn gen_update_from_subcommand( let field_name = field.ident.as_ref().unwrap(); ( quote!( ref mut #field_name ), - from_arg_matches::gen_updater(&fields.named, &attrs, false), + args::gen_updater(&fields.named, &attrs, false), ) }) .unzip(); @@ -362,7 +384,7 @@ fn gen_update_from_subcommand( quote!((ref mut arg)), quote!(clap::FromArgMatches::update_from_arg_matches( arg, - arg_matches + sub_arg_matches )), ) } else { @@ -372,7 +394,10 @@ fn gen_update_from_subcommand( }; quote! { - (#sub_name, #name :: #variant_name #pattern) => { #updater } + (#sub_name, #name :: #variant_name #pattern) => { + let arg_matches = sub_arg_matches; + #updater + } } }); @@ -385,7 +410,7 @@ fn gen_update_from_subcommand( ( quote!((ref mut arg)), quote! { - <#ty as clap::Subcommand>::update_from_subcommand(arg, Some((name, arg_matches))); + <#ty as clap::FromArgMatches>::update_from_arg_matches(arg, sub_arg_matches); }, ) } @@ -400,16 +425,18 @@ fn gen_update_from_subcommand( }); quote! { - fn update_from_subcommand<'b>( + fn update_from_arg_matches<'b>( &mut self, - subcommand: Option<(&str, &clap::ArgMatches)> + arg_matches: &clap::ArgMatches, ) { - if let Some((name, arg_matches)) = subcommand { + if let Some((name, sub_arg_matches)) = arg_matches.subcommand() { match (name, self) { #( #subcommands ),* #( #child_subcommands ),* - (_, s) => if let Some(sub) = ::from_subcommand(Some((name, arg_matches))) { - *s = sub; + (other_name, s) => { + if let Some(sub) = ::from_arg_matches(arg_matches) { + *s = sub; + } } } } diff --git a/clap_derive/src/dummies.rs b/clap_derive/src/dummies.rs index ea478de67d19..72e8ab4bed2c 100644 --- a/clap_derive/src/dummies.rs +++ b/clap_derive/src/dummies.rs @@ -6,13 +6,12 @@ use quote::quote; pub fn clap_struct(name: &Ident) { into_app(name); - from_arg_matches(name); + args(name); append_dummy(quote!( impl clap::Clap for #name {} )); } pub fn clap_enum(name: &Ident) { into_app(name); - from_arg_matches(name); subcommand(name); append_dummy(quote!( impl clap::Clap for #name {} )); } @@ -23,15 +22,9 @@ pub fn into_app(name: &Ident) { fn into_app<'b>() -> clap::App<'b> { unimplemented!() } - fn augment_clap<'b>(_app: clap::App<'b>) -> clap::App<'b> { - unimplemented!() - } fn into_app_for_update<'b>() -> clap::App<'b> { unimplemented!() } - fn augment_clap_for_update<'b>(_app: clap::App<'b>) -> clap::App<'b> { - unimplemented!() - } } }); } @@ -39,7 +32,7 @@ pub fn into_app(name: &Ident) { pub fn from_arg_matches(name: &Ident) { append_dummy(quote! { impl clap::FromArgMatches for #name { - fn from_arg_matches(_m: &clap::ArgMatches) -> Self { + fn from_arg_matches(_m: &clap::ArgMatches) -> Option { unimplemented!() } fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) { @@ -50,18 +43,27 @@ pub fn from_arg_matches(name: &Ident) { } pub fn subcommand(name: &Ident) { + from_arg_matches(name); append_dummy(quote! { impl clap::Subcommand for #name { - fn from_subcommand(_sub: Option<(&str, &clap::ArgMatches)>) -> Option { + fn augment_subcommands(_app: clap::App<'_>) -> clap::App<'_> { unimplemented!() } - fn update_from_subcommand(&mut self, _sub: Option<(&str, &clap::ArgMatches)>) { + fn augment_subcommands_for_update(_app: clap::App<'_>) -> clap::App<'_> { unimplemented!() } - fn augment_subcommands(_app: clap::App<'_>) -> clap::App<'_> { + } + }); +} + +pub fn args(name: &Ident) { + from_arg_matches(name); + append_dummy(quote! { + impl clap::Args for #name { + fn augment_args(_app: clap::App<'_>) -> clap::App<'_> { unimplemented!() } - fn augment_subcommands_for_update(_app: clap::App<'_>) -> clap::App<'_> { + fn augment_args_for_update(_app: clap::App<'_>) -> clap::App<'_> { unimplemented!() } } diff --git a/clap_derive/src/lib.rs b/clap_derive/src/lib.rs index 6ab11e00490e..ae077606ca3f 100644 --- a/clap_derive/src/lib.rs +++ b/clap_derive/src/lib.rs @@ -77,3 +77,11 @@ pub fn subcommand(input: TokenStream) -> TokenStream { let input: DeriveInput = parse_macro_input!(input); derives::derive_subcommand(&input).into() } + +/// Generates the `Subcommand` impl. +#[proc_macro_derive(Args, attributes(clap))] +#[proc_macro_error] +pub fn args(input: TokenStream) -> TokenStream { + let input: DeriveInput = parse_macro_input!(input); + derives::derive_args(&input).into() +} diff --git a/clap_derive/tests/issues.rs b/clap_derive/tests/issues.rs index dcb920df065e..b4bd69e001fd 100644 --- a/clap_derive/tests/issues.rs +++ b/clap_derive/tests/issues.rs @@ -34,7 +34,10 @@ fn issue_289() { #[derive(Clap)] #[clap(setting = AppSettings::InferSubcommands)] enum Args { - SomeCommand(SubSubCommand), + SomeCommand { + #[clap(subcommand)] + sub: SubSubCommand, + }, AnotherCommand, } diff --git a/src/derive.rs b/src/derive.rs index 9ee3d2cfc3ba..ac8359803632 100644 --- a/src/derive.rs +++ b/src/derive.rs @@ -5,6 +5,8 @@ use crate::{App, ArgMatches, Error}; use std::ffi::OsString; +/// Parse command-line arguments into `Self`. +/// /// The primary one-stop-shop trait used to create an instance of a `clap` /// [`App`], conduct the parsing, and turn the resulting [`ArgMatches`] back /// into concrete instance of the user struct. @@ -15,6 +17,8 @@ use std::ffi::OsString; /// and `parse_from` which allows the consumer to supply the iterator (along /// with fallible options for each). /// +/// See also [`Subcommand`] and [`Args`]. +/// /// # Examples /// /// The following example creates a `Context` struct that would be used @@ -70,13 +74,14 @@ pub trait Clap: FromArgMatches + IntoApp + Sized { /// Parse from `std::env::args_os()`, exit on error fn parse() -> Self { let matches = ::into_app().get_matches(); - ::from_arg_matches(&matches) + ::from_arg_matches(&matches).expect("IntoApp validated everything") } /// Parse from `std::env::args_os()`, return Err on error. fn try_parse() -> Result { let matches = ::into_app().try_get_matches()?; - Ok(::from_arg_matches(&matches)) + Ok(::from_arg_matches(&matches) + .expect("IntoApp validated everything")) } /// Parse from iterator, exit on error @@ -87,7 +92,7 @@ pub trait Clap: FromArgMatches + IntoApp + Sized { T: Into + Clone, { let matches = ::into_app().get_matches_from(itr); - ::from_arg_matches(&matches) + ::from_arg_matches(&matches).expect("IntoApp validated everything") } /// Parse from iterator, return Err on error. @@ -98,7 +103,8 @@ pub trait Clap: FromArgMatches + IntoApp + Sized { T: Into + Clone, { let matches = ::into_app().try_get_matches_from(itr)?; - Ok(::from_arg_matches(&matches)) + Ok(::from_arg_matches(&matches) + .expect("IntoApp validated everything")) } /// Update from iterator, exit on error @@ -126,7 +132,7 @@ pub trait Clap: FromArgMatches + IntoApp + Sized { } } -/// Build an [`App`] according to the struct +/// Build an [`App`] relevant for a user-defined container. pub trait IntoApp: Sized { /// Build an [`App`] that can instantiate `Self`. /// @@ -136,14 +142,6 @@ pub trait IntoApp: Sized { /// /// See [`FromArgMatches::update_from_arg_matches`] for updating `self`. fn into_app_for_update<'help>() -> App<'help>; - /// Append to [`App`] so it can instantiate `Self`. - /// - /// This is used to implement `#[clap(flatten)]` - fn augment_clap(app: App<'_>) -> App<'_>; - /// Append to [`App`] so it can update `self`. - /// - /// This is used to implement `#[clap(flatten)]` - fn augment_clap_for_update(app: App<'_>) -> App<'_>; } /// Converts an instance of [`ArgMatches`] to a user-defined container. @@ -178,25 +176,75 @@ pub trait FromArgMatches: Sized { /// } /// } /// ``` - fn from_arg_matches(matches: &ArgMatches) -> Self; + fn from_arg_matches(matches: &ArgMatches) -> Option; /// Assign values from `ArgMatches` to `self`. fn update_from_arg_matches(&mut self, matches: &ArgMatches); } -/// @TODO @release @docs -pub trait Subcommand: Sized { - /// Instantiate `Self` from subcommand name and [`ArgMatches`]. - /// - /// Returns `None` if subcommand does not exist - fn from_subcommand(subcommand: Option<(&str, &ArgMatches)>) -> Option; - /// Assign values from `ArgMatches` to `self`. - fn update_from_subcommand(&mut self, subcommand: Option<(&str, &ArgMatches)>); +/// Parse arguments into a user-defined container. +/// +/// Implementing this trait let's a parent container delegate argument parsing behavior to `Self`. +/// with: +/// - `#[clap(flatten)] args: ChildArgs`: Can only be used with struct fields. +/// - Implicitly: Can only be used with enum variants. +/// +/// +/// # Example +/// +/// ```rust +/// #[derive(clap::Clap)] +/// struct Args { +/// #[clap(flatten)] +/// logging: LogArgs, +/// } +/// +/// #[derive(clap::Args)] +/// struct LogArgs { +/// #[clap(long, short = 'v', parse(from_occurrences))] +/// verbose: i8, +/// } +/// ``` +pub trait Args: FromArgMatches + Sized { /// Append to [`App`] so it can instantiate `Self`. /// + /// See also [`IntoApp`]. + fn augment_args(app: App<'_>) -> App<'_>; + /// Append to [`App`] so it can update `self`. + /// /// This is used to implement `#[clap(flatten)]` /// /// See also [`IntoApp`]. + fn augment_args_for_update(app: App<'_>) -> App<'_>; +} + +/// Parse a sub-command into a user-defined enum. +/// +/// Implementing this trait let's a parent container delegate subcommand behavior to `Self`. +/// with: +/// - `#[clap(subcommand)] field: SubCmd`: Can be used with either struct fields or enum variants. +/// - `#[clap(flatten)] Variant(SubCmd)`: Can only be used with enum variants. +/// +/// +/// # Example +/// +/// ```rust +/// #[derive(clap::Clap)] +/// struct Args { +/// #[clap(subcommand)] +/// action: Action, +/// } +/// +/// #[derive(clap::Subcommand)] +/// enum Action { +/// Add, +/// Remove, +/// } +/// ``` +pub trait Subcommand: FromArgMatches + Sized { + /// Append to [`App`] so it can instantiate `Self`. + /// + /// See also [`IntoApp`]. fn augment_subcommands(app: App<'_>) -> App<'_>; /// Append to [`App`] so it can update `self`. /// @@ -269,33 +317,30 @@ impl IntoApp for Box { fn into_app<'help>() -> App<'help> { ::into_app() } - fn augment_clap(app: App<'_>) -> App<'_> { - ::augment_clap(app) - } fn into_app_for_update<'help>() -> App<'help> { ::into_app_for_update() } - fn augment_clap_for_update(app: App<'_>) -> App<'_> { - ::augment_clap_for_update(app) - } } impl FromArgMatches for Box { - fn from_arg_matches(matches: &ArgMatches) -> Self { - Box::new(::from_arg_matches(matches)) + fn from_arg_matches(matches: &ArgMatches) -> Option { + ::from_arg_matches(matches).map(Box::new) } fn update_from_arg_matches(&mut self, matches: &ArgMatches) { - ::update_from_arg_matches(self, matches); + ::update_from_arg_matches(self, matches) } } -impl Subcommand for Box { - fn from_subcommand(subcommand: Option<(&str, &ArgMatches)>) -> Option { - ::from_subcommand(subcommand).map(Box::new) +impl Args for Box { + fn augment_args(app: App<'_>) -> App<'_> { + ::augment_args(app) } - fn update_from_subcommand(&mut self, subcommand: Option<(&str, &ArgMatches)>) { - ::update_from_subcommand(self, subcommand); + fn augment_args_for_update(app: App<'_>) -> App<'_> { + ::augment_args_for_update(app) } +} + +impl Subcommand for Box { fn augment_subcommands(app: App<'_>) -> App<'_> { ::augment_subcommands(app) } diff --git a/src/lib.rs b/src/lib.rs index 6cf85adfac05..03c3e517a83b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ pub use crate::{ }; #[cfg(feature = "derive")] -pub use crate::derive::{ArgEnum, Clap, FromArgMatches, IntoApp, Subcommand}; +pub use crate::derive::{ArgEnum, Args, Clap, FromArgMatches, IntoApp, Subcommand}; #[cfg(feature = "yaml")] #[doc(hidden)]