forked from clap-rs/clap
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(derive)!: Compile-error on nested subcommands
Before, partial command lines would panic at runtime. Now it'll be a compile error For example: ``` pub enum Opt { Daemon(DaemonCommand), } pub enum DaemonCommand { Start, Stop, } ``` Gives: ``` error[E0277]: the trait bound `DaemonCommand: clap::Args` is not satisfied --> clap_derive/tests/subcommands.rs:297:16 | 297 | Daemon(DaemonCommand), | ^^^^^^^^^^^^^ the trait `clap::Args` is not implemented for `DaemonCommand` | = note: required by `augment_args` ``` To nest this, you currently need `enum -> struct -> enum`. A later change will make it so you can use the `subcommand` attribute within enums to cover this case. This is a part of clap-rs#2005
- Loading branch information
Showing
11 changed files
with
289 additions
and
210 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,94 @@ | ||
// Copyright 2018 Guillaume Pinot (@TeXitoi) <[email protected]>, | ||
// Kevin Knapp (@kbknapp) <[email protected]>, and | ||
// Andrew Hobden (@hoverbear) <[email protected]> | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
// | ||
// This work was derived from Structopt (https://github.com/TeXitoi/structopt) | ||
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the | ||
// MIT/Apache 2.0 license. | ||
use proc_macro2::TokenStream; | ||
use proc_macro_error::abort; | ||
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 +104,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 +185,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,22 +351,24 @@ 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 { | ||
None => quote_spanned!(kind.span()=> #field_name: Default::default()), | ||
Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()), | ||
}, | ||
|
||
Kind::Arg(ty) | Kind::FromGlobal(ty) => gen_parsers(&attrs, ty, field_name, field, None), | ||
Kind::Arg(ty) | Kind::FromGlobal(ty) => { | ||
gen_parsers(&attrs, ty, field_name, field, None) | ||
} | ||
} | ||
}); | ||
|
||
|
@@ -350,16 +414,16 @@ 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 { | ||
Ty::Option => quote_spanned! { kind.span()=> | ||
if let Some(#field_name) = #field_name.as_mut() { | ||
#updater | ||
} else { | ||
*#field_name = <#subcmd_type as clap::Subcommand>::from_subcommand( | ||
#arg_matches.subcommand() | ||
*#field_name = <#subcmd_type as clap::FromArgMatches>::from_arg_matches( | ||
#arg_matches | ||
) | ||
} | ||
}, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.