Merge remote-tracking branch 'origin/stable_channel' into support_generics

This commit is contained in:
Paul Lesur
2018-08-20 22:50:43 +02:00
5 changed files with 131 additions and 45 deletions
+13
View File
@@ -0,0 +1,13 @@
language: rust
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
- rust: beta
cache: cargo
script:
- cargo build --verbose --all
- cargo test --verbose --all
+15 -1
View File
@@ -1,7 +1,19 @@
[package]
name = "optional_struct"
version = "0.1.0"
version = "0.1.3"
authors = ["Paul Lesur <paul.lesur@plesur.fr>"]
description = """
Crate defining a macro that will generate, from a structure, another structure with only Option<T> fields
"""
readme = "README.md"
categories = ["config"]
keywords = ["macro", "configuration"]
license = "Apache-2.0"
repository = "https://github.com/pLesur/OptionalStruct"
exclude = [".travis.yml"]
[badges]
travis-ci = { repository = "pLesur/OptionalStruct", branch = "stable_channel" }
[dependencies]
syn = "0.11.11"
@@ -9,3 +21,5 @@ quote = "0.3"
[lib]
proc-macro = true
+5 -2
View File
@@ -1,6 +1,8 @@
# OptionalStruct
[![Build Status](https://travis-ci.org/pLesur/OptionalStruct.svg?branch=stable_channel)](https://travis-ci.org/pLesur/OptionalStruct)
[![Crates.io][https://img.shields.io/crates/v/optional_struct.svg]][https://crates.io/crates/optional_struct]
## How to use
## Goal
This crate allows the user to generate a structure containing the same fields as the original struct but wrapped in Option<T>.
A method is also implemented for the original struct, `apply_options`. It consumes the generated optional_struct, and for every Some(x) field, it assigns the original structure's value with the optional_struct one.
@@ -82,7 +84,8 @@ conf.apply_options(user_conf);
* You can also nest your generated struct by mapping the original types to their new names:
```rust
#[derive(OptionalStruct)]
#[LogConfig = "OptionalLogConfig"]
#[opt_nested_original(LogConfig)]
#[opt_nested_generated(OptionalLogConfig)]
struct Config {
timeout: Option<u32>,
log_config: LogConfig,
+96 -39
View File
@@ -1,5 +1,3 @@
#![feature(custom_attribute)]
extern crate proc_macro;
extern crate syn;
#[macro_use]
@@ -11,21 +9,25 @@ use std::collections::HashMap;
use syn::Field;
use syn::Generics;
use syn::Ident;
use syn::Lit;
#[proc_macro_derive(OptionalStruct, attributes(optional_name, optional_derive))]
#[proc_macro_derive(
OptionalStruct,
attributes(optional_name, optional_derive, opt_nested_original, opt_nested_generated)
)]
pub fn optional_struct(input: TokenStream) -> TokenStream {
let s = input.to_string();
let ast = syn::parse_derive_input(&s).unwrap();
let gen = create_optional_struct(&ast);
let gen = generate_optional_struct(&ast);
gen.parse().unwrap()
}
fn create_optional_struct(ast: &syn::DeriveInput) -> Tokens {
fn generate_optional_struct(ast: &syn::DeriveInput) -> Tokens {
let data = parse_attributes(&ast);
if let syn::Body::Struct(ref variant_data) = ast.body {
if let &syn::VariantData::Struct(ref fields) = variant_data {
return create_non_tuple_struct(fields, data, &ast.generics);
return create_struct(fields, data, &ast.generics);
}
}
@@ -50,46 +52,101 @@ impl Data {
}
}
fn nested_meta_item_to_ident(nested_item: &syn::NestedMetaItem) -> &Ident {
match nested_item {
&syn::NestedMetaItem::MetaItem(ref item) => match item {
&syn::MetaItem::Word(ref ident) => ident,
_ => panic!("Only traits name are supported inside optional_struct"),
},
&syn::NestedMetaItem::Literal(_) => {
panic!("Only traits name are supported inside optional_struct")
}
}
}
fn create_nested_names_map(orig: Vec<Ident>, gen: Vec<Ident>) -> HashMap<String, String> {
let mut map = HashMap::new();
let orig_gen = orig.iter().zip(gen);
for (orig, gen) in orig_gen {
if gen.to_string().is_empty() {
map.insert(orig.to_string(), "Optional".to_owned() + &gen.to_string());
} else {
map.insert(orig.to_string(), gen.to_string());
}
}
map
}
fn handle_list(
name: &Ident,
values: &Vec<syn::NestedMetaItem>,
nested_original: &mut Vec<Ident>,
nested_generated: &mut Vec<Ident>,
derives: &mut Tokens,
) {
match name.to_string().as_str() {
"optional_derive" => {
let mut derives_local = quote!{};
for value in values {
let derive_ident = nested_meta_item_to_ident(value);
derives_local = quote!{ #derive_ident, #derives_local }
}
*derives = derives_local;
}
"opt_nested_generated" => {
for value in values {
let generated_nested_name = nested_meta_item_to_ident(value);
nested_generated.push(generated_nested_name.clone());
}
}
"opt_nested_original" => {
for value in values {
let original_nested_name = nested_meta_item_to_ident(value);
nested_original.push(original_nested_name.clone());
}
}
_ => panic!("Only optional_derive are supported"),
};
}
fn handle_name_value(name: &Ident, value: &Lit, struct_name: &mut Ident) {
match value {
&Lit::Str(ref name_value, _) => {
if name == "optional_name" {
*struct_name = Ident::new(name_value.clone())
} else {
panic!("Only optional_name is supported");
}
}
_ => panic!("optional_name should be a string"),
}
}
fn parse_attributes(ast: &syn::DeriveInput) -> Data {
let orignal_struct_name = ast.ident.clone();
let mut struct_name = String::from("Optional");
struct_name.push_str(&ast.ident.to_string());
let mut struct_name = Ident::new(struct_name);
let mut derives = quote!{};
let mut nested_names = HashMap::new();
let mut nested_generated = Vec::new();
let mut nested_original = Vec::new();
for attribute in &ast.attrs {
match &attribute.value {
&syn::MetaItem::Word(_) => panic!("No word attribute is supported"),
&syn::MetaItem::NameValue(ref name, ref value) => match value {
&syn::Lit::Str(ref name_value, _) => {
if name != "optional_name" {
nested_names.insert(name.to_string(), name_value.clone());
} else {
struct_name = Ident::new(name_value.clone());
}
}
_ => panic!("optional_name should be a string"),
},
&syn::MetaItem::List(ref name, ref values) => {
if name != "optional_derive" {
panic!("Only optional_derive are supported");
}
for value in values {
match value {
&syn::NestedMetaItem::MetaItem(ref item) => match item {
&syn::MetaItem::Word(ref derive_name) => {
derives = quote!{ #derive_name, #derives }
}
_ => panic!("Only traits name are supported inside optional_struct"),
},
&syn::NestedMetaItem::Literal(_) => {
panic!("Only traits name are supported inside optional_struct")
}
}
}
&syn::MetaItem::NameValue(ref name, ref value) => {
handle_name_value(name, value, &mut struct_name)
}
&syn::MetaItem::List(ref name, ref values) => handle_list(
name,
values,
&mut nested_original,
&mut nested_generated,
&mut derives,
),
}
}
@@ -104,13 +161,13 @@ fn parse_attributes(ast: &syn::DeriveInput) -> Data {
orignal_struct_name: orignal_struct_name,
optional_struct_name: struct_name,
derives: derives,
nested_names: nested_names,
nested_names: create_nested_names_map(nested_original, nested_generated),
}
}
fn create_non_tuple_struct(fields: &Vec<Field>, data: Data, generics: &Generics) -> Tokens {
fn create_struct(fields: &Vec<Field>, data: Data, generics: &Generics) -> Tokens {
let (orignal_struct_name, optional_struct_name, derives, nested_names) = data.explode();
let (assigners, attributes, empty) = generate_dynamic_assignments(&fields, nested_names);
let (assigners, attributes, empty) = create_fields(&fields, nested_names);
let (_, generics_no_where, _) = generics.split_for_impl();
@@ -136,7 +193,7 @@ fn create_non_tuple_struct(fields: &Vec<Field>, data: Data, generics: &Generics)
}
}
fn generate_dynamic_assignments(
fn create_fields(
fields: &Vec<Field>,
nested_names: HashMap<String, String>,
) -> (Tokens, Tokens, Tokens) {
+2 -3
View File
@@ -1,10 +1,9 @@
#![feature(custom_attribute)]
#[macro_use]
extern crate optional_struct;
#[derive(OptionalStruct)]
#[LogConfig = "OptionalLogConfig"]
#[opt_nested_original(LogConfig)]
#[opt_nested_generated(OptionalLogConfig)]
struct Config {
timeout: Option<u32>,
log_config: LogConfig,