mirror of
https://github.com/clap-rs/clap
synced 2024-11-10 06:44:16 +00:00
Extract subcommands into separate trait
This commit is contained in:
parent
ae2e00f418
commit
ae574df2f9
48 changed files with 689 additions and 895 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -28,3 +28,5 @@ clap-rs.iml
|
|||
|
||||
# Auxiliary files
|
||||
test-results.test
|
||||
expanded.rs
|
||||
clap_derive/expanded.rs
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}}
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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> {
|
||||
|
|
211
clap_derive/src/derives/subcommand.rs
Normal file
211
clap_derive/src/derives/subcommand.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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))]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -16,6 +16,6 @@ enum Opt {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let opt = Opt::from_args();
|
||||
let opt = Opt::parse();
|
||||
println!("{:?}", opt);
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
68
src/derive.rs
Normal 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 {}
|
34
src/lib.rs
34
src/lib.rs
|
@ -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 {}
|
||||
|
|
|
@ -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) }
|
||||
|
|
Loading…
Reference in a new issue