mirror of
https://github.com/clap-rs/clap
synced 2024-12-14 23:02:31 +00:00
Merge pull request #2586 from epage/args
fix(derive)!: Compile-error on nested subcommands
This commit is contained in:
commit
62588bd82c
15 changed files with 340 additions and 207 deletions
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -34,7 +34,10 @@ fn issue_289() {
|
|||
#[derive(Clap)]
|
||||
#[clap(setting = AppSettings::InferSubcommands)]
|
||||
enum Args {
|
||||
SomeCommand(SubSubCommand),
|
||||
SomeCommand {
|
||||
#[clap(subcommand)]
|
||||
sub: SubSubCommand,
|
||||
},
|
||||
AnotherCommand,
|
||||
}
|
||||
|
||||
|
|
9
clap_derive/tests/ui/enum_variant_not_args.rs
Normal file
9
clap_derive/tests/ui/enum_variant_not_args.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
#[derive(clap::Clap)]
|
||||
enum Opt {
|
||||
Sub(SubCmd),
|
||||
}
|
||||
|
||||
#[derive(clap::Clap)]
|
||||
enum SubCmd {}
|
||||
|
||||
fn main() {}
|
7
clap_derive/tests/ui/enum_variant_not_args.stderr
Normal file
7
clap_derive/tests/ui/enum_variant_not_args.stderr
Normal 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`
|
10
clap_derive/tests/ui/flatten_on_subcommand.rs
Normal file
10
clap_derive/tests/ui/flatten_on_subcommand.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
#[derive(clap::Clap)]
|
||||
struct Opt {
|
||||
#[clap(flatten)]
|
||||
sub: SubCmd,
|
||||
}
|
||||
|
||||
#[derive(clap::Clap)]
|
||||
enum SubCmd {}
|
||||
|
||||
fn main() {}
|
7
clap_derive/tests/ui/flatten_on_subcommand.stderr
Normal file
7
clap_derive/tests/ui/flatten_on_subcommand.stderr
Normal 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`
|
121
src/derive.rs
121
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 = <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)
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Reference in a new issue