mirror of
https://github.com/clap-rs/clap
synced 2024-12-15 15:22:30 +00:00
refactor: Begins the rename process
This commit is contained in:
parent
129418e846
commit
f8d4bf3c97
7 changed files with 537 additions and 559 deletions
|
@ -7,30 +7,29 @@
|
|||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
use errors::*;
|
||||
use helpers;
|
||||
use quote::Tokens;
|
||||
use syn::DeriveInput;
|
||||
use ClapDerive;
|
||||
use proc_macro2;
|
||||
use quote;
|
||||
use syn;
|
||||
use syn::punctuated;
|
||||
use syn::token;
|
||||
|
||||
pub struct ArgEnum;
|
||||
pub fn derive_arg_enum(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
||||
unimplemented!()
|
||||
|
||||
impl ClapDerive for ArgEnum {
|
||||
fn generate_from(ast: &DeriveInput) -> Result<Tokens> {
|
||||
let from_str_block = impl_from_str(ast)?;
|
||||
let variants_block = impl_variants(ast)?;
|
||||
// let from_str_block = impl_from_str(ast)?;
|
||||
// let variants_block = impl_variants(ast)?;
|
||||
|
||||
Ok(quote! {
|
||||
#from_str_block
|
||||
#variants_block
|
||||
})
|
||||
}
|
||||
// quote! {
|
||||
// #from_str_block
|
||||
// #variants_block
|
||||
// }
|
||||
}
|
||||
|
||||
fn impl_from_str(ast: &DeriveInput) -> Result<Tokens> {
|
||||
/*
|
||||
fn impl_from_str(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
||||
let ident = &ast.ident;
|
||||
let is_case_sensitive = ast.attrs.iter().any(|v| v.name() == "case_sensitive");
|
||||
let variants = helpers::variants(ast)?;
|
||||
let variants = variants(ast)?;
|
||||
|
||||
let strings = variants
|
||||
.iter()
|
||||
|
@ -67,9 +66,9 @@ fn impl_from_str(ast: &DeriveInput) -> Result<Tokens> {
|
|||
})
|
||||
}
|
||||
|
||||
fn impl_variants(ast: &DeriveInput) -> Result<Tokens> {
|
||||
fn impl_variants(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
||||
let ident = &ast.ident;
|
||||
let variants = helpers::variants(ast)?
|
||||
let variants = variants(ast)?
|
||||
.iter()
|
||||
.map(|ref variant| String::from(variant.ident.as_ref()))
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -83,3 +82,13 @@ fn impl_variants(ast: &DeriveInput) -> Result<Tokens> {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn variants(ast: &syn::DeriveInput) -> &punctuated::Punctuated<syn::Variant, token::Comma> {
|
||||
use syn::Data::*;
|
||||
|
||||
match ast.data {
|
||||
Enum(ref data) => data.variants,
|
||||
_ => panic!("Only enums are supported for deriving the ArgEnum trait"),
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -12,10 +12,9 @@
|
|||
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
|
||||
// MIT/Apache 2.0 license.
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use proc_macro2;
|
||||
use std::{env, mem};
|
||||
use syn::Type::Path;
|
||||
use syn::{self, Attribute, Ident, LitStr, MetaList, MetaNameValue, TypePath};
|
||||
use syn;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum Kind {
|
||||
|
@ -34,14 +33,14 @@ pub enum Ty {
|
|||
pub struct Attrs {
|
||||
name: String,
|
||||
methods: Vec<Method>,
|
||||
parser: (Parser, TokenStream),
|
||||
parser: (Parser, proc_macro2::TokenStream),
|
||||
has_custom_parser: bool,
|
||||
kind: Kind,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct Method {
|
||||
name: String,
|
||||
args: TokenStream,
|
||||
args: proc_macro2::TokenStream,
|
||||
}
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Parser {
|
||||
|
@ -88,10 +87,10 @@ impl Attrs {
|
|||
}),
|
||||
}
|
||||
}
|
||||
fn push_attrs(&mut self, attrs: &[Attribute]) {
|
||||
use Lit::*;
|
||||
use Meta::*;
|
||||
use NestedMeta::*;
|
||||
fn push_attrs(&mut self, attrs: &[syn::Attribute]) {
|
||||
use syn::Lit::*;
|
||||
use syn::Meta::*;
|
||||
use syn::NestedMeta::*;
|
||||
|
||||
let iter = attrs
|
||||
.iter()
|
||||
|
@ -115,16 +114,16 @@ impl Attrs {
|
|||
});
|
||||
for attr in iter {
|
||||
match attr {
|
||||
NameValue(MetaNameValue {
|
||||
NameValue(syn::MetaNameValue {
|
||||
ident,
|
||||
lit: Str(value),
|
||||
..
|
||||
}) => self.push_str_method(&ident.to_string(), &value.value()),
|
||||
NameValue(MetaNameValue { ident, lit, .. }) => self.methods.push(Method {
|
||||
NameValue(syn::MetaNameValue { ident, lit, .. }) => self.methods.push(Method {
|
||||
name: ident.to_string(),
|
||||
args: quote!(#lit),
|
||||
}),
|
||||
List(MetaList {
|
||||
List(syn::MetaList {
|
||||
ref ident,
|
||||
ref nested,
|
||||
..
|
||||
|
@ -135,7 +134,7 @@ impl Attrs {
|
|||
}
|
||||
self.has_custom_parser = true;
|
||||
self.parser = match nested[0] {
|
||||
Meta(NameValue(MetaNameValue {
|
||||
Meta(NameValue(syn::MetaNameValue {
|
||||
ref ident,
|
||||
lit: Str(ref v),
|
||||
..
|
||||
|
@ -145,7 +144,7 @@ impl Attrs {
|
|||
(parser, quote!(#function))
|
||||
}
|
||||
Meta(Word(ref i)) => {
|
||||
use Parser::*;
|
||||
use self::Parser::*;
|
||||
let parser = i.to_string().parse().unwrap();
|
||||
let function = match parser {
|
||||
FromStr => quote!(::std::convert::From::from),
|
||||
|
@ -161,7 +160,7 @@ impl Attrs {
|
|||
ref l @ _ => panic!("unknown value parser specification: {}", quote!(#l)),
|
||||
};
|
||||
}
|
||||
List(MetaList {
|
||||
List(syn::MetaList {
|
||||
ref ident,
|
||||
ref nested,
|
||||
..
|
||||
|
@ -169,7 +168,7 @@ impl Attrs {
|
|||
{
|
||||
for method in nested {
|
||||
match *method {
|
||||
Meta(NameValue(MetaNameValue {
|
||||
Meta(NameValue(syn::MetaNameValue {
|
||||
ref ident,
|
||||
lit: Str(ref v),
|
||||
..
|
||||
|
@ -188,8 +187,8 @@ impl Attrs {
|
|||
}
|
||||
}
|
||||
}
|
||||
fn push_raw_method(&mut self, name: &str, args: &LitStr) {
|
||||
let ts: TokenStream = args.value().parse().expect(&format!(
|
||||
fn push_raw_method(&mut self, name: &str, args: &syn::LitStr) {
|
||||
let ts: proc_macro2::TokenStream = args.value().parse().expect(&format!(
|
||||
"bad parameter {} = {}: the parameter must be valid rust code",
|
||||
name,
|
||||
quote!(#args)
|
||||
|
@ -199,7 +198,7 @@ impl Attrs {
|
|||
args: quote!(#(#ts)*),
|
||||
})
|
||||
}
|
||||
fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) {
|
||||
fn push_doc_comment(&mut self, attrs: &[syn::Attribute], name: &str) {
|
||||
let doc_comments: Vec<_> = attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
|
@ -210,9 +209,9 @@ impl Attrs {
|
|||
}
|
||||
})
|
||||
.filter_map(|attr| {
|
||||
use Lit::*;
|
||||
use Meta::*;
|
||||
if let NameValue(MetaNameValue {
|
||||
use syn::Lit::*;
|
||||
use syn::Meta::*;
|
||||
if let NameValue(syn::MetaNameValue {
|
||||
ident, lit: Str(s), ..
|
||||
}) = attr
|
||||
{
|
||||
|
@ -251,7 +250,7 @@ impl Attrs {
|
|||
args: quote!(#arg),
|
||||
});
|
||||
}
|
||||
pub fn from_struct(attrs: &[Attribute], name: String) -> Attrs {
|
||||
pub fn from_struct(attrs: &[syn::Attribute], name: String) -> Attrs {
|
||||
let mut res = Self::new(name);
|
||||
let attrs_with_env = [
|
||||
("version", "CARGO_PKG_VERSION"),
|
||||
|
@ -282,7 +281,7 @@ impl Attrs {
|
|||
}
|
||||
}
|
||||
fn ty_from_field(ty: &syn::Type) -> Ty {
|
||||
if let Path(TypePath {
|
||||
if let syn::Type::Path(syn::TypePath {
|
||||
path: syn::Path { ref segments, .. },
|
||||
..
|
||||
}) = *ty
|
||||
|
@ -364,14 +363,14 @@ impl Attrs {
|
|||
pub fn has_method(&self, method: &str) -> bool {
|
||||
self.methods.iter().find(|m| m.name == method).is_some()
|
||||
}
|
||||
pub fn methods(&self) -> TokenStream {
|
||||
pub fn methods(&self) -> proc_macro2::TokenStream {
|
||||
let methods = self.methods.iter().map(|&Method { ref name, ref args }| {
|
||||
let name = Ident::new(&name, Span::call_site());
|
||||
let name = syn::Ident::new(&name, proc_macro2::Span::call_site());
|
||||
quote!( .#name(#args) )
|
||||
});
|
||||
quote!( #(#methods)* )
|
||||
}
|
||||
pub fn name(&self) -> &str { &self.name }
|
||||
pub fn parser(&self) -> &(Parser, TokenStream) { &self.parser }
|
||||
pub fn parser(&self) -> &(Parser, proc_macro2::TokenStream) { &self.parser }
|
||||
pub fn kind(&self) -> Kind { self.kind }
|
||||
}
|
422
src/derives/clap.rs
Normal file
422
src/derives/clap.rs
Normal file
|
@ -0,0 +1,422 @@
|
|||
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
|
||||
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
|
||||
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
//
|
||||
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
|
||||
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
|
||||
// MIT/Apache 2.0 license.
|
||||
use std::env;
|
||||
|
||||
use proc_macro2;
|
||||
use syn;
|
||||
use syn::punctuated;
|
||||
use syn::token;
|
||||
|
||||
use derives;
|
||||
use derives::attrs::{Attrs, Kind, Parser, Ty};
|
||||
|
||||
/// Generate a block of code to add arguments/subcommands corresponding to
|
||||
/// the `fields` to an app.
|
||||
fn gen_augmentation(
|
||||
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
|
||||
app_var: &syn::Ident,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let subcmds: Vec<_> = fields
|
||||
.iter()
|
||||
.filter_map(|field| {
|
||||
let attrs = Attrs::from_field(&field);
|
||||
if let Kind::Subcommand(ty) = attrs.kind() {
|
||||
let subcmd_type = match (ty, derives::sub_type(&field.ty)) {
|
||||
(Ty::Option, Some(sub_type)) => sub_type,
|
||||
_ => &field.ty,
|
||||
};
|
||||
let required = if ty == Ty::Option {
|
||||
quote!()
|
||||
} else {
|
||||
quote! {
|
||||
let #app_var = #app_var.setting(
|
||||
::structopt::clap::AppSettings::SubcommandRequiredElseHelp
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Some(quote!{
|
||||
let #app_var = <#subcmd_type>::augment_clap( #app_var );
|
||||
#required
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
subcmds.len() <= 1,
|
||||
"cannot have more than one nested subcommand"
|
||||
);
|
||||
|
||||
let args = fields.iter().filter_map(|field| {
|
||||
let attrs = Attrs::from_field(field);
|
||||
match attrs.kind() {
|
||||
Kind::Subcommand(_) => None,
|
||||
Kind::FlattenStruct => {
|
||||
let ty = &field.ty;
|
||||
Some(quote! {
|
||||
let #app_var = <#ty>::augment_clap(#app_var);
|
||||
let #app_var = if <#ty>::is_subcommand() {
|
||||
#app_var.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp)
|
||||
} else {
|
||||
#app_var
|
||||
};
|
||||
})
|
||||
}
|
||||
Kind::Arg(ty) => {
|
||||
let convert_type = match ty {
|
||||
Ty::Vec | Ty::Option => derives::sub_type(&field.ty).unwrap_or(&field.ty),
|
||||
_ => &field.ty,
|
||||
};
|
||||
|
||||
let occurences = attrs.parser().0 == Parser::FromOccurrences;
|
||||
|
||||
let validator = match *attrs.parser() {
|
||||
(Parser::TryFromStr, ref f) => quote! {
|
||||
.validator(|s| {
|
||||
#f(&s)
|
||||
.map(|_: #convert_type| ())
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
},
|
||||
(Parser::TryFromOsStr, ref f) => quote! {
|
||||
.validator_os(|s| #f(&s).map(|_: #convert_type| ()))
|
||||
},
|
||||
_ => quote!(),
|
||||
};
|
||||
|
||||
let modifier = match ty {
|
||||
Ty::Bool => quote!( .takes_value(false).multiple(false) ),
|
||||
Ty::Option => quote!( .takes_value(true).multiple(false) #validator ),
|
||||
Ty::Vec => quote!( .takes_value(true).multiple(true) #validator ),
|
||||
Ty::Other if occurences => quote!( .takes_value(false).multiple(true) ),
|
||||
Ty::Other => {
|
||||
let required = !attrs.has_method("default_value");
|
||||
quote!( .takes_value(true).multiple(false).required(#required) #validator )
|
||||
}
|
||||
};
|
||||
let methods = attrs.methods();
|
||||
let name = attrs.name();
|
||||
Some(quote!{
|
||||
let #app_var = #app_var.arg(
|
||||
::structopt::clap::Arg::with_name(#name)
|
||||
#modifier
|
||||
#methods
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {{
|
||||
#( #args )*
|
||||
#( #subcmds )*
|
||||
#app_var
|
||||
}}
|
||||
}
|
||||
|
||||
fn gen_constructor(
|
||||
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let fields = fields.iter().map(|field| {
|
||||
let attrs = Attrs::from_field(field);
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
match attrs.kind() {
|
||||
Kind::Subcommand(ty) => {
|
||||
let subcmd_type = match (ty, derives::sub_type(&field.ty)) {
|
||||
(Ty::Option, Some(sub_type)) => sub_type,
|
||||
_ => &field.ty,
|
||||
};
|
||||
let unwrapper = match ty {
|
||||
Ty::Option => quote!(),
|
||||
_ => quote!( .unwrap() ),
|
||||
};
|
||||
quote!(#field_name: <#subcmd_type>::from_subcommand(matches.subcommand())#unwrapper)
|
||||
}
|
||||
Kind::FlattenStruct => quote!(#field_name: ::structopt::StructOpt::from_clap(matches)),
|
||||
Kind::Arg(ty) => {
|
||||
use self::Parser::*;
|
||||
let (value_of, values_of, parse) = match *attrs.parser() {
|
||||
(FromStr, ref f) => (quote!(value_of), quote!(values_of), f.clone()),
|
||||
(TryFromStr, ref f) => (
|
||||
quote!(value_of),
|
||||
quote!(values_of),
|
||||
quote!(|s| #f(s).unwrap()),
|
||||
),
|
||||
(FromOsStr, ref f) => (quote!(value_of_os), quote!(values_of_os), f.clone()),
|
||||
(TryFromOsStr, ref f) => (
|
||||
quote!(value_of_os),
|
||||
quote!(values_of_os),
|
||||
quote!(|s| #f(s).unwrap()),
|
||||
),
|
||||
(FromOccurrences, ref f) => (quote!(occurrences_of), quote!(), f.clone()),
|
||||
};
|
||||
|
||||
let occurences = attrs.parser().0 == Parser::FromOccurrences;
|
||||
let name = attrs.name();
|
||||
let field_value = match ty {
|
||||
Ty::Bool => quote!(matches.is_present(#name)),
|
||||
Ty::Option => quote! {
|
||||
matches.#value_of(#name)
|
||||
.as_ref()
|
||||
.map(#parse)
|
||||
},
|
||||
Ty::Vec => quote! {
|
||||
matches.#values_of(#name)
|
||||
.map(|v| v.map(#parse).collect())
|
||||
.unwrap_or_else(Vec::new)
|
||||
},
|
||||
Ty::Other if occurences => quote! {
|
||||
#parse(matches.#value_of(#name))
|
||||
},
|
||||
Ty::Other => quote! {
|
||||
matches.#value_of(#name)
|
||||
.map(#parse)
|
||||
.unwrap()
|
||||
},
|
||||
};
|
||||
|
||||
quote!( #field_name: #field_value )
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {{
|
||||
#( #fields ),*
|
||||
}}
|
||||
}
|
||||
|
||||
fn gen_from_clap(
|
||||
struct_name: &syn::Ident,
|
||||
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let field_block = gen_constructor(fields);
|
||||
|
||||
quote! {
|
||||
fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
|
||||
#struct_name #field_block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_clap(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
|
||||
let name = env::var("CARGO_PKG_NAME")
|
||||
.ok()
|
||||
.unwrap_or_else(String::default);
|
||||
let attrs = Attrs::from_struct(attrs, name);
|
||||
let name = attrs.name();
|
||||
let methods = attrs.methods();
|
||||
quote!(::structopt::clap::App::new(#name)#methods)
|
||||
}
|
||||
|
||||
fn gen_clap_struct(struct_attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
|
||||
let gen = gen_clap(struct_attrs);
|
||||
quote! {
|
||||
fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> {
|
||||
let app = #gen;
|
||||
Self::augment_clap(app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_augment_clap(
|
||||
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let app_var = syn::Ident::new("app", proc_macro2::Span::call_site());
|
||||
let augmentation = gen_augmentation(fields, &app_var);
|
||||
quote! {
|
||||
pub fn augment_clap<'a, 'b>(
|
||||
#app_var: ::structopt::clap::App<'a, 'b>
|
||||
) -> ::structopt::clap::App<'a, 'b> {
|
||||
#augmentation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_clap_enum(enum_attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
|
||||
let gen = gen_clap(enum_attrs);
|
||||
quote! {
|
||||
fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> {
|
||||
let app = #gen
|
||||
.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp);
|
||||
Self::augment_clap(app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_augment_clap_enum(
|
||||
variants: &punctuated::Punctuated<syn::Variant, token::Comma>,
|
||||
) -> proc_macro2::TokenStream {
|
||||
use syn::Fields::*;
|
||||
|
||||
let subcommands = variants.iter().map(|variant| {
|
||||
let name = variant.ident.to_string();
|
||||
let attrs = Attrs::from_struct(&variant.attrs, name);
|
||||
let app_var = syn::Ident::new("subcommand", proc_macro2::Span::call_site());
|
||||
let arg_block = match variant.fields {
|
||||
Named(ref fields) => gen_augmentation(&fields.named, &app_var),
|
||||
Unit => quote!( #app_var ),
|
||||
Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
|
||||
let ty = &unnamed[0];
|
||||
quote! {
|
||||
{
|
||||
let #app_var = <#ty>::augment_clap(#app_var);
|
||||
if <#ty>::is_subcommand() {
|
||||
#app_var.setting(
|
||||
::structopt::clap::AppSettings::SubcommandRequiredElseHelp
|
||||
)
|
||||
} else {
|
||||
#app_var
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Unnamed(..) => panic!("{}: tuple enum are not supported", variant.ident),
|
||||
};
|
||||
|
||||
let name = attrs.name();
|
||||
let from_attrs = attrs.methods();
|
||||
quote! {
|
||||
.subcommand({
|
||||
let #app_var = ::structopt::clap::SubCommand::with_name(#name);
|
||||
let #app_var = #arg_block;
|
||||
#app_var#from_attrs
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
pub fn augment_clap<'a, 'b>(
|
||||
app: ::structopt::clap::App<'a, 'b>
|
||||
) -> ::structopt::clap::App<'a, 'b> {
|
||||
app #( #subcommands )*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_from_clap_enum(name: &syn::Ident) -> proc_macro2::TokenStream {
|
||||
quote! {
|
||||
fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
|
||||
<#name>::from_subcommand(matches.subcommand())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_from_subcommand(
|
||||
name: &syn::Ident,
|
||||
variants: &punctuated::Punctuated<syn::Variant, token::Comma>,
|
||||
) -> proc_macro2::TokenStream {
|
||||
use syn::Fields::*;
|
||||
|
||||
let match_arms = variants.iter().map(|variant| {
|
||||
let attrs = Attrs::from_struct(&variant.attrs, variant.ident.to_string());
|
||||
let sub_name = attrs.name();
|
||||
let variant_name = &variant.ident;
|
||||
let constructor_block = match variant.fields {
|
||||
Named(ref fields) => gen_constructor(&fields.named),
|
||||
Unit => quote!(),
|
||||
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
|
||||
let ty = &fields.unnamed[0];
|
||||
quote!( ( <#ty as ::structopt::StructOpt>::from_clap(matches) ) )
|
||||
}
|
||||
Unnamed(..) => panic!("{}: tuple enum are not supported", variant.ident),
|
||||
};
|
||||
|
||||
quote! {
|
||||
(#sub_name, Some(matches)) =>
|
||||
Some(#name :: #variant_name #constructor_block)
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
pub fn from_subcommand<'a, 'b>(
|
||||
sub: (&'b str, Option<&'b ::structopt::clap::ArgMatches<'a>>)
|
||||
) -> Option<Self> {
|
||||
match sub {
|
||||
#( #match_arms ),*,
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_structopt_for_struct(
|
||||
name: &syn::Ident,
|
||||
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
|
||||
attrs: &[syn::Attribute],
|
||||
) -> proc_macro2::TokenStream {
|
||||
let clap = gen_clap_struct(attrs);
|
||||
let augment_clap = gen_augment_clap(fields);
|
||||
let from_clap = gen_from_clap(name, fields);
|
||||
|
||||
quote! {
|
||||
#[allow(unused_variables)]
|
||||
impl ::structopt::StructOpt for #name {
|
||||
#clap
|
||||
#from_clap
|
||||
}
|
||||
|
||||
#[allow(dead_code, unreachable_code)]
|
||||
#[doc(hidden)]
|
||||
impl #name {
|
||||
#augment_clap
|
||||
pub fn is_subcommand() -> bool { false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_structopt_for_enum(
|
||||
name: &syn::Ident,
|
||||
variants: &punctuated::Punctuated<syn::Variant, token::Comma>,
|
||||
attrs: &[syn::Attribute],
|
||||
) -> proc_macro2::TokenStream {
|
||||
let clap = gen_clap_enum(attrs);
|
||||
let augment_clap = gen_augment_clap_enum(variants);
|
||||
let from_clap = gen_from_clap_enum(name);
|
||||
let from_subcommand = gen_from_subcommand(name, variants);
|
||||
|
||||
quote! {
|
||||
impl ::structopt::StructOpt for #name {
|
||||
#clap
|
||||
#from_clap
|
||||
}
|
||||
|
||||
#[allow(unused_variables, dead_code, unreachable_code)]
|
||||
#[doc(hidden)]
|
||||
impl #name {
|
||||
#augment_clap
|
||||
#from_subcommand
|
||||
pub fn is_subcommand() -> bool { true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn derive_clap(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
||||
use syn::Data::*;
|
||||
|
||||
let struct_name = &input.ident;
|
||||
let inner_impl = match input.data {
|
||||
Struct(syn::DataStruct {
|
||||
fields: syn::Fields::Named(ref fields),
|
||||
..
|
||||
}) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs),
|
||||
Enum(ref e) => impl_structopt_for_enum(struct_name, &e.variants, &input.attrs),
|
||||
_ => panic!("structopt only supports non-tuple structs and enums"),
|
||||
};
|
||||
|
||||
quote!(#inner_impl)
|
||||
}
|
48
src/derives/mod.rs
Normal file
48
src/derives/mod.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
|
||||
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
|
||||
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
//
|
||||
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
|
||||
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
|
||||
// MIT/Apache 2.0 license.
|
||||
use syn;
|
||||
|
||||
pub mod arg_enum;
|
||||
pub mod attrs;
|
||||
mod clap;
|
||||
|
||||
pub use self::arg_enum::derive_arg_enum;
|
||||
pub use self::clap::derive_clap;
|
||||
|
||||
fn sub_type(t: &syn::Type) -> Option<&syn::Type> {
|
||||
let segs = match *t {
|
||||
syn::Type::Path(syn::TypePath {
|
||||
path: syn::Path { ref segments, .. },
|
||||
..
|
||||
}) => segments,
|
||||
_ => return None,
|
||||
};
|
||||
match *segs.iter().last().unwrap() {
|
||||
syn::PathSegment {
|
||||
arguments:
|
||||
syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
|
||||
ref args, ..
|
||||
}),
|
||||
..
|
||||
} if args.len() == 1 =>
|
||||
{
|
||||
if let syn::GenericArgument::Type(ref ty) = args[0] {
|
||||
Some(ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
|
||||
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
|
||||
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
use proc_macro;
|
||||
|
||||
// Unfortunately `proc_macro` and `syn` don't have a good error handling story.
|
||||
error_chain! {
|
||||
errors {
|
||||
WrongBodyType(expected: &'static str) {
|
||||
description("The wrong type for the derived structure was provided.")
|
||||
display("Wrong type for derive structure: {:?} expected", expected)
|
||||
}
|
||||
ParseError(error: String) {
|
||||
description("A parsing failure.")
|
||||
display("A parsing failure happened: {:?}", error)
|
||||
}
|
||||
ProcLexError(error: proc_macro::LexError) {
|
||||
description("A proc_macro lex failure.")
|
||||
display("A proc_macro lex failure happened: {:?}", error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
|
||||
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
|
||||
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
use errors::*;
|
||||
use syn::Body::Enum;
|
||||
use syn::{DeriveInput, Variant};
|
||||
|
||||
pub fn variants(ast: &DeriveInput) -> Result<&Vec<Variant>> {
|
||||
match ast.body {
|
||||
Enum(ref variants) => Ok(variants),
|
||||
_ => Err(ErrorKind::WrongBodyType("enum"))?,
|
||||
}
|
||||
}
|
479
src/lib.rs
479
src/lib.rs
|
@ -12,482 +12,29 @@
|
|||
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
|
||||
// MIT/Apache 2.0 license.
|
||||
|
||||
//! This crate is custom derive for StructOpt. It should not be used
|
||||
//! directly. See [structopt documentation](https://docs.rs/structopt)
|
||||
//! for the usage of `#[derive(StructOpt)]`.
|
||||
//! 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;
|
||||
extern crate syn;
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
extern crate proc_macro2;
|
||||
|
||||
use errors::*;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::Tokens;
|
||||
use syn::DeriveInput;
|
||||
|
||||
mod arg_enum;
|
||||
use arg_enum::ArgEnum;
|
||||
mod errors;
|
||||
mod helpers;
|
||||
mod derives;
|
||||
|
||||
/// It is required to have this seperate and specificly defined.
|
||||
#[proc_macro_derive(ArgEnum, attributes(case_sensitive))]
|
||||
pub fn derive_arg_enum(input: TokenStream) -> TokenStream { ArgEnum::derive(input).unwrap() }
|
||||
extern crate proc_macro;
|
||||
extern crate syn;
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
extern crate proc_macro2;
|
||||
|
||||
mod attrs;
|
||||
|
||||
use attrs::{Attrs, Kind, Parser, Ty};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::token::Comma;
|
||||
use syn::*;
|
||||
|
||||
/// Generates the `StructOpt` impl.
|
||||
#[proc_macro_derive(StructOpt, attributes(structopt))]
|
||||
pub fn structopt(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input: DeriveInput = syn::parse(input).unwrap();
|
||||
let gen = impl_structopt(&input);
|
||||
gen.into()
|
||||
pub fn arg_enum(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input: syn::DeriveInput = syn::parse(input).unwrap();
|
||||
derives::derive_arg_enum(&input).into()
|
||||
}
|
||||
|
||||
fn sub_type(t: &syn::Type) -> Option<&syn::Type> {
|
||||
let segs = match *t {
|
||||
syn::Type::Path(TypePath {
|
||||
path: syn::Path { ref segments, .. },
|
||||
..
|
||||
}) => segments,
|
||||
_ => return None,
|
||||
};
|
||||
match *segs.iter().last().unwrap() {
|
||||
PathSegment {
|
||||
arguments:
|
||||
PathArguments::AngleBracketed(AngleBracketedGenericArguments { ref args, .. }),
|
||||
..
|
||||
} if args.len() == 1 =>
|
||||
{
|
||||
if let GenericArgument::Type(ref ty) = args[0] {
|
||||
Some(ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a block of code to add arguments/subcommands corresponding to
|
||||
/// the `fields` to an app.
|
||||
fn gen_augmentation(fields: &Punctuated<Field, Comma>, app_var: &Ident) -> TokenStream {
|
||||
let subcmds: Vec<_> = fields
|
||||
.iter()
|
||||
.filter_map(|field| {
|
||||
let attrs = Attrs::from_field(&field);
|
||||
if let Kind::Subcommand(ty) = attrs.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! {
|
||||
let #app_var = #app_var.setting(
|
||||
::structopt::clap::AppSettings::SubcommandRequiredElseHelp
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Some(quote!{
|
||||
let #app_var = <#subcmd_type>::augment_clap( #app_var );
|
||||
#required
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
subcmds.len() <= 1,
|
||||
"cannot have more than one nested subcommand"
|
||||
);
|
||||
|
||||
let args = fields.iter().filter_map(|field| {
|
||||
let attrs = Attrs::from_field(field);
|
||||
match attrs.kind() {
|
||||
Kind::Subcommand(_) => None,
|
||||
Kind::FlattenStruct => {
|
||||
let ty = &field.ty;
|
||||
Some(quote! {
|
||||
let #app_var = <#ty>::augment_clap(#app_var);
|
||||
let #app_var = if <#ty>::is_subcommand() {
|
||||
#app_var.setting(::structopt::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),
|
||||
_ => &field.ty,
|
||||
};
|
||||
|
||||
let occurences = attrs.parser().0 == Parser::FromOccurrences;
|
||||
|
||||
let validator = match *attrs.parser() {
|
||||
(Parser::TryFromStr, ref f) => quote! {
|
||||
.validator(|s| {
|
||||
#f(&s)
|
||||
.map(|_: #convert_type| ())
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
},
|
||||
(Parser::TryFromOsStr, ref f) => quote! {
|
||||
.validator_os(|s| #f(&s).map(|_: #convert_type| ()))
|
||||
},
|
||||
_ => quote!(),
|
||||
};
|
||||
|
||||
let modifier = match ty {
|
||||
Ty::Bool => quote!( .takes_value(false).multiple(false) ),
|
||||
Ty::Option => quote!( .takes_value(true).multiple(false) #validator ),
|
||||
Ty::Vec => quote!( .takes_value(true).multiple(true) #validator ),
|
||||
Ty::Other if occurences => quote!( .takes_value(false).multiple(true) ),
|
||||
Ty::Other => {
|
||||
let required = !attrs.has_method("default_value");
|
||||
quote!( .takes_value(true).multiple(false).required(#required) #validator )
|
||||
}
|
||||
};
|
||||
let methods = attrs.methods();
|
||||
let name = attrs.name();
|
||||
Some(quote!{
|
||||
let #app_var = #app_var.arg(
|
||||
::structopt::clap::Arg::with_name(#name)
|
||||
#modifier
|
||||
#methods
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {{
|
||||
#( #args )*
|
||||
#( #subcmds )*
|
||||
#app_var
|
||||
}}
|
||||
}
|
||||
|
||||
fn gen_constructor(fields: &Punctuated<Field, Comma>) -> TokenStream {
|
||||
let fields = fields.iter().map(|field| {
|
||||
let attrs = Attrs::from_field(field);
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
match attrs.kind() {
|
||||
Kind::Subcommand(ty) => {
|
||||
let subcmd_type = match (ty, sub_type(&field.ty)) {
|
||||
(Ty::Option, Some(sub_type)) => sub_type,
|
||||
_ => &field.ty,
|
||||
};
|
||||
let unwrapper = match ty {
|
||||
Ty::Option => quote!(),
|
||||
_ => quote!( .unwrap() ),
|
||||
};
|
||||
quote!(#field_name: <#subcmd_type>::from_subcommand(matches.subcommand())#unwrapper)
|
||||
}
|
||||
Kind::FlattenStruct => quote!(#field_name: ::structopt::StructOpt::from_clap(matches)),
|
||||
Kind::Arg(ty) => {
|
||||
use Parser::*;
|
||||
let (value_of, values_of, parse) = match *attrs.parser() {
|
||||
(FromStr, ref f) => (quote!(value_of), quote!(values_of), f.clone()),
|
||||
(TryFromStr, ref f) => (
|
||||
quote!(value_of),
|
||||
quote!(values_of),
|
||||
quote!(|s| #f(s).unwrap()),
|
||||
),
|
||||
(FromOsStr, ref f) => (quote!(value_of_os), quote!(values_of_os), f.clone()),
|
||||
(TryFromOsStr, ref f) => (
|
||||
quote!(value_of_os),
|
||||
quote!(values_of_os),
|
||||
quote!(|s| #f(s).unwrap()),
|
||||
),
|
||||
(FromOccurrences, ref f) => (quote!(occurrences_of), quote!(), f.clone()),
|
||||
};
|
||||
|
||||
let occurences = attrs.parser().0 == Parser::FromOccurrences;
|
||||
let name = attrs.name();
|
||||
let field_value = match ty {
|
||||
Ty::Bool => quote!(matches.is_present(#name)),
|
||||
Ty::Option => quote! {
|
||||
matches.#value_of(#name)
|
||||
.as_ref()
|
||||
.map(#parse)
|
||||
},
|
||||
Ty::Vec => quote! {
|
||||
matches.#values_of(#name)
|
||||
.map(|v| v.map(#parse).collect())
|
||||
.unwrap_or_else(Vec::new)
|
||||
},
|
||||
Ty::Other if occurences => quote! {
|
||||
#parse(matches.#value_of(#name))
|
||||
},
|
||||
Ty::Other => quote! {
|
||||
matches.#value_of(#name)
|
||||
.map(#parse)
|
||||
.unwrap()
|
||||
},
|
||||
};
|
||||
|
||||
quote!( #field_name: #field_value )
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {{
|
||||
#( #fields ),*
|
||||
}}
|
||||
}
|
||||
|
||||
fn gen_from_clap(struct_name: &Ident, fields: &Punctuated<Field, Comma>) -> TokenStream {
|
||||
let field_block = gen_constructor(fields);
|
||||
|
||||
quote! {
|
||||
fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
|
||||
#struct_name #field_block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_clap(attrs: &[Attribute]) -> TokenStream {
|
||||
let name = std::env::var("CARGO_PKG_NAME")
|
||||
.ok()
|
||||
.unwrap_or_else(String::default);
|
||||
let attrs = Attrs::from_struct(attrs, name);
|
||||
let name = attrs.name();
|
||||
let methods = attrs.methods();
|
||||
quote!(::structopt::clap::App::new(#name)#methods)
|
||||
}
|
||||
|
||||
fn gen_clap_struct(struct_attrs: &[Attribute]) -> TokenStream {
|
||||
let gen = gen_clap(struct_attrs);
|
||||
quote! {
|
||||
fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> {
|
||||
let app = #gen;
|
||||
Self::augment_clap(app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_augment_clap(fields: &Punctuated<Field, Comma>) -> TokenStream {
|
||||
let app_var = Ident::new("app", Span::call_site());
|
||||
let augmentation = gen_augmentation(fields, &app_var);
|
||||
quote! {
|
||||
pub fn augment_clap<'a, 'b>(
|
||||
#app_var: ::structopt::clap::App<'a, 'b>
|
||||
) -> ::structopt::clap::App<'a, 'b> {
|
||||
#augmentation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_clap_enum(enum_attrs: &[Attribute]) -> TokenStream {
|
||||
let gen = gen_clap(enum_attrs);
|
||||
quote! {
|
||||
fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> {
|
||||
let app = #gen
|
||||
.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp);
|
||||
Self::augment_clap(app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_augment_clap_enum(variants: &Punctuated<Variant, Comma>) -> TokenStream {
|
||||
use syn::Fields::*;
|
||||
|
||||
let subcommands = variants.iter().map(|variant| {
|
||||
let name = variant.ident.to_string();
|
||||
let attrs = Attrs::from_struct(&variant.attrs, name);
|
||||
let app_var = Ident::new("subcommand", Span::call_site());
|
||||
let arg_block = match variant.fields {
|
||||
Named(ref fields) => gen_augmentation(&fields.named, &app_var),
|
||||
Unit => quote!( #app_var ),
|
||||
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
|
||||
let ty = &unnamed[0];
|
||||
quote! {
|
||||
{
|
||||
let #app_var = <#ty>::augment_clap(#app_var);
|
||||
if <#ty>::is_subcommand() {
|
||||
#app_var.setting(
|
||||
::structopt::clap::AppSettings::SubcommandRequiredElseHelp
|
||||
)
|
||||
} else {
|
||||
#app_var
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Unnamed(..) => panic!("{}: tuple enum are not supported", variant.ident),
|
||||
};
|
||||
|
||||
let name = attrs.name();
|
||||
let from_attrs = attrs.methods();
|
||||
quote! {
|
||||
.subcommand({
|
||||
let #app_var = ::structopt::clap::SubCommand::with_name(#name);
|
||||
let #app_var = #arg_block;
|
||||
#app_var#from_attrs
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
pub fn augment_clap<'a, 'b>(
|
||||
app: ::structopt::clap::App<'a, 'b>
|
||||
) -> ::structopt::clap::App<'a, 'b> {
|
||||
app #( #subcommands )*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_from_clap_enum(name: &Ident) -> TokenStream {
|
||||
quote! {
|
||||
fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
|
||||
<#name>::from_subcommand(matches.subcommand())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_from_subcommand(name: &Ident, variants: &Punctuated<Variant, Comma>) -> TokenStream {
|
||||
use syn::Fields::*;
|
||||
|
||||
let match_arms = variants.iter().map(|variant| {
|
||||
let attrs = Attrs::from_struct(&variant.attrs, variant.ident.to_string());
|
||||
let sub_name = attrs.name();
|
||||
let variant_name = &variant.ident;
|
||||
let constructor_block = match variant.fields {
|
||||
Named(ref fields) => gen_constructor(&fields.named),
|
||||
Unit => quote!(),
|
||||
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
|
||||
let ty = &fields.unnamed[0];
|
||||
quote!( ( <#ty as ::structopt::StructOpt>::from_clap(matches) ) )
|
||||
}
|
||||
Unnamed(..) => panic!("{}: tuple enum are not supported", variant.ident),
|
||||
};
|
||||
|
||||
quote! {
|
||||
(#sub_name, Some(matches)) =>
|
||||
Some(#name :: #variant_name #constructor_block)
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
pub fn from_subcommand<'a, 'b>(
|
||||
sub: (&'b str, Option<&'b ::structopt::clap::ArgMatches<'a>>)
|
||||
) -> Option<Self> {
|
||||
match sub {
|
||||
#( #match_arms ),*,
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_structopt_for_struct(
|
||||
name: &Ident,
|
||||
fields: &Punctuated<Field, Comma>,
|
||||
attrs: &[Attribute],
|
||||
) -> TokenStream {
|
||||
let clap = gen_clap_struct(attrs);
|
||||
let augment_clap = gen_augment_clap(fields);
|
||||
let from_clap = gen_from_clap(name, fields);
|
||||
|
||||
quote! {
|
||||
#[allow(unused_variables)]
|
||||
impl ::structopt::StructOpt for #name {
|
||||
#clap
|
||||
#from_clap
|
||||
}
|
||||
|
||||
#[allow(dead_code, unreachable_code)]
|
||||
#[doc(hidden)]
|
||||
impl #name {
|
||||
#augment_clap
|
||||
pub fn is_subcommand() -> bool { false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_structopt_for_enum(
|
||||
name: &Ident,
|
||||
variants: &Punctuated<Variant, Comma>,
|
||||
attrs: &[Attribute],
|
||||
) -> TokenStream {
|
||||
let clap = gen_clap_enum(attrs);
|
||||
let augment_clap = gen_augment_clap_enum(variants);
|
||||
let from_clap = gen_from_clap_enum(name);
|
||||
let from_subcommand = gen_from_subcommand(name, variants);
|
||||
|
||||
quote! {
|
||||
impl ::structopt::StructOpt for #name {
|
||||
#clap
|
||||
#from_clap
|
||||
}
|
||||
|
||||
#[allow(unused_variables, dead_code, unreachable_code)]
|
||||
#[doc(hidden)]
|
||||
impl #name {
|
||||
#augment_clap
|
||||
#from_subcommand
|
||||
pub fn is_subcommand() -> bool { true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_structopt(input: &DeriveInput) -> TokenStream {
|
||||
use syn::Data::*;
|
||||
|
||||
let struct_name = &input.ident;
|
||||
let inner_impl = match input.data {
|
||||
Struct(DataStruct {
|
||||
fields: syn::Fields::Named(ref fields),
|
||||
..
|
||||
}) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs),
|
||||
Enum(ref e) => impl_structopt_for_enum(struct_name, &e.variants, &input.attrs),
|
||||
_ => panic!("structopt only supports non-tuple structs and enums"),
|
||||
};
|
||||
|
||||
quote!(#inner_impl)
|
||||
}
|
||||
|
||||
trait ClapDerive {
|
||||
/// Generate the output from a given input.
|
||||
fn generate_from(ast: &DeriveInput) -> Result<Tokens>;
|
||||
|
||||
/// Wraps around `generate_from` and does some pre/post processing.
|
||||
fn derive(input: TokenStream) -> Result<TokenStream> {
|
||||
let derive_input = Self::parse_input(input)?;
|
||||
let generated_output = Self::generate_from(&derive_input)?;
|
||||
let stream = generated_output
|
||||
.parse()
|
||||
.map_err(|e| ErrorKind::ProcLexError(e))?;
|
||||
Ok(stream)
|
||||
}
|
||||
/// Parses the inputted stream.
|
||||
fn parse_input(input: TokenStream) -> Result<DeriveInput> {
|
||||
// Construct a string representation of the type definition
|
||||
let as_string = input.to_string();
|
||||
// Parse the string representation
|
||||
let parsed = syn::parse_derive_input(&as_string).map_err(|e| ErrorKind::ParseError(e))?;
|
||||
Ok(parsed)
|
||||
}
|
||||
/// Generates the `Clap` impl.
|
||||
#[proc_macro_derive(Clap, attributes(clap))]
|
||||
pub fn clap(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input: syn::DeriveInput = syn::parse(input).unwrap();
|
||||
derives::derive_clap(&input).into()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue