Update syn and friends

This commit is contained in:
Guillaume Pinot 2018-02-04 18:33:59 +01:00
parent 0d8c4ae23b
commit bc02743249
3 changed files with 133 additions and 91 deletions

View file

@ -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

View file

@ -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
}

View file

@ -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")
};