diff --git a/clap_derive/src/attrs.rs b/clap_derive/src/attrs.rs index edfa2627..f8a79e52 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 index f0fa50e4..bc7c523f 100644 --- a/clap_derive/src/derives/args.rs +++ b/clap_derive/src/derives/args.rs @@ -11,23 +11,98 @@ // 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}, + 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, - parent_attribute: &Attrs, + attrs: &[Attribute], ) -> TokenStream { - let constructor = gen_constructor(fields, parent_attribute); - let updater = gen_updater(fields, parent_attribute, true); + 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)] @@ -43,8 +118,9 @@ pub fn gen_from_arg_matches_for_struct( )] #[deny(clippy::correctness)] impl clap::FromArgMatches for #struct_name { - fn from_arg_matches(arg_matches: &clap::ArgMatches) -> Self { - #struct_name #constructor + 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) { @@ -123,7 +199,7 @@ pub fn gen_augment( Kind::Flatten => { let ty = &field.ty; Some(quote_spanned! { kind.span()=> - let #app_var = <#ty as clap::IntoApp>::augment_clap(#app_var); + let #app_var = <#ty as clap::Args>::augment_args(#app_var); }) } Kind::Arg(ty) => { @@ -289,14 +365,14 @@ pub fn gen_constructor(fields: &Punctuated, parent_attribute: &Att }; quote_spanned! { kind.span()=> #field_name: { - <#subcmd_type as clap::Subcommand>::from_subcommand(#arg_matches.subcommand()) + <#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) + #field_name: clap::FromArgMatches::from_arg_matches(#arg_matches).unwrap() }, Kind::Skip(val) => match val { @@ -304,7 +380,9 @@ pub fn gen_constructor(fields: &Punctuated, parent_attribute: &Att Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()), }, - Kind::Arg(ty) | Kind::FromGlobal(ty) => gen_parsers(&attrs, ty, field_name, field, None), + Kind::Arg(ty) | Kind::FromGlobal(ty) => { + gen_parsers(&attrs, ty, field_name, field, None) + } } }); @@ -350,7 +428,7 @@ pub fn gen_updater( }; let updater = quote_spanned! { ty.span()=> - <#subcmd_type as clap::Subcommand>::update_from_subcommand(#field_name, #arg_matches.subcommand()); + <#subcmd_type as clap::FromArgMatches>::update_from_arg_matches(#field_name, #arg_matches); }; let updater = match **ty { @@ -358,8 +436,8 @@ pub fn gen_updater( if let Some(#field_name) = #field_name.as_mut() { #updater } else { - *#field_name = <#subcmd_type as clap::Subcommand>::from_subcommand( - #arg_matches.subcommand() + *#field_name = <#subcmd_type as clap::FromArgMatches>::from_arg_matches( + #arg_matches ) } }, diff --git a/clap_derive/src/derives/clap.rs b/clap_derive/src/derives/clap.rs index ab888ac1..39d4e769 100644 --- a/clap_derive/src/derives/clap.rs +++ b/clap_derive/src/derives/clap.rs @@ -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 = args::gen_from_arg_matches_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 = subcommand::gen_from_arg_matches_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/into_app.rs b/clap_derive/src/derives/into_app.rs index 9c781c02..1a7b144b 100644 --- a/clap_derive/src/derives/into_app.rs +++ b/clap_derive/src/derives/into_app.rs @@ -17,14 +17,10 @@ use std::env; use proc_macro2::{Span, TokenStream}; use proc_macro_error::abort_call_site; use quote::quote; -use syn::{ - punctuated::Punctuated, token::Comma, Attribute, Data, DataStruct, DeriveInput, Field, Fields, - Ident, -}; +use syn::{Attribute, Data, DataStruct, DeriveInput, Fields, Ident}; use crate::{ - attrs::{Attrs, GenOutput, Name, DEFAULT_CASING, DEFAULT_ENV_CASING}, - derives::args, + attrs::{Attrs, Name, DEFAULT_CASING, DEFAULT_ENV_CASING}, dummies, utils::Sp, }; @@ -36,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)] @@ -70,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 { @@ -107,59 +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 = args::gen_augment(fields, &app_var, parent_attribute, false); - let augmentation_update = args::gen_augment(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 - } - } -} diff --git a/clap_derive/src/derives/mod.rs b/clap_derive/src/derives/mod.rs index 8db2035a..f90b53d3 100644 --- a/clap_derive/src/derives/mod.rs +++ b/clap_derive/src/derives/mod.rs @@ -19,6 +19,6 @@ 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 094c6a9d..8d73ce61 100644 --- a/clap_derive/src/derives/subcommand.rs +++ b/clap_derive/src/derives/subcommand.rs @@ -1,3 +1,16 @@ +// 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 crate::{ attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING}, derives::args, @@ -24,22 +37,22 @@ 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(name.clone()), + Name::Derived(enum_name.clone()), Sp::call_site(DEFAULT_CASING), 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 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, @@ -52,16 +65,29 @@ 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::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 + } } } } -pub fn gen_from_arg_matches_for_enum(name: &Ident) -> TokenStream { +fn gen_from_arg_matches_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream { + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Derived(name.clone()), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + + let from_arg_matches = gen_from_arg_matches(name, &e.variants, &attrs); + let update_from_arg_matches = gen_update_from_arg_matches(name, &e.variants, &attrs); + quote! { #[allow(dead_code, unreachable_code, unused_variables)] #[allow( @@ -76,18 +102,13 @@ pub fn gen_from_arg_matches_for_enum(name: &Ident) -> TokenStream { )] #[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()); - } + #from_arg_matches + #update_from_arg_matches } } } fn gen_augment( - fn_name: &str, variants: &Punctuated, parent_attribute: &Attrs, override_required: bool, @@ -137,7 +158,7 @@ fn gen_augment( let ty = &unnamed[0]; quote_spanned! { ty.span()=> { - <#ty as clap::IntoApp>::augment_clap(#app_var) + <#ty as clap::Args>::augment_args(#app_var) } } } @@ -163,17 +184,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, @@ -262,7 +280,7 @@ fn gen_from_subcommand( 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), }; @@ -279,7 +297,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)); } } @@ -293,39 +311,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_arg_matches( name: &Ident, variants: &Punctuated, parent_attribute: &Attrs, @@ -384,7 +397,7 @@ fn gen_update_from_subcommand( quote!((ref mut arg)), quote!(clap::FromArgMatches::update_from_arg_matches( arg, - arg_matches + sub_arg_matches )), ) } else { @@ -394,7 +407,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 + } } }); @@ -407,7 +423,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); }, ) } @@ -422,16 +438,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 ea478de6..72e8ab4b 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,14 +43,9 @@ 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 { - unimplemented!() - } - fn update_from_subcommand(&mut self, _sub: Option<(&str, &clap::ArgMatches)>) { - unimplemented!() - } fn augment_subcommands(_app: clap::App<'_>) -> clap::App<'_> { unimplemented!() } @@ -68,6 +56,20 @@ pub fn subcommand(name: &Ident) { }); } +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_args_for_update(_app: clap::App<'_>) -> clap::App<'_> { + unimplemented!() + } + } + }); +} + pub fn arg_enum(name: &Ident) { append_dummy(quote! { impl clap::ArgEnum for #name { diff --git a/clap_derive/src/lib.rs b/clap_derive/src/lib.rs index 6ab11e00..2fbecfa0 100644 --- a/clap_derive/src/lib.rs +++ b/clap_derive/src/lib.rs @@ -54,14 +54,6 @@ pub fn clap(input: TokenStream) -> TokenStream { derives::derive_clap(&input).into() } -// /// Generates the `FromArgMatches` impl. -// #[proc_macro_derive(FromArgMatches, attributes(clap))] -// #[proc_macro_error] -// pub fn from_arg_matches(input: TokenStream) -> TokenStream { -// let input: DeriveInput = parse_macro_input!(input); -// derives::derive_from_arg_matches(&input).into() -// } - /// Generates the `IntoApp` impl. #[proc_macro_derive(IntoApp, attributes(clap))] #[proc_macro_error] @@ -77,3 +69,11 @@ pub fn subcommand(input: TokenStream) -> TokenStream { let input: DeriveInput = parse_macro_input!(input); derives::derive_subcommand(&input).into() } + +/// Generates the `Args` 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 dcb920df..b4bd69e0 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/clap_derive/tests/ui/enum_variant_not_args.rs b/clap_derive/tests/ui/enum_variant_not_args.rs new file mode 100644 index 00000000..8628b925 --- /dev/null +++ b/clap_derive/tests/ui/enum_variant_not_args.rs @@ -0,0 +1,9 @@ +#[derive(clap::Clap)] +enum Opt { + Sub(SubCmd), +} + +#[derive(clap::Clap)] +enum SubCmd {} + +fn main() {} diff --git a/clap_derive/tests/ui/enum_variant_not_args.stderr b/clap_derive/tests/ui/enum_variant_not_args.stderr new file mode 100644 index 00000000..f1a9b207 --- /dev/null +++ b/clap_derive/tests/ui/enum_variant_not_args.stderr @@ -0,0 +1,7 @@ +error[E0277]: the trait bound `SubCmd: clap::Args` is not satisfied + --> $DIR/enum_variant_not_args.rs:3:9 + | +3 | Sub(SubCmd), + | ^^^^^^ the trait `clap::Args` is not implemented for `SubCmd` + | + = note: required by `augment_args` diff --git a/clap_derive/tests/ui/flatten_on_subcommand.rs b/clap_derive/tests/ui/flatten_on_subcommand.rs new file mode 100644 index 00000000..ad7cd5a0 --- /dev/null +++ b/clap_derive/tests/ui/flatten_on_subcommand.rs @@ -0,0 +1,10 @@ +#[derive(clap::Clap)] +struct Opt { + #[clap(flatten)] + sub: SubCmd, +} + +#[derive(clap::Clap)] +enum SubCmd {} + +fn main() {} diff --git a/clap_derive/tests/ui/flatten_on_subcommand.stderr b/clap_derive/tests/ui/flatten_on_subcommand.stderr new file mode 100644 index 00000000..cc753951 --- /dev/null +++ b/clap_derive/tests/ui/flatten_on_subcommand.stderr @@ -0,0 +1,7 @@ +error[E0277]: the trait bound `SubCmd: clap::Args` is not satisfied + --> $DIR/flatten_on_subcommand.rs:3:12 + | +3 | #[clap(flatten)] + | ^^^^^^^ the trait `clap::Args` is not implemented for `SubCmd` + | + = note: required by `augment_args` diff --git a/src/derive.rs b/src/derive.rs index 9ee3d2cf..8a535ff0 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,77 @@ 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 lets a parent container delegate argument parsing behavior to `Self`. +/// with: +/// - `#[clap(flatten)] args: ChildArgs`: Attribute can only be used with struct fields that impl +/// `Args`. +/// - `Variant(ChildArgs)`: No attribute is used with enum variants that impl `Args`. +/// +/// +/// # 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`: Attribute can be used with either struct fields or enum +/// variants that impl `Subcommand`. +/// - `#[clap(flatten)] Variant(SubCmd)`: Attribute can only be used with enum variants that impl +/// `Subcommand`. +/// +/// # 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 +319,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 Args for Box { + fn augment_args(app: App<'_>) -> App<'_> { + ::augment_args(app) + } + fn augment_args_for_update(app: App<'_>) -> App<'_> { + ::augment_args_for_update(app) } } impl Subcommand for Box { - fn from_subcommand(subcommand: Option<(&str, &ArgMatches)>) -> Option { - ::from_subcommand(subcommand).map(Box::new) - } - fn update_from_subcommand(&mut self, subcommand: Option<(&str, &ArgMatches)>) { - ::update_from_subcommand(self, subcommand); - } fn augment_subcommands(app: App<'_>) -> App<'_> { ::augment_subcommands(app) } diff --git a/src/lib.rs b/src/lib.rs index 6cf85adf..03c3e517 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)]