Merge pull request #2586 from epage/args

fix(derive)!: Compile-error on nested subcommands
This commit is contained in:
Pavan Kumar Sunkara 2021-07-16 16:44:21 +01:00 committed by GitHub
commit 62588bd82c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 340 additions and 207 deletions

View file

@ -107,13 +107,6 @@ pub struct Attrs {
kind: Sp<Kind>,
}
/// 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 }

View file

@ -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::<Field, Comma>::new(), &input.attrs),
_ => abort_call_site!("`#[derive(Args)]` only supports non-tuple structs"),
}
}
pub fn gen_for_struct(
struct_name: &Ident,
fields: &Punctuated<Field, Comma>,
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<Field, Comma>,
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<Self> {
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<Field, Comma>, 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<Field, Comma>, 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
)
}
},

View file

@ -56,27 +56,25 @@ fn gen_for_struct(
fields: &Punctuated<Field, Comma>,
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
}
}

View file

@ -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::<Field, Comma>::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<Field, Comma>,
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<Field, Comma>, 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
}
}
}

View file

@ -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;

View file

@ -1,3 +1,16 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@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 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<Variant, Token![,]>,
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<Variant, Token![,]>,
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<Self> {
match subcommand {
fn from_arg_matches(arg_matches: &clap::ArgMatches) -> Option<Self> {
match arg_matches.subcommand() {
#( #match_arms, )*
other => {
::std::option::Option::Some((other, sub_arg_matches)) => {
#( #child_subcommands )else*
match other {
#wildcard
}
}
::std::option::Option::None => ::std::option::Option::None,
}
}
}
}
fn gen_update_from_subcommand(
fn gen_update_from_arg_matches(
name: &Ident,
variants: &Punctuated<Variant, Token![,]>,
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,19 +438,21 @@ 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) = <Self as clap::Subcommand>::from_subcommand(Some((name, arg_matches))) {
(other_name, s) => {
if let Some(sub) = <Self as clap::FromArgMatches>::from_arg_matches(arg_matches) {
*s = sub;
}
}
}
}
}
}
}

View file

@ -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<Self> {
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<Self> {
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 {

View file

@ -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()
}

View file

@ -34,7 +34,10 @@ fn issue_289() {
#[derive(Clap)]
#[clap(setting = AppSettings::InferSubcommands)]
enum Args {
SomeCommand(SubSubCommand),
SomeCommand {
#[clap(subcommand)]
sub: SubSubCommand,
},
AnotherCommand,
}

View file

@ -0,0 +1,9 @@
#[derive(clap::Clap)]
enum Opt {
Sub(SubCmd),
}
#[derive(clap::Clap)]
enum SubCmd {}
fn main() {}

View file

@ -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`

View file

@ -0,0 +1,10 @@
#[derive(clap::Clap)]
struct Opt {
#[clap(flatten)]
sub: SubCmd,
}
#[derive(clap::Clap)]
enum SubCmd {}
fn main() {}

View file

@ -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`

View file

@ -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 = <Self as IntoApp>::into_app().get_matches();
<Self as FromArgMatches>::from_arg_matches(&matches)
<Self as FromArgMatches>::from_arg_matches(&matches).expect("IntoApp validated everything")
}
/// Parse from `std::env::args_os()`, return Err on error.
fn try_parse() -> Result<Self, Error> {
let matches = <Self as IntoApp>::into_app().try_get_matches()?;
Ok(<Self as FromArgMatches>::from_arg_matches(&matches))
Ok(<Self as FromArgMatches>::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<OsString> + Clone,
{
let matches = <Self as IntoApp>::into_app().get_matches_from(itr);
<Self as FromArgMatches>::from_arg_matches(&matches)
<Self as FromArgMatches>::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<OsString> + Clone,
{
let matches = <Self as IntoApp>::into_app().try_get_matches_from(itr)?;
Ok(<Self as FromArgMatches>::from_arg_matches(&matches))
Ok(<Self as FromArgMatches>::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<Self>;
/// 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<Self>;
/// 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<T: IntoApp> IntoApp for Box<T> {
fn into_app<'help>() -> App<'help> {
<T as IntoApp>::into_app()
}
fn augment_clap(app: App<'_>) -> App<'_> {
<T as IntoApp>::augment_clap(app)
}
fn into_app_for_update<'help>() -> App<'help> {
<T as IntoApp>::into_app_for_update()
}
fn augment_clap_for_update(app: App<'_>) -> App<'_> {
<T as IntoApp>::augment_clap_for_update(app)
}
}
impl<T: FromArgMatches> FromArgMatches for Box<T> {
fn from_arg_matches(matches: &ArgMatches) -> Self {
Box::new(<T as FromArgMatches>::from_arg_matches(matches))
fn from_arg_matches(matches: &ArgMatches) -> Option<Self> {
<T as FromArgMatches>::from_arg_matches(matches).map(Box::new)
}
fn update_from_arg_matches(&mut self, matches: &ArgMatches) {
<T as FromArgMatches>::update_from_arg_matches(self, matches);
<T as FromArgMatches>::update_from_arg_matches(self, matches)
}
}
impl<T: Args> Args for Box<T> {
fn augment_args(app: App<'_>) -> App<'_> {
<T as Args>::augment_args(app)
}
fn augment_args_for_update(app: App<'_>) -> App<'_> {
<T as Args>::augment_args_for_update(app)
}
}
impl<T: Subcommand> Subcommand for Box<T> {
fn from_subcommand(subcommand: Option<(&str, &ArgMatches)>) -> Option<Self> {
<T as Subcommand>::from_subcommand(subcommand).map(Box::new)
}
fn update_from_subcommand(&mut self, subcommand: Option<(&str, &ArgMatches)>) {
<T as Subcommand>::update_from_subcommand(self, subcommand);
}
fn augment_subcommands(app: App<'_>) -> App<'_> {
<T as Subcommand>::augment_subcommands(app)
}

View file

@ -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)]