mirror of
https://github.com/clap-rs/clap
synced 2025-03-05 07:47:40 +00:00
Update syn and friends
This commit is contained in:
parent
0d8c4ae23b
commit
bc02743249
3 changed files with 133 additions and 91 deletions
|
@ -13,8 +13,9 @@ license = "WTFPL"
|
|||
travis-ci = { repository = "TeXitoi/structopt" }
|
||||
|
||||
[dependencies]
|
||||
syn = "0.11.4"
|
||||
quote = "0.3.12"
|
||||
syn = "0.12"
|
||||
quote = "0.4"
|
||||
proc-macro2 = "0.2"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
|
||||
use std::env;
|
||||
use quote::Tokens;
|
||||
use syn::{self, Attribute};
|
||||
use syn::{self, Attribute, MetaNameValue, MetaList, LitStr};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Attrs {
|
||||
name: String,
|
||||
methods: Vec<Method>,
|
||||
|
@ -16,6 +17,7 @@ pub struct Attrs {
|
|||
has_custom_parser: bool,
|
||||
is_subcommand: bool,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct Method {
|
||||
name: String,
|
||||
args: Tokens,
|
||||
|
@ -44,8 +46,8 @@ impl ::std::str::FromStr for Parser {
|
|||
|
||||
impl Attrs {
|
||||
fn new(attrs: &[Attribute], name: String, methods: Vec<Method>) -> Attrs {
|
||||
use NestedMetaItem::MetaItem;
|
||||
use MetaItem::*;
|
||||
use Meta::*;
|
||||
use NestedMeta::*;
|
||||
use Lit::*;
|
||||
|
||||
let mut res = Attrs {
|
||||
|
@ -56,42 +58,53 @@ impl Attrs {
|
|||
is_subcommand: false,
|
||||
};
|
||||
let iter = attrs.iter()
|
||||
.flat_map(|attr| match attr.value {
|
||||
List(ref i, ref v) if i.as_ref() == "structopt" => v.as_slice(),
|
||||
_ => &[],
|
||||
.filter_map(|attr| {
|
||||
let path = &attr.path;
|
||||
match quote!(#path) == quote!(structopt) {
|
||||
true => Some(
|
||||
attr.interpret_meta()
|
||||
.expect(&format!("invalid structopt syntax: {}", quote!(attr)))
|
||||
),
|
||||
false => None,
|
||||
}
|
||||
}).
|
||||
map(|m| match *m {
|
||||
MetaItem(ref mi) => mi,
|
||||
ref lit => panic!("unsupported syntax: {}", quote!(#lit).to_string()),
|
||||
flat_map(|m| match m {
|
||||
List(l) => l.nested,
|
||||
tokens => panic!("unsupported syntax: {}", quote!(#tokens).to_string()),
|
||||
})
|
||||
.map(|m| match m {
|
||||
Meta(m) => m,
|
||||
ref tokens => panic!("unsupported syntax: {}", quote!(#tokens).to_string()),
|
||||
});
|
||||
for attr in iter {
|
||||
match *attr {
|
||||
NameValue(ref key, Str(ref value, _)) if value.is_empty() => {
|
||||
res.methods = res.methods
|
||||
.into_iter()
|
||||
.filter(|m| m.name == key.as_ref())
|
||||
.collect();
|
||||
}
|
||||
NameValue(ref key, Str(ref value, _)) if key == "name" =>
|
||||
res.name = value.to_string(),
|
||||
NameValue(ref key, ref value) => {
|
||||
match attr {
|
||||
NameValue(MetaNameValue { ident, lit: Str(ref value), .. })
|
||||
if value.value() == "" => {
|
||||
res.methods = res.methods
|
||||
.into_iter()
|
||||
.filter(|m| m.name == ident.as_ref())
|
||||
.collect();
|
||||
}
|
||||
NameValue(MetaNameValue { ident, lit: Str(ref s), .. })
|
||||
if ident == "name" => res.name = s.value(),
|
||||
NameValue(MetaNameValue { ident, lit, .. }) => {
|
||||
res.methods.push(Method {
|
||||
name: key.to_string(),
|
||||
args: quote!(#value),
|
||||
name: ident.to_string(),
|
||||
args: quote!(#lit),
|
||||
})
|
||||
}
|
||||
List(ref method, ref args) if method == "parse" => {
|
||||
if args.len() != 1 {
|
||||
List(MetaList { ident, ref nested, .. }) if ident == "parse" => {
|
||||
if nested.len() != 1 {
|
||||
panic!("parse must have exactly one argument");
|
||||
}
|
||||
res.has_custom_parser = true;
|
||||
res.parser = match args[0] {
|
||||
MetaItem(NameValue(ref i, Str(ref v, _))) => {
|
||||
let function = syn::parse_path(v).expect("parser function path");
|
||||
let parser = i.as_ref().parse().unwrap();
|
||||
res.parser = match nested[0] {
|
||||
Meta(NameValue(MetaNameValue { ident, lit: Str(ref v), .. })) => {
|
||||
let function: syn::Path = v.parse().expect("parser function path");
|
||||
let parser = ident.as_ref().parse().unwrap();
|
||||
(parser, quote!(#function))
|
||||
}
|
||||
MetaItem(Word(ref i)) => {
|
||||
Meta(Word(ref i)) => {
|
||||
use Parser::*;
|
||||
let parser = i.as_ref().parse().unwrap();
|
||||
let function = match parser {
|
||||
|
@ -106,11 +119,11 @@ impl Attrs {
|
|||
ref l @ _ => panic!("unknown value parser specification: {}", quote!(#l)),
|
||||
};
|
||||
}
|
||||
List(ref method, ref args) if method == "raw" => {
|
||||
for method in args {
|
||||
List(MetaList { ident, ref nested, .. }) if ident == "raw" => {
|
||||
for method in nested {
|
||||
match *method {
|
||||
MetaItem(NameValue(ref key, Str(ref value, _))) =>
|
||||
res.push_raw_method(key.as_ref(), value),
|
||||
Meta(NameValue(MetaNameValue { ident, lit: Str(ref v), .. })) =>
|
||||
res.push_raw_method(ident.as_ref(), v),
|
||||
ref mi @ _ => panic!("unsupported raw entry: {}", quote!(#mi)),
|
||||
}
|
||||
}
|
||||
|
@ -122,8 +135,8 @@ impl Attrs {
|
|||
}
|
||||
res
|
||||
}
|
||||
fn push_raw_method(&mut self, name: &str, args: &str) {
|
||||
let ts = syn::parse_token_trees(args)
|
||||
fn push_raw_method(&mut self, name: &str, args: &LitStr) {
|
||||
let ts: ::proc_macro2::TokenStream = args.value().parse()
|
||||
.expect(&format!("bad parameter {} = {}: the parameter must be valid rust code", name, quote!(#args)));
|
||||
self.methods.push(Method {
|
||||
name: name.to_string(),
|
||||
|
@ -132,24 +145,28 @@ impl Attrs {
|
|||
}
|
||||
fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) {
|
||||
if self.has_method(name) { return; }
|
||||
let doc_comments: Vec<&str> = attrs.iter()
|
||||
let doc_comments: Vec<_> = attrs.iter()
|
||||
.filter_map(|attr| {
|
||||
use MetaItem::*;
|
||||
use StrStyle::*;
|
||||
let path = &attr.path;
|
||||
match quote!(#path) == quote!(doc) {
|
||||
true => attr.interpret_meta(),
|
||||
false => None,
|
||||
}
|
||||
})
|
||||
.filter_map(|attr| {
|
||||
use Meta::*;
|
||||
use Lit::*;
|
||||
if let Attribute {
|
||||
value: NameValue(ref name, Str(ref value, Cooked)),
|
||||
is_sugared_doc: true,
|
||||
..
|
||||
} = *attr {
|
||||
if name != "doc" { return None; }
|
||||
let text = value.trim_left_matches("//!")
|
||||
if let NameValue(MetaNameValue { ident, lit: Str(s), .. }) = attr {
|
||||
if ident != "doc" { return None; }
|
||||
let value = s.value();
|
||||
let text = value
|
||||
.trim_left_matches("//!")
|
||||
.trim_left_matches("///")
|
||||
.trim_left_matches("/*!")
|
||||
.trim_left_matches("/**")
|
||||
.trim_right_matches("*/")
|
||||
.trim();
|
||||
Some(text)
|
||||
Some(text.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -13,20 +13,22 @@ extern crate proc_macro;
|
|||
extern crate syn;
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
extern crate proc_macro2;
|
||||
|
||||
mod attrs;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use syn::*;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::token::{Comma};
|
||||
use attrs::{Attrs, Parser};
|
||||
|
||||
/// Generates the `StructOpt` impl.
|
||||
#[proc_macro_derive(StructOpt, attributes(structopt))]
|
||||
pub fn structopt(input: TokenStream) -> TokenStream {
|
||||
let s = input.to_string();
|
||||
let ast = syn::parse_derive_input(&s).unwrap();
|
||||
let gen = impl_structopt(&ast);
|
||||
gen.parse().unwrap()
|
||||
let input: DeriveInput = syn::parse(input).unwrap();
|
||||
let gen = impl_structopt(&input);
|
||||
gen.into()
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
|
@ -37,9 +39,9 @@ enum Ty {
|
|||
Other,
|
||||
}
|
||||
|
||||
fn ty(t: &syn::Ty) -> Ty {
|
||||
if let syn::Ty::Path(None, syn::Path { segments: ref segs, .. }) = *t {
|
||||
match segs.last().unwrap().ident.as_ref() {
|
||||
fn ty(t: &syn::Type) -> Ty {
|
||||
if let syn::Type::Path(TypePath { path: syn::Path { ref segments, .. }, .. }) = *t {
|
||||
match segments.iter().last().unwrap().ident.as_ref() {
|
||||
"bool" => Ty::Bool,
|
||||
"Option" => Ty::Option,
|
||||
"Vec" => Ty::Vec,
|
||||
|
@ -50,18 +52,25 @@ fn ty(t: &syn::Ty) -> Ty {
|
|||
}
|
||||
}
|
||||
|
||||
fn sub_type(t: &syn::Ty) -> Option<&syn::Ty> {
|
||||
fn sub_type(t: &syn::Type) -> Option<&syn::Type> {
|
||||
let segs = match *t {
|
||||
syn::Ty::Path(None, syn::Path { ref segments, .. }) => segments,
|
||||
syn::Type::Path(TypePath { path: syn::Path { ref segments, .. }, .. }) => segments,
|
||||
_ => return None,
|
||||
};
|
||||
match *segs.last().unwrap() {
|
||||
match *segs.iter().last().unwrap() {
|
||||
PathSegment {
|
||||
parameters: PathParameters::AngleBracketed(
|
||||
AngleBracketedParameterData { ref types, .. }),
|
||||
arguments: PathArguments::AngleBracketed(
|
||||
AngleBracketedGenericArguments { ref args, .. }
|
||||
),
|
||||
..
|
||||
} if !types.is_empty() => Some(&types[0]),
|
||||
_ => None,
|
||||
} if args.len() == 1 => {
|
||||
if let GenericArgument::Type(ref ty) = args[0] {
|
||||
Some(ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,7 +83,7 @@ fn convert_with_custom_parse(cur_type: Ty) -> Ty {
|
|||
|
||||
/// Generate a block of code to add arguments/subcommands corresponding to
|
||||
/// the `fields` to an app.
|
||||
fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens {
|
||||
fn gen_augmentation(fields: &Punctuated<Field, Comma>, app_var: &Ident) -> quote::Tokens {
|
||||
let subcmds: Vec<quote::Tokens> = fields.iter()
|
||||
.filter(|&field| Attrs::from_field(&field).is_subcommand())
|
||||
.map(|field| {
|
||||
|
@ -149,7 +158,7 @@ fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens {
|
|||
}}
|
||||
}
|
||||
|
||||
fn gen_constructor(fields: &[Field]) -> quote::Tokens {
|
||||
fn gen_constructor(fields: &Punctuated<Field, Comma>) -> quote::Tokens {
|
||||
let fields = fields.iter().map(|field| {
|
||||
let attrs = Attrs::from_field(field);
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
|
@ -216,7 +225,7 @@ fn gen_constructor(fields: &[Field]) -> quote::Tokens {
|
|||
}}
|
||||
}
|
||||
|
||||
fn gen_from_clap(struct_name: &Ident, fields: &[Field]) -> quote::Tokens {
|
||||
fn gen_from_clap(struct_name: &Ident, fields: &Punctuated<Field, Comma>) -> quote::Tokens {
|
||||
let field_block = gen_constructor(fields);
|
||||
|
||||
quote! {
|
||||
|
@ -245,8 +254,8 @@ fn gen_clap_struct(struct_attrs: &[Attribute]) -> quote::Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
fn gen_augment_clap(fields: &[Field]) -> quote::Tokens {
|
||||
let app_var = Ident::new("app");
|
||||
fn gen_augment_clap(fields: &Punctuated<Field, Comma>) -> quote::Tokens {
|
||||
let app_var: Ident = "app".into();
|
||||
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> {
|
||||
|
@ -266,18 +275,21 @@ fn gen_clap_enum(enum_attrs: &[Attribute]) -> quote::Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
fn gen_augment_clap_enum(variants: &[Variant]) -> quote::Tokens {
|
||||
fn gen_augment_clap_enum(variants: &Punctuated<Variant, Comma>) -> quote::Tokens {
|
||||
use syn::Fields::*;
|
||||
|
||||
let subcommands = variants.iter().map(|variant| {
|
||||
let attrs = Attrs::from_struct(&variant.attrs, variant.ident.to_string());
|
||||
let app_var = Ident::new("subcommand");
|
||||
let arg_block = match variant.data {
|
||||
VariantData::Struct(ref fields) => gen_augmentation(fields, &app_var),
|
||||
VariantData::Unit => quote!( #app_var ),
|
||||
VariantData::Tuple(ref fields) if fields.len() == 1 => {
|
||||
let ty = &fields[0];
|
||||
let name = variant.ident.as_ref().to_string();
|
||||
let attrs = Attrs::from_struct(&variant.attrs, name);
|
||||
let app_var: Ident = "subcommand".into();
|
||||
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!(#ty::augment_clap(#app_var))
|
||||
}
|
||||
VariantData::Tuple(..) => panic!("{}: tuple enum are not supported", variant.ident),
|
||||
Unnamed(..) => panic!("{}: tuple enum are not supported", variant.ident),
|
||||
};
|
||||
|
||||
let name = attrs.name();
|
||||
|
@ -306,19 +318,21 @@ fn gen_from_clap_enum(name: &Ident) -> quote::Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens {
|
||||
fn gen_from_subcommand(name: &Ident, variants: &Punctuated<Variant, Comma>) -> quote::Tokens {
|
||||
use syn::Fields::*;
|
||||
|
||||
let match_arms = variants.iter().map(|variant| {
|
||||
let attrs = Attrs::from_struct(&variant.attrs, variant.ident.as_ref().to_string());
|
||||
let sub_name = attrs.name();
|
||||
let variant_name = &variant.ident;
|
||||
let constructor_block = match variant.data {
|
||||
VariantData::Struct(ref fields) => gen_constructor(fields),
|
||||
VariantData::Unit => quote!(),
|
||||
VariantData::Tuple(ref fields) if fields.len() == 1 => {
|
||||
let ty = &fields[0];
|
||||
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) ) )
|
||||
}
|
||||
VariantData::Tuple(..) =>
|
||||
Unnamed(..) =>
|
||||
panic!("{}: tuple enum are not supported", variant.ident),
|
||||
};
|
||||
|
||||
|
@ -339,7 +353,11 @@ fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute]) -> quote::Tokens {
|
||||
fn impl_structopt_for_struct(
|
||||
name: &Ident,
|
||||
fields: &Punctuated<Field, Comma>,
|
||||
attrs: &[Attribute]
|
||||
) -> quote::Tokens {
|
||||
let clap = gen_clap_struct(attrs);
|
||||
let augment_clap = gen_augment_clap(fields);
|
||||
let from_clap = gen_from_clap(name, fields);
|
||||
|
@ -357,7 +375,11 @@ fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute]
|
|||
}
|
||||
}
|
||||
|
||||
fn impl_structopt_for_enum(name: &Ident, variants: &[Variant], attrs: &[Attribute]) -> quote::Tokens {
|
||||
fn impl_structopt_for_enum(
|
||||
name: &Ident,
|
||||
variants: &Punctuated<Variant, Comma>,
|
||||
attrs: &[Attribute]
|
||||
) -> quote::Tokens {
|
||||
let clap = gen_clap_enum(attrs);
|
||||
let augment_clap = gen_augment_clap_enum(variants);
|
||||
let from_clap = gen_from_clap_enum(name);
|
||||
|
@ -377,13 +399,15 @@ fn impl_structopt_for_enum(name: &Ident, variants: &[Variant], attrs: &[Attribut
|
|||
}
|
||||
}
|
||||
|
||||
fn impl_structopt(ast: &DeriveInput) -> quote::Tokens {
|
||||
let struct_name = &ast.ident;
|
||||
let inner_impl = match ast.body {
|
||||
Body::Struct(VariantData::Struct(ref fields)) =>
|
||||
impl_structopt_for_struct(struct_name, fields, &ast.attrs),
|
||||
Body::Enum(ref variants) =>
|
||||
impl_structopt_for_enum(struct_name, variants, &ast.attrs),
|
||||
fn impl_structopt(input: &DeriveInput) -> quote::Tokens {
|
||||
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")
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue