Extract subcommands into separate trait

This commit is contained in:
CreepySkeleton 2020-02-08 22:36:00 +03:00
parent ae2e00f418
commit ae574df2f9
No known key found for this signature in database
GPG key ID: 10E7166A258A3E2C
48 changed files with 689 additions and 895 deletions

2
.gitignore vendored
View file

@ -28,3 +28,5 @@ clap-rs.iml
# Auxiliary files
test-results.test
expanded.rs
clap_derive/expanded.rs

View file

@ -104,10 +104,7 @@ pub struct Attrs {
/// 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 struct GenOutput {
pub tokens: proc_macro2::TokenStream,
pub attrs: Attrs,
}
pub type GenOutput = (proc_macro2::TokenStream, Attrs);
impl Method {
pub fn new(name: Ident, args: TokenStream) -> Self {

View file

@ -11,486 +11,53 @@
// 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::Span;
use proc_macro_error::{abort, abort_call_site, set_dummy};
use quote::{quote, quote_spanned};
use syn::{self, punctuated, spanned::Spanned, token, FieldsUnnamed, Ident};
use super::{from_argmatches, into_app, sub_type, Attrs, Kind, Name, ParserKind, Ty};
/// Generate a block of code to add arguments/subcommands corresponding to
/// the `fields` to an app.
fn gen_app_augmentation(
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
app_var: &syn::Ident,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let mut subcmds = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(
&field,
parent_attribute.casing(),
parent_attribute.env_casing(),
);
let kind = attrs.kind();
if let Kind::Subcommand(ty) = &*kind {
let subcmd_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
let required = if **ty == Ty::Option {
quote!()
} else {
quote_spanned! { kind.span()=>
let #app_var = #app_var.setting(
::clap::AppSettings::SubcommandRequiredElseHelp
);
}
};
let span = field.span();
let ts = quote! {
let #app_var = <#subcmd_type>::augment_app( #app_var );
#required
};
Some((span, ts))
} else {
None
}
});
let subcmd = subcmds.next().map(|(_, ts)| ts);
if let Some((span, _)) = subcmds.next() {
abort!(
span,
"multiple subcommand sets are not allowed, that's the second"
);
}
let args = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(
field,
parent_attribute.casing(),
parent_attribute.env_casing(),
);
let kind = attrs.kind();
match &*kind {
Kind::Subcommand(_) | Kind::Skip(_) => None,
Kind::Flatten => {
let ty = &field.ty;
Some(quote_spanned! { kind.span()=>
let #app_var = <#ty>::augment_app(#app_var);
let #app_var = if <#ty>::is_subcommand() {
#app_var.setting(::clap::AppSettings::SubcommandRequiredElseHelp)
} else {
#app_var
};
})
}
Kind::Arg(ty) => {
let convert_type = match **ty {
Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty),
Ty::OptionOption | Ty::OptionVec => {
sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty)
}
_ => &field.ty,
};
let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
let flag = *attrs.parser().kind == ParserKind::FromFlag;
let parser = attrs.parser();
let func = &parser.func;
let validator = match *parser.kind {
ParserKind::TryFromStr => quote_spanned! { func.span()=>
.validator(|s| {
#func(s.as_str())
.map(|_: #convert_type| ())
.map_err(|e| e.to_string())
})
},
ParserKind::TryFromOsStr => quote_spanned! { func.span()=>
.validator_os(|s| #func(&s).map(|_: #convert_type| ()))
},
_ => quote!(),
};
let modifier = match **ty {
Ty::Bool => quote!(),
Ty::Option => quote_spanned! { ty.span()=>
.takes_value(true)
#validator
},
Ty::OptionOption => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple(false)
.min_values(0)
.max_values(1)
#validator
},
Ty::OptionVec => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple(true)
.min_values(0)
#validator
},
Ty::Vec => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple(true)
#validator
},
Ty::Other if occurrences => quote_spanned! { ty.span()=>
.multiple_occurrences(true)
},
Ty::Other if flag => quote_spanned! { ty.span()=>
.takes_value(false)
.multiple(false)
},
Ty::Other => {
let required = !attrs.has_method("default_value");
quote_spanned! { ty.span()=>
.takes_value(true)
.required(#required)
#validator
}
}
};
let name = attrs.cased_name();
let methods = attrs.field_methods();
Some(quote_spanned! { field.span()=>
let #app_var = #app_var.arg(
::clap::Arg::with_name(#name)
#modifier
#methods
);
})
}
}
});
let app_methods = parent_attribute.top_level_methods();
let version = parent_attribute.version();
quote! {{
let #app_var = #app_var#app_methods;
#( #args )*
#subcmd
#app_var#version
}}
}
fn gen_augment_app_fn(
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let app_var = syn::Ident::new("app", proc_macro2::Span::call_site());
let augmentation = gen_app_augmentation(fields, &app_var, parent_attribute);
quote! {
pub fn augment_app<'b>(
#app_var: ::clap::App<'b>
) -> ::clap::App<'b> {
#augmentation
}
}
}
fn gen_augment_app_for_enum(
variants: &punctuated::Punctuated<syn::Variant, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
use syn::Fields::*;
let subcommands = variants.iter().map(|variant| {
let attrs = Attrs::from_struct(
variant.span(),
&variant.attrs,
Name::Derived(variant.ident.clone()),
parent_attribute.casing(),
parent_attribute.env_casing(),
);
let kind = attrs.kind();
match &*kind {
Kind::Flatten => match variant.fields {
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0];
quote! {
let app = <#ty>::augment_app(app);
}
}
_ => abort!(
variant.span(),
"`flatten` is usable only with single-typed tuple variants"
),
},
_ => {
let app_var = Ident::new("subcommand", Span::call_site());
let arg_block = match variant.fields {
Named(ref fields) => gen_app_augmentation(&fields.named, &app_var, &attrs),
Unit => quote!( #app_var ),
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0];
quote_spanned! { ty.span()=>
{
let #app_var = <#ty>::augment_app(
#app_var
);
if <#ty>::is_subcommand() {
#app_var.setting(
::clap::AppSettings::SubcommandRequiredElseHelp
)
} else {
#app_var
}
}
}
}
Unnamed(..) => abort!(
variant.span(),
"non single-typed tuple enums are not supported"
),
};
let name = attrs.cased_name();
let from_attrs = attrs.top_level_methods();
let version = attrs.version();
quote! {
let app = app.subcommand({
let #app_var = ::clap::App::new(#name);
let #app_var = #arg_block;
#app_var#from_attrs#version
});
}
}
}
});
let app_methods = parent_attribute.top_level_methods();
let version = parent_attribute.version();
quote! {
pub fn augment_app<'b>(
app: ::clap::App<'b>
) -> ::clap::App<'b> {
let app = app #app_methods;
#( #subcommands )*;
app #version
}
}
}
fn gen_from_subcommand(
name: &syn::Ident,
variants: &punctuated::Punctuated<syn::Variant, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
use syn::Fields::*;
let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants
.iter()
.map(|variant| {
let attrs = Attrs::from_struct(
variant.span(),
&variant.attrs,
Name::Derived(variant.ident.clone()),
parent_attribute.casing(),
parent_attribute.env_casing(),
);
(variant, attrs)
})
.partition(|(_, attrs)| {
let kind = attrs.kind();
match &*kind {
Kind::Flatten => true,
_ => false,
}
});
let match_arms = variants.iter().map(|(variant, attrs)| {
let sub_name = attrs.cased_name();
let variant_name = &variant.ident;
let constructor_block = match variant.fields {
Named(ref fields) => from_argmatches::gen_constructor(&fields.named, &attrs),
Unit => quote!(),
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0];
quote!( ( <#ty as ::clap::FromArgMatches>::from_argmatches(matches) ) )
}
Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
};
quote! {
(#sub_name, Some(matches)) => {
Some(#name :: #variant_name #constructor_block)
}
}
});
let child_subcommands = flatten_variants.iter().map(|(variant, _attrs)| {
let variant_name = &variant.ident;
match variant.fields {
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0];
quote! {
if let Some(res) = <#ty>::from_subcommand(other) {
return Some(#name :: #variant_name (res));
}
}
}
_ => abort!(
variant.span(),
"`flatten` is usable only with single-typed tuple variants"
),
}
});
quote! {
pub fn from_subcommand<'b>(
sub: (&'b str, Option<&'b ::clap::ArgMatches>)
) -> Option<Self> {
match sub {
#( #match_arms ),*,
other => {
#( #child_subcommands )*;
None
}
}
}
}
}
fn clap_impl_for_struct(
name: &syn::Ident,
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
attrs: &[syn::Attribute],
) -> proc_macro2::TokenStream {
let into_app_impl = into_app::gen_into_app_impl_for_struct(name, attrs);
let into_app_impl_tokens = into_app_impl.tokens;
let augment_app_fn = gen_augment_app_fn(fields, &into_app_impl.attrs);
let from_argmatches_impl =
from_argmatches::gen_from_argmatches_impl_for_struct(name, fields, &into_app_impl.attrs);
let parse_fns = gen_parse_fns(name);
quote! {
#[allow(unused_variables)]
impl ::clap::Clap for #name { }
#into_app_impl_tokens
#from_argmatches_impl
#[allow(dead_code, unreachable_code)]
#[doc(hidden)]
impl #name {
#augment_app_fn
#parse_fns
pub fn is_subcommand() -> bool { false }
}
}
}
fn clap_impl_for_enum(
name: &syn::Ident,
variants: &punctuated::Punctuated<syn::Variant, token::Comma>,
attrs: &[syn::Attribute],
) -> proc_macro2::TokenStream {
let into_app_impl = into_app::gen_into_app_impl_for_enum(name, attrs);
let into_app_impl_tokens = into_app_impl.tokens;
let augment_app_fn = gen_augment_app_for_enum(variants, &into_app_impl.attrs);
let from_argmatches_impl = from_argmatches::gen_from_argmatches_impl_for_enum(name);
let from_subcommand = gen_from_subcommand(name, variants, &into_app_impl.attrs);
let parse_fns = gen_parse_fns(name);
quote! {
#[allow(unused_variables)]
impl ::clap::Clap for #name { }
#into_app_impl_tokens
#from_argmatches_impl
#[allow(unused_variables, dead_code, unreachable_code)]
#[doc(hidden)]
impl #name {
#augment_app_fn
#from_subcommand
#parse_fns
pub fn is_subcommand() -> bool { true }
}
}
}
use super::{from_argmatches, into_app, subcommand};
use proc_macro2::Ident;
use proc_macro_error::abort_call_site;
use quote::quote;
use syn::{self, punctuated, token, Attribute, DataEnum};
pub fn derive_clap(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
use syn::Data::*;
let struct_name = &input.ident;
set_dummy(quote! {
impl ::clap::Clap for #struct_name {}
impl ::clap::IntoApp for #struct_name {
fn into_app<'b>() -> ::clap::App<'b> {
unimplemented!()
}
}
impl ::clap::FromArgMatches for #struct_name {
fn from_argmatches(m: &::clap::ArgMatches) -> Self {
unimplemented!()
}
}
impl #struct_name {
pub fn parse() -> Self {
unimplemented!();
}
}
});
let ident = &input.ident;
match input.data {
Struct(syn::DataStruct {
fields: syn::Fields::Named(ref fields),
..
}) => clap_impl_for_struct(struct_name, &fields.named, &input.attrs),
Enum(ref e) => clap_impl_for_enum(struct_name, &e.variants, &input.attrs),
_ => abort_call_site!("clap_derive only supports non-tuple structs and enums"),
}) => gen_for_struct(ident, &fields.named, &input.attrs),
Enum(ref e) => gen_for_enum(ident, &input.attrs, e),
_ => abort_call_site!("`#[derive(Clap)]` only supports non-tuple structs and enums"),
}
}
fn gen_parse_fns(name: &syn::Ident) -> proc_macro2::TokenStream {
fn gen_for_struct(
name: &syn::Ident,
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
attrs: &[syn::Attribute],
) -> proc_macro2::TokenStream {
let (into_app, attrs) = into_app::gen_for_struct(name, fields, attrs);
let from_arg_matches = from_argmatches::gen_for_struct(name, fields, &attrs);
quote! {
#[allow(unreachable_pub)]
pub fn parse() -> #name {
use ::clap::{FromArgMatches, IntoApp};
#name::from_argmatches(&#name::into_app().get_matches())
}
#[allow(unreachable_pub)]
pub fn try_parse() -> ::std::result::Result<#name, ::clap::Error> {
use ::clap::{FromArgMatches, IntoApp};
Ok(#name::from_argmatches(&#name::into_app().try_get_matches()?))
}
#[allow(unreachable_pub)]
pub fn parse_from<I, T>(itr: I) -> #name
where
I: ::std::iter::IntoIterator<Item = T>,
T: Into<::std::ffi::OsString> + Clone {
use ::clap::{FromArgMatches, IntoApp};
#name::from_argmatches(&#name::into_app().get_matches_from(itr))
}
#[allow(unreachable_pub)]
pub fn try_parse_from<I, T>(itr: I) -> ::std::result::Result<#name, ::clap::Error>
where
I: ::std::iter::IntoIterator<Item = T>,
T: Into<::std::ffi::OsString> + Clone {
use ::clap::{FromArgMatches, IntoApp};
Ok(#name::from_argmatches(&#name::into_app().try_get_matches_from(itr)?))
}
impl ::clap::Clap for #name {}
#into_app
#from_arg_matches
}
}
fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> proc_macro2::TokenStream {
let into_app = into_app::gen_for_enum(name);
let from_arg_matches = from_argmatches::gen_for_enum(name);
let subcommand = subcommand::gen_for_enum(name, attrs, e);
quote! {
impl ::clap::Clap for #name { }
#into_app
#from_arg_matches
#subcommand
}
}

View file

@ -13,10 +13,9 @@
// MIT/Apache 2.0 license.
use std::env;
use proc_macro2;
use proc_macro_error::abort_call_site;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned as _;
use syn::{punctuated, token};
use syn::{punctuated::Punctuated, spanned::Spanned, Token};
use super::{
spanned::Sp, sub_type, Attrs, Kind, Name, ParserKind, Ty, DEFAULT_CASING, DEFAULT_ENV_CASING,
@ -26,14 +25,12 @@ pub fn derive_from_argmatches(input: &syn::DeriveInput) -> proc_macro2::TokenStr
use syn::Data::*;
let struct_name = &input.ident;
let inner_impl = match input.data {
match input.data {
Struct(syn::DataStruct {
fields: syn::Fields::Named(ref fields),
..
}) => {
let name = env::var("CARGO_PKG_NAME")
.ok()
.unwrap_or_else(String::default);
let name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
let attrs = Attrs::from_struct(
proc_macro2::Span::call_site(),
@ -43,54 +40,17 @@ pub fn derive_from_argmatches(input: &syn::DeriveInput) -> proc_macro2::TokenStr
Sp::call_site(DEFAULT_ENV_CASING),
);
gen_from_argmatches_impl_for_struct(struct_name, &fields.named, &attrs)
gen_for_struct(struct_name, &fields.named, &attrs)
}
// Enum(ref e) => clap_for_enum_impl(struct_name, &e.variants, &input.attrs),
_ => panic!("clap_derive only supports non-tuple structs"), // and enums"),
};
quote!(#inner_impl)
}
pub fn gen_from_argmatches_impl_for_struct(
name: &syn::Ident,
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let from_argmatches_fn = gen_from_argmatches_fn_for_struct(name, fields, parent_attribute);
quote! {
impl ::clap::FromArgMatches for #name {
#from_argmatches_fn
}
impl From<::clap::ArgMatches> for #name {
fn from(m: ::clap::ArgMatches) -> Self {
use ::clap::FromArgMatches;
<Self as ::clap::FromArgMatches>::from_argmatches(&m)
}
}
// @TODO impl TryFrom once stable
}
}
pub fn gen_from_argmatches_fn_for_struct(
struct_name: &syn::Ident,
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let field_block = gen_constructor(fields, parent_attribute);
quote! {
fn from_argmatches(matches: &::clap::ArgMatches) -> Self {
#struct_name #field_block
Enum(_) => gen_for_enum(struct_name),
_ => {
abort_call_site!("#[derive(FromArgMatches)] only supports non-tuple structs and enums")
}
}
}
pub fn gen_constructor(
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
fields: &Punctuated<syn::Field, Token![,]>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let fields = fields.iter().map(|field| {
@ -112,12 +72,19 @@ pub fn gen_constructor(
_ => quote_spanned!( ty.span()=> .unwrap() ),
};
quote_spanned! { kind.span()=>
#field_name: <#subcmd_type>::from_subcommand(matches.subcommand())#unwrapper
#field_name: {
let (name, subcmd) = matches.subcommand();
<#subcmd_type as ::clap::Subcommand>::from_subcommand(
name,
subcmd
)
#unwrapper
}
}
}
Kind::Flatten => quote_spanned! { kind.span()=>
#field_name: ::clap::FromArgMatches::from_argmatches(matches)
#field_name: ::clap::FromArgMatches::from_arg_matches(matches)
},
Kind::Skip(val) => match val {
@ -222,22 +189,30 @@ pub fn gen_constructor(
}}
}
pub fn gen_from_argmatches_impl_for_enum(name: &syn::Ident) -> proc_macro2::TokenStream {
pub fn gen_for_struct(
struct_name: &syn::Ident,
fields: &Punctuated<syn::Field, Token![,]>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let constructor = gen_constructor(fields, parent_attribute);
quote! {
impl ::clap::FromArgMatches for #struct_name {
fn from_arg_matches(matches: &::clap::ArgMatches) -> Self {
#struct_name #constructor
}
}
}
}
pub fn gen_for_enum(name: &syn::Ident) -> proc_macro2::TokenStream {
quote! {
impl ::clap::FromArgMatches for #name {
fn from_argmatches(matches: &::clap::ArgMatches) -> Self {
<#name>::from_subcommand(matches.subcommand())
fn from_arg_matches(matches: &::clap::ArgMatches) -> Self {
let (name, subcmd) = matches.subcommand();
<#name as ::clap::Subcommand>::from_subcommand(name, subcmd)
.unwrap()
}
}
impl From<::clap::ArgMatches> for #name {
fn from(m: ::clap::ArgMatches) -> Self {
use ::clap::FromArgMatches;
<Self as ::clap::FromArgMatches>::from_argmatches(&m)
}
}
// @TODO: impl TryFrom once stable
}
}

View file

@ -11,124 +11,261 @@
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use std::env;
use proc_macro2;
use quote::quote;
use syn;
use proc_macro2::TokenStream;
use proc_macro_error::{abort, abort_call_site};
use quote::{quote, quote_spanned};
use syn::{punctuated::Punctuated, spanned::Spanned, Token};
use super::{spanned::Sp, Attrs, GenOutput, Name, DEFAULT_CASING, DEFAULT_ENV_CASING};
use super::{
spanned::Sp, ty::Ty, Attrs, GenOutput, Kind, Name, ParserKind, DEFAULT_CASING,
DEFAULT_ENV_CASING,
};
use crate::derives::ty::sub_type;
pub fn derive_into_app(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
use syn::Data::*;
let struct_name = &input.ident;
let inner_impl = match input.data {
Struct(syn::DataStruct { .. }) => {
gen_into_app_impl_for_struct(struct_name, &input.attrs).tokens
}
// @TODO impl into_app for enums?
// Enum(ref e) => clap_for_enum_impl(struct_name, &e.variants, &input.attrs),
_ => panic!("clap_derive only supports non-tuple structs"), // and enums"),
};
quote!(#inner_impl)
match input.data {
Struct(syn::DataStruct {
fields: syn::Fields::Named(ref fields),
..
}) => gen_for_struct(&input.ident, &fields.named, &input.attrs).0,
Enum(_) => gen_for_enum(&input.ident),
_ => abort_call_site!("#[derive(IntoApp)] only supports non-tuple structs and enums"),
}
}
pub fn gen_into_app_impl_for_struct(name: &syn::Ident, attrs: &[syn::Attribute]) -> GenOutput {
let into_app_fn = gen_into_app_fn_for_struct(attrs);
let into_app_fn_tokens = into_app_fn.tokens;
pub fn gen_for_struct(
struct_name: &syn::Ident,
fields: &Punctuated<syn::Field, Token![,]>,
attrs: &[syn::Attribute],
) -> GenOutput {
let (into_app, attrs) = gen_into_app_fn(attrs);
let augment_clap = gen_augment_clap_fn(fields, &attrs);
let tokens = quote! {
impl ::clap::IntoApp for #name {
#into_app_fn_tokens
impl ::clap::IntoApp for #struct_name {
#into_app
#augment_clap
}
};
impl<'b> Into<::clap::App<'b>> for #name {
fn into(self) -> ::clap::App<'b> {
use ::clap::IntoApp;
<#name as ::clap::IntoApp>::into_app()
(tokens, attrs)
}
pub fn gen_for_enum(name: &syn::Ident) -> TokenStream {
let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
quote! {
impl ::clap::IntoApp for #name {
fn into_app<'b>() -> ::clap::App<'b> {
let app = ::clap::App::new(#app_name)
.setting(::clap::AppSettings::SubcommandRequiredElseHelp);
<#name as ::clap::IntoApp>::augment_clap(app)
}
fn augment_clap<'b>(app: ::clap::App<'b>) -> ::clap::App<'b> {
<#name as ::clap::Subcommand>::augment_subcommands(app)
}
}
};
GenOutput {
tokens,
attrs: into_app_fn.attrs,
}
}
pub fn gen_into_app_fn_for_struct(struct_attrs: &[syn::Attribute]) -> GenOutput {
let gen = gen_app_builder(struct_attrs);
let app_tokens = gen.tokens;
let tokens = quote! {
fn into_app<'b>() -> ::clap::App<'b> {
Self::augment_app(#app_tokens)
}
};
GenOutput {
tokens,
attrs: gen.attrs,
}
}
pub fn gen_app_builder(attrs: &[syn::Attribute]) -> GenOutput {
let name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
fn gen_into_app_fn(attrs: &[syn::Attribute]) -> GenOutput {
let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
let attrs = Attrs::from_struct(
proc_macro2::Span::call_site(),
attrs,
Name::Assigned(quote!(#name)),
Name::Assigned(quote!(#app_name)),
Sp::call_site(DEFAULT_CASING),
Sp::call_site(DEFAULT_ENV_CASING),
);
let tokens = {
let name = attrs.cased_name();
quote!(::clap::App::new(#name))
};
GenOutput { tokens, attrs }
}
pub fn gen_into_app_impl_for_enum(name: &syn::Ident, attrs: &[syn::Attribute]) -> GenOutput {
let into_app_fn = gen_into_app_fn_for_enum(attrs);
let into_app_fn_tokens = into_app_fn.tokens;
let tokens = quote! {
impl ::clap::IntoApp for #name {
#into_app_fn_tokens
}
impl<'b> Into<::clap::App<'b>> for #name {
fn into(self) -> ::clap::App<'b> {
use ::clap::IntoApp;
<#name as ::clap::IntoApp>::into_app()
}
}
};
GenOutput {
tokens,
attrs: into_app_fn.attrs,
}
}
pub fn gen_into_app_fn_for_enum(enum_attrs: &[syn::Attribute]) -> GenOutput {
let gen = gen_app_builder(enum_attrs);
let app_tokens = gen.tokens;
let name = attrs.cased_name();
let tokens = quote! {
fn into_app<'b>() -> ::clap::App<'b> {
let app = #app_tokens
.setting(::clap::AppSettings::SubcommandRequiredElseHelp);
Self::augment_app(app)
Self::augment_clap(::clap::App::new(#name))
}
};
GenOutput {
tokens,
attrs: gen.attrs,
(tokens, attrs)
}
fn gen_augment_clap_fn(
fields: &Punctuated<syn::Field, Token![,]>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let app_var = syn::Ident::new("app", proc_macro2::Span::call_site());
let augmentation = gen_app_augmentation(fields, &app_var, parent_attribute);
quote! {
fn augment_clap<'b>(#app_var: ::clap::App<'b>) -> ::clap::App<'b> {
#augmentation
}
}
}
/// Generate a block of code to add arguments/subcommands corresponding to
/// the `fields` to an app.
pub fn gen_app_augmentation(
fields: &Punctuated<syn::Field, Token![,]>,
app_var: &syn::Ident,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let mut subcmds = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(
&field,
parent_attribute.casing(),
parent_attribute.env_casing(),
);
let kind = attrs.kind();
if let Kind::Subcommand(ty) = &*kind {
let subcmd_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
let required = if **ty == Ty::Option {
quote!()
} else {
quote_spanned! { kind.span()=>
let #app_var = #app_var.setting(
::clap::AppSettings::SubcommandRequiredElseHelp
);
}
};
let span = field.span();
let ts = quote! {
let #app_var = <#subcmd_type as ::clap::Subcommand>::augment_subcommands( #app_var );
#required
};
Some((span, ts))
} else {
None
}
});
let subcmd = subcmds.next().map(|(_, ts)| ts);
if let Some((span, _)) = subcmds.next() {
abort!(
span,
"multiple subcommand sets are not allowed, that's the second"
);
}
let args = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(
field,
parent_attribute.casing(),
parent_attribute.env_casing(),
);
let kind = attrs.kind();
match &*kind {
Kind::Subcommand(_) | Kind::Skip(_) => None,
Kind::Flatten => {
let ty = &field.ty;
Some(quote_spanned! { kind.span()=>
let #app_var = <#ty as ::clap::IntoApp>::augment_clap(#app_var);
})
}
Kind::Arg(ty) => {
let convert_type = match **ty {
Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty),
Ty::OptionOption | Ty::OptionVec => {
sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty)
}
_ => &field.ty,
};
let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
let flag = *attrs.parser().kind == ParserKind::FromFlag;
let parser = attrs.parser();
let func = &parser.func;
let validator = match *parser.kind {
ParserKind::TryFromStr => quote_spanned! { func.span()=>
.validator(|s| {
#func(s.as_str())
.map(|_: #convert_type| ())
.map_err(|e| e.to_string())
})
},
ParserKind::TryFromOsStr => quote_spanned! { func.span()=>
.validator_os(|s| #func(&s).map(|_: #convert_type| ()))
},
_ => quote!(),
};
let modifier = match **ty {
Ty::Bool => quote!(),
Ty::Option => quote_spanned! { ty.span()=>
.takes_value(true)
#validator
},
Ty::OptionOption => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple(false)
.min_values(0)
.max_values(1)
#validator
},
Ty::OptionVec => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple(true)
.min_values(0)
#validator
},
Ty::Vec => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple(true)
#validator
},
Ty::Other if occurrences => quote_spanned! { ty.span()=>
.multiple_occurrences(true)
},
Ty::Other if flag => quote_spanned! { ty.span()=>
.takes_value(false)
.multiple(false)
},
Ty::Other => {
let required = !attrs.has_method("default_value");
quote_spanned! { ty.span()=>
.takes_value(true)
.required(#required)
#validator
}
}
};
let name = attrs.cased_name();
let methods = attrs.field_methods();
Some(quote_spanned! { field.span()=>
let #app_var = #app_var.arg(
::clap::Arg::with_name(#name)
#modifier
#methods
);
})
}
}
});
let app_methods = parent_attribute.top_level_methods();
let version = parent_attribute.version();
quote! {{
let #app_var = #app_var#app_methods;
#( #args )*
#subcmd
#app_var#version
}}
}

View file

@ -19,6 +19,7 @@ mod from_argmatches;
mod into_app;
pub mod parse;
pub mod spanned;
mod subcommand;
pub mod ty;
// pub use self::arg_enum::derive_arg_enum;
@ -29,4 +30,5 @@ pub use self::attrs::{
pub use self::clap::derive_clap;
pub use self::from_argmatches::derive_from_argmatches;
pub use self::into_app::derive_into_app;
pub use self::subcommand::derive_subcommand;
pub use self::ty::{sub_type, Ty};

View file

@ -1,8 +1,9 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::ToTokens;
use std::ops::{Deref, DerefMut};
use syn::LitStr;
use std::ops::{Deref, DerefMut};
/// An entity with a span attached.
#[derive(Debug, Clone)]
pub struct Sp<T> {

View file

@ -0,0 +1,211 @@
use crate::derives::attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING};
use crate::derives::{from_argmatches, into_app, spanned::Sp};
use proc_macro2::{Ident, Span, TokenStream};
use proc_macro_error::{abort, abort_call_site, set_dummy};
use quote::{quote, quote_spanned};
use syn::{
punctuated::Punctuated, spanned::Spanned, Attribute, Data, DataEnum, FieldsUnnamed, Token,
Variant,
};
pub fn derive_subcommand(input: &syn::DeriveInput) -> TokenStream {
let name = &input.ident;
set_dummy(quote! {
impl ::clap::Subcommand for #name {
fn from_subcommand<'b>(
name: &str,
matches: ::std::option::Option<&::clap::ArgMatches>
) -> Option<Self> {
unimplemented!()
}
fn augment_subcommands<'b>(app: ::clap::App<'b>) -> ::clap::App<'b> {
unimplemented!()
}
}
});
match input.data {
Data::Enum(ref e) => gen_for_enum(name, &input.attrs, e),
_ => abort_call_site!(
"`#[derive(Subcommand)]` supports only enums";
hint = "use `#[derive(Clap)]` for structs";
),
}
}
pub fn gen_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_subcommand = gen_from_subcommand(name, &e.variants, &attrs);
let augment_subcommands = gen_augment_subcommands(&e.variants, &attrs);
quote! {
impl ::clap::Subcommand for #name {
#augment_subcommands
#from_subcommand
}
}
}
fn gen_augment_subcommands(
variants: &Punctuated<syn::Variant, Token![,]>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
use syn::Fields::*;
let subcommands = variants.iter().map(|variant| {
let attrs = Attrs::from_struct(
variant.span(),
&variant.attrs,
Name::Derived(variant.ident.clone()),
parent_attribute.casing(),
parent_attribute.env_casing(),
);
let kind = attrs.kind();
match &*kind {
Kind::Flatten => match variant.fields {
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0];
quote! {
let app = <#ty as ::clap::Subcommand>::augment_subcommands(app);
}
}
_ => abort!(
variant.span(),
"`flatten` is usable only with single-typed tuple variants"
),
},
_ => {
let app_var = Ident::new("subcommand", Span::call_site());
let arg_block = match variant.fields {
Named(ref fields) => {
into_app::gen_app_augmentation(&fields.named, &app_var, &attrs)
}
Unit => quote!( #app_var ),
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0];
quote_spanned! { ty.span()=>
{
<#ty as ::clap::IntoApp>::augment_clap(#app_var)
}
}
}
Unnamed(..) => abort!(
variant.span(),
"non single-typed tuple enums are not supported"
),
};
let name = attrs.cased_name();
let from_attrs = attrs.top_level_methods();
let version = attrs.version();
quote! {
let app = app.subcommand({
let #app_var = ::clap::App::new(#name);
let #app_var = #arg_block;
#app_var#from_attrs#version
});
}
}
}
});
let app_methods = parent_attribute.top_level_methods();
let version = parent_attribute.version();
quote! {
fn augment_subcommands<'b>(app: ::clap::App<'b>) -> ::clap::App<'b> {
let app = app #app_methods;
#( #subcommands )*;
app #version
}
}
}
fn gen_from_subcommand(
name: &syn::Ident,
variants: &Punctuated<Variant, Token![,]>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
use syn::Fields::*;
let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants
.iter()
.map(|variant| {
let attrs = Attrs::from_struct(
variant.span(),
&variant.attrs,
Name::Derived(variant.ident.clone()),
parent_attribute.casing(),
parent_attribute.env_casing(),
);
(variant, attrs)
})
.partition(|(_, attrs)| {
let kind = attrs.kind();
match &*kind {
Kind::Flatten => true,
_ => false,
}
});
let match_arms = variants.iter().map(|(variant, attrs)| {
let sub_name = attrs.cased_name();
let variant_name = &variant.ident;
let constructor_block = match variant.fields {
Named(ref fields) => from_argmatches::gen_constructor(&fields.named, &attrs),
Unit => quote!(),
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0];
quote!( ( <#ty as ::clap::FromArgMatches>::from_arg_matches(matches) ) )
}
Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
};
quote! {
(#sub_name, Some(matches)) => {
Some(#name :: #variant_name #constructor_block)
}
}
});
let child_subcommands = flatten_variants.iter().map(|(variant, _attrs)| {
let variant_name = &variant.ident;
match variant.fields {
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.0, other.1) {
return Some(#name :: #variant_name (res));
}
}
}
_ => abort!(
variant.span(),
"`flatten` is usable only with single-typed tuple variants"
),
}
});
quote! {
fn from_subcommand<'b>(
name: &'b str,
sub: Option<&'b ::clap::ArgMatches>) -> Option<Self>
{
match (name, sub) {
#( #match_arms ),*,
other => {
#( #child_subcommands )*;
None
}
}
}
}
}

View file

@ -15,12 +15,11 @@
//! This crate is custom derive for clap. It should not be used
//! directly. See [clap documentation](https://docs.rs/clap)
//! for the usage of `#[derive(Clap)]`.
#![recursion_limit = "256"]
extern crate proc_macro;
use proc_macro_error::proc_macro_error;
use syn;
use proc_macro_error::{proc_macro_error, set_dummy};
use quote::quote;
mod derives;
@ -35,7 +34,8 @@ mod derives;
#[proc_macro_derive(Clap, attributes(clap))]
#[proc_macro_error]
pub fn clap(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: syn::DeriveInput = syn::parse(input).unwrap();
let input: syn::DeriveInput = syn::parse_macro_input!(input);
set_dummy_clap_impl(&input.ident);
derives::derive_clap(&input).into()
}
@ -43,14 +43,45 @@ pub fn clap(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
#[proc_macro_derive(IntoApp, attributes(clap))]
#[proc_macro_error]
pub fn into_app(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: syn::DeriveInput = syn::parse(input).unwrap();
let input: syn::DeriveInput = syn::parse_macro_input!(input);
set_dummy_clap_impl(&input.ident);
derives::derive_into_app(&input).into()
}
/// Generates the `FromArgMatches` impl.
#[proc_macro_derive(FromArgMatches)]
#[proc_macro_derive(FromArgMatches, attributes(clap))]
#[proc_macro_error]
pub fn from_argmatches(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: syn::DeriveInput = syn::parse(input).unwrap();
pub fn from_arg_matches(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: syn::DeriveInput = syn::parse_macro_input!(input);
set_dummy_clap_impl(&input.ident);
derives::derive_from_argmatches(&input).into()
}
/// Generates the `Subcommand` impl.
#[proc_macro_derive(Subcommand, attributes(clap))]
#[proc_macro_error]
pub fn subcommand(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: syn::DeriveInput = syn::parse_macro_input!(input);
derives::derive_subcommand(&input).into()
}
fn set_dummy_clap_impl(struct_name: &syn::Ident) {
set_dummy(quote! {
impl ::clap::Clap for #struct_name {}
impl ::clap::IntoApp for #struct_name {
fn into_app<'b>() -> ::clap::App<'b> {
unimplemented!()
}
fn augment_clap<'b>(app: ::clap::App<'b>) -> ::clap::App<'b> {
unimplemented!()
}
}
impl ::clap::FromArgMatches for #struct_name {
fn from_arg_matches(m: &::clap::ArgMatches) -> Self {
unimplemented!()
}
}
});
}

View file

@ -22,7 +22,7 @@ fn try_str(s: &str) -> Result<String, std::convert::Infallible> {
#[test]
fn warning_never_struct() {
#[derive(Debug, PartialEq, Clap)]
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(parse(try_from_str = try_str))]
s: String,
@ -37,7 +37,7 @@ fn warning_never_struct() {
#[test]
fn warning_never_enum() {
#[derive(Debug, PartialEq, Clap)]
#[derive(Clap, Debug, PartialEq)]
enum Opt {
Foo {
#[clap(parse(try_from_str = try_str))]

View file

@ -3,7 +3,7 @@
mod utils;
use utils::*;
use clap::{ArgGroup, Clap};
use clap::{AppSettings, ArgGroup, Clap};
#[test]
fn issue_151() {
@ -31,8 +31,6 @@ fn issue_151() {
#[test]
fn issue_289() {
use clap::{AppSettings, Clap};
#[derive(Clap)]
#[clap(setting = AppSettings::InferSubcommands)]
enum Args {
@ -40,6 +38,8 @@ fn issue_289() {
AnotherCommand,
}
// FIXME (@CreepySkeleton): current implementation requires us to
// derive IntoApp here while we don't really need it
#[derive(Clap)]
#[clap(setting = AppSettings::InferSubcommands)]
enum SubSubCommand {

View file

@ -23,9 +23,9 @@ mod options {
}
mod subcommands {
use clap::Clap;
use clap::Subcommand;
#[derive(Debug, Clap)]
#[derive(Debug, Subcommand)]
pub enum SubCommand {
/// foo
Foo {

View file

@ -161,47 +161,52 @@ fn test_tuple_commands() {
assert!(!output.contains("Not shown"));
}
#[test]
fn enum_in_enum_subsubcommand() {
#[derive(Clap, Debug, PartialEq)]
pub enum Opt {
Daemon(DaemonCommand),
}
// #[test]
// #[ignore] // FIXME (@CreepySkeleton)
// fn enum_in_enum_subsubcommand() {
// #[derive(Clap, Debug, PartialEq)]
// pub enum Opt {
// Daemon(DaemonCommand),
// }
#[derive(Clap, Debug, PartialEq)]
pub enum DaemonCommand {
Start,
Stop,
}
// #[derive(Clap, Debug, PartialEq)]
// pub enum DaemonCommand {
// Start,
// Stop,
// }
let result = Opt::try_parse_from(&["test"]);
assert!(result.is_err());
// let result = Opt::try_parse_from(&["test"]);
// assert!(result.is_err());
let result = Opt::try_parse_from(&["test", "daemon"]);
assert!(result.is_err());
// let result = Opt::try_parse_from(&["test", "daemon"]);
// assert!(result.is_err());
let result = Opt::parse_from(&["test", "daemon", "start"]);
assert_eq!(Opt::Daemon(DaemonCommand::Start), result);
}
// let result = Opt::parse_from(&["test", "daemon", "start"]);
// assert_eq!(Opt::Daemon(DaemonCommand::Start), result);
// }
#[test]
fn flatten_enum() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(flatten)]
sub_cmd: SubCmd,
}
#[derive(Clap, Debug, PartialEq)]
enum SubCmd {
Foo,
Bar,
}
// WTF??? It tests that `flatten` is essentially a synonym
// for `subcommand`. It's not documented and I doubt it was supposed to
// work this way.
// #[test]
// #[ignore]
// fn flatten_enum() {
// #[derive(Clap, Debug, PartialEq)]
// struct Opt {
// #[clap(flatten)]
// sub_cmd: SubCmd,
// }
// #[derive(Clap, Debug, PartialEq)]
// enum SubCmd {
// Foo,
// Bar,
// }
assert!(Opt::try_parse_from(&["test"]).is_err());
assert_eq!(
Opt::parse_from(&["test", "foo"]),
Opt {
sub_cmd: SubCmd::Foo
}
);
}
// assert!(Opt::try_parse_from(&["test"]).is_err());
// assert_eq!(
// Opt::parse_from(&["test", "foo"]),
// Opt {
// sub_cmd: SubCmd::Foo
// }
// );
// }

View file

@ -3,9 +3,3 @@ error: default_value is meaningless for bool
|
14 | #[clap(short, default_value = true)]
| ^^^^^^^^^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/bool_default_value.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: required is meaningless for bool
|
14 | #[clap(short, required = true)]
| ^^^^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/bool_required.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -7,10 +7,10 @@ struct Opt {}
#[derive(Clap, Debug)]
struct Opt1 {
#[clap = "short"]
foo: u32
foo: u32,
}
fn main() {
let opt = Opt::from_args();
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -9,15 +9,3 @@ error: expected parentheses: #[clap(...)]
|
9 | #[clap = "short"]
| ^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/clap_empty_attr.rs:3:10
|
3 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`
error[E0277]: the trait bound `Opt1: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/clap_empty_attr.rs:7:10
|
7 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt1`

View file

@ -16,6 +16,6 @@ enum Opt {
}
fn main() {
let opt = Opt::from_args();
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -3,9 +3,3 @@ error: `flatten` is usable only with single-typed tuple variants
|
14 | #[clap(flatten)]
| ^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/enum_flatten.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: methods and doc comments are not allowed for flattened entry
|
23 | #[clap(flatten)]
| ^^^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/flatten_and_doc.rs:19:10
|
19 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: methods and doc comments are not allowed for flattened entry
|
22 | #[clap(short, flatten)]
| ^^^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/flatten_and_methods.rs:19:10
|
19 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: parse attribute is not allowed for flattened entry
|
22 | #[clap(flatten, parse(from_occurrences))]
| ^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/flatten_and_parse.rs:19:10
|
19 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: Option<Option<T>> type is meaningless for positional argument
|
14 | n: Option<Option<u32>>,
| ^^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/opt_opt_nonpositional.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: Option<Vec<T>> type is meaningless for positional argument
|
14 | n: Option<Vec<u32>>,
| ^^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/opt_vec_nonpositional.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: default_value is meaningless for Option
|
14 | #[clap(short, default_value = 1)]
| ^^^^^^^^^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/option_default_value.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: required is meaningless for Option
|
14 | #[clap(short, required = true)]
| ^^^^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/option_required.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: you must set parser for `try_from_os_str` explicitly
|
14 | #[clap(parse(try_from_os_str))]
| ^^^^^^^^^^^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/parse_empty_try_from_os.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: `parse` argument must be a function path
|
14 | #[clap(parse(from_str = "2"))]
| ^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/parse_function_is_not_path.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: parser specification must start with identifier
|
14 | #[clap(parse("from_str"))]
| ^^^^^^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/parse_literal_spec.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: parse must have exactly one argument
|
14 | #[clap(parse(from_str, from_str))]
| ^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/parse_not_zero_args.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -8,9 +8,3 @@ error: `bool` cannot be used as positional parameter with default parser
|
5 | verbose: bool,
| ^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/positional_bool.rs:3:10
|
3 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -17,15 +17,3 @@ error: `#[clap(raw(...))` attributes are removed, they are replaced with raw met
|
19 | #[clap(raw(requires_if = r#""one", "two""#))]
| ^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/raw.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`
error[E0277]: the trait bound `Opt2: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/raw.rs:17:10
|
17 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt2`

View file

@ -3,9 +3,3 @@ error: unsupported casing: `fail`
|
12 | #[clap(name = "basic", rename_all = "fail")]
| ^^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/rename_all_wrong_casing.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: subcommand, flatten and skip cannot be used together
|
17 | #[clap(skip, flatten)]
| ^^^^^^^
error[E0277]: the trait bound `MakeCookie: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/skip_flatten.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `MakeCookie`

View file

@ -3,9 +3,3 @@ error: subcommand, flatten and skip cannot be used together
|
17 | #[clap(subcommand, skip)]
| ^^^^
error[E0277]: the trait bound `MakeCookie: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/skip_subcommand.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `MakeCookie`

View file

@ -3,9 +3,3 @@ error: methods are not allowed for skipped fields
|
8 | #[clap(skip, long)]
| ^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/skip_with_other_options.rs:3:10
|
3 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: `parse` attribute is only allowed on fields
|
12 | #[clap(name = "basic", parse(from_str))]
| ^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/struct_parse.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: subcommand is only allowed on fields
|
12 | #[clap(name = "basic", subcommand)]
| ^^^^^^^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/struct_subcommand.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -3,9 +3,3 @@ error: subcommand, flatten and skip cannot be used together
|
16 | #[clap(subcommand, flatten)]
| ^^^^^^^
error[E0277]: the trait bound `MakeCookie: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/subcommand_and_flatten.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `MakeCookie`

View file

@ -3,9 +3,3 @@ error: methods in attributes are not allowed for subcommand
|
16 | #[clap(subcommand, long)]
| ^^^^^^^^^^
error[E0277]: the trait bound `MakeCookie: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/subcommand_and_methods.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `MakeCookie`

View file

@ -3,9 +3,3 @@ error: parse attribute is not allowed for subcommand
|
16 | #[clap(subcommand, parse(from_occurrences))]
| ^^^^^
error[E0277]: the trait bound `MakeCookie: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/subcommand_and_parse.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `MakeCookie`

View file

@ -3,9 +3,3 @@ error: Option<Option<T>> type is not allowed for subcommand
|
17 | cmd: Option<Option<Command>>,
| ^^^^^^
error[E0277]: the trait bound `MakeCookie: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/subcommand_opt_opt.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `MakeCookie`

View file

@ -3,9 +3,3 @@ error: Option<Vec<T>> type is not allowed for subcommand
|
17 | cmd: Option<Vec<Command>>,
| ^^^^^^
error[E0277]: the trait bound `MakeCookie: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/subcommand_opt_vec.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `MakeCookie`

View file

@ -1,11 +1,5 @@
error: clap_derive only supports non-tuple structs and enums
error: `#[derive(Clap)]` only supports non-tuple structs and enums
--> $DIR/tuple_struct.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^
error[E0277]: the trait bound `Opt: std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not satisfied
--> $DIR/tuple_struct.rs:11:10
|
11 | #[derive(Clap, Debug)]
| ^^^^ the trait `std::convert::From<clap::parse::matches::arg_matches::ArgMatches>` is not implemented for `Opt`

View file

@ -1,3 +1,10 @@
// Hi, future me (or whoever you are)!
//
// Yes, we do need this attr.
// No, the warnings cannot be fixed otherwise.
// Accept and endure. Do not touch.
#![allow(unused)]
use clap::IntoApp;
pub fn get_help<T: IntoApp>() -> String {

68
src/derive.rs Normal file
View file

@ -0,0 +1,68 @@
//! This module contains traits that are usable with `#[derive(...)].`
use crate::{App, ArgMatches, Error};
use std::ffi::OsString;
/// This trait is just a convenience on top of FromArgMatches + IntoApp
pub trait Clap: FromArgMatches + IntoApp + Sized {
/// Parse from `std::env::args()`, exit on error
fn parse() -> Self {
let matches = <Self as IntoApp>::into_app().get_matches();
<Self as FromArgMatches>::from_arg_matches(&matches)
}
/// Parse from `std::env::args()`, 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))
}
/// Parse from iterator, exit on error
fn parse_from<I, T>(itr: I) -> Self
where
I: IntoIterator<Item = T>,
// TODO (@CreepySkeleton): discover a way to avoid cloning here
T: Into<OsString> + Clone,
{
let matches = <Self as IntoApp>::into_app().get_matches_from(itr);
<Self as FromArgMatches>::from_arg_matches(&matches)
}
/// Parse from `std::env::args()`, return Err on error.
fn try_parse_from<I, T>(itr: I) -> Result<Self, Error>
where
I: IntoIterator<Item = T>,
// TODO (@CreepySkeleton): discover a way to avoid cloning here
T: Into<OsString> + Clone,
{
let matches = <Self as IntoApp>::into_app().try_get_matches_from(itr)?;
Ok(<Self as FromArgMatches>::from_arg_matches(&matches))
}
}
/// Build an App according to the struct
///
/// Also serves for flattening
pub trait IntoApp: Sized {
/// @TODO @release @docs
fn into_app<'b>() -> App<'b>;
/// @TODO @release @docs
fn augment_clap(app: App<'_>) -> App<'_>;
}
/// Extract values from ArgMatches into the struct.
pub trait FromArgMatches: Sized {
/// @TODO @release @docs
fn from_arg_matches(matches: &ArgMatches) -> Self;
}
/// @TODO @release @docs
pub trait Subcommand: Sized {
/// @TODO @release @docs
fn from_subcommand(name: &str, matches: Option<&ArgMatches>) -> Option<Self>;
/// @TODO @release @docs
fn augment_subcommands(app: App<'_>) -> App<'_>;
}
/// @TODO @release @docs
pub trait ArgEnum {}

View file

@ -448,6 +448,7 @@
compile_error!("`std` feature is currently required to build this crate");
pub use crate::build::{App, AppSettings, Arg, ArgGroup, ArgSettings, Propagation};
pub use crate::derive::{Clap, FromArgMatches, IntoApp, Subcommand};
pub use crate::output::fmt::Format;
pub use crate::parse::errors::{Error, ErrorKind, Result};
pub use crate::parse::{ArgMatches, OsValues, SubCommand, Values};
@ -462,46 +463,21 @@ pub use clap_derive::{self, *};
#[cfg_attr(feature = "derive", doc(hidden))]
pub use lazy_static;
use std::result::Result as StdResult;
#[doc(hidden)]
pub use mkeymap::KeyType;
#[macro_use]
#[allow(missing_docs)]
pub mod macros;
pub mod derive;
mod build;
mod mkeymap;
mod output;
mod parse;
mod util;
#[doc(hidden)]
pub use mkeymap::KeyType;
const INTERNAL_ERROR_MSG: &str = "Fatal internal error. Please consider filing a bug \
report at https://github.com/clap-rs/clap/issues";
const INVALID_UTF8: &str = "unexpected invalid UTF-8 code point";
/// @TODO @release @docs
pub trait Clap: From<ArgMatches> + IntoApp + Sized {}
/// @TODO @release @docs
pub trait FromArgMatches: Sized {
/// @TODO @release @docs
fn from_argmatches(matches: &crate::parse::ArgMatches) -> Self;
/// @TODO @release @docs
fn try_from_argmatches(
matches: &crate::parse::ArgMatches,
) -> StdResult<Self, crate::parse::errors::Error> {
Ok(<Self as FromArgMatches>::from_argmatches(matches))
}
}
/// @TODO @release @docs
pub trait IntoApp: Sized {
/// @TODO @release @docs
fn into_app<'b>() -> crate::build::App<'b>;
}
/// @TODO @release @docs
pub trait ArgEnum {}

View file

@ -22,6 +22,7 @@ pub trait OsStrExt2 {
#[cfg(target_os = "windows")]
impl OsStrExt3 for OsStr {
#[allow(clippy::transmute_ptr_to_ptr)]
fn from_bytes(b: &[u8]) -> &Self {
use std::mem;
unsafe { mem::transmute(b) }