From 629d02f6448f185703d21614213eb81099fa5514 Mon Sep 17 00:00:00 2001 From: Paul Lesur Date: Mon, 23 Oct 2017 16:58:34 +0200 Subject: [PATCH 1/7] refactoring how nested structures are handled - the crate now works on stable channel --- src/lib.rs | 139 +++++++++++++++++++++++++++++++++--------------- tests/nested.rs | 3 +- 2 files changed, 98 insertions(+), 44 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6154499..f301706 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![feature(custom_attribute)] - extern crate proc_macro; extern crate syn; #[macro_use] @@ -9,22 +7,25 @@ use std::collections::HashMap; use proc_macro::TokenStream; use syn::Field; use syn::Ident; +use syn::Lit; use quote::Tokens; -#[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); + return create_struct(fields, data); } } @@ -49,52 +50,104 @@ 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, gen: Vec) -> HashMap { + 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, + nested_original: &mut Vec, + nested_generated: &mut Vec, + 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"), - } + handle_name_value(name, value, &mut struct_name); } &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") - } - } - } + handle_list( + name, + values, + &mut nested_original, + &mut nested_generated, + &mut derives, + ); } } } @@ -110,13 +163,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, data: Data) -> Tokens { +fn create_struct(fields: &Vec, data: Data) -> 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); quote!{ #derives @@ -140,7 +193,7 @@ fn create_non_tuple_struct(fields: &Vec, data: Data) -> Tokens { } } -fn generate_dynamic_assignments( +fn create_fields( fields: &Vec, nested_names: HashMap, ) -> (Tokens, Tokens, Tokens) { diff --git a/tests/nested.rs b/tests/nested.rs index b250678..a2da490 100644 --- a/tests/nested.rs +++ b/tests/nested.rs @@ -4,7 +4,8 @@ extern crate optional_struct; #[derive(OptionalStruct)] -#[LogConfig = "OptionalLogConfig"] +#[opt_nested_original(LogConfig)] +#[opt_nested_generated(OptionalLogConfig)] struct Config { timeout: Option, log_config: LogConfig, From ee449e0250151d9e8961b81b9b02c864a13ec7b0 Mon Sep 17 00:00:00 2001 From: Paul Lesur Date: Tue, 24 Oct 2017 00:23:51 +0200 Subject: [PATCH 2/7] readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 020b265..540443d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # OptionalStruct -## 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. 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 +82,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, log_config: LogConfig, From 0d5c977d14ff101c1e2df6b0c5d73ffa890604dd Mon Sep 17 00:00:00 2001 From: Paul Lesur Date: Tue, 24 Oct 2017 00:34:04 +0200 Subject: [PATCH 3/7] cargo.toml --- Cargo.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index ab08c0b..d629001 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,10 @@ name = "optional_struct" version = "0.1.0" authors = ["Paul Lesur "] +description = "Crate defining a macro that will generate, from a structure, another structure with only Option fields" +readme = "README.md" +categories = ["config"] +license = "Apache-2.0" [dependencies] syn = "0.11.11" @@ -9,3 +13,5 @@ quote = "0.3" [lib] proc-macro = true + + From e689594c71105029d143f006fa254a412053f2d9 Mon Sep 17 00:00:00 2001 From: Paul Lesur Date: Tue, 24 Oct 2017 00:47:10 +0200 Subject: [PATCH 4/7] add travis CI --- .travis.yml | 13 +++++++++++++ Cargo.toml | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..463e07a --- /dev/null +++ b/.travis.yml @@ -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 diff --git a/Cargo.toml b/Cargo.toml index d629001..9afb471 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,14 @@ [package] name = "optional_struct" -version = "0.1.0" +version = "0.1.1" authors = ["Paul Lesur "] description = "Crate defining a macro that will generate, from a structure, another structure with only Option fields" readme = "README.md" categories = ["config"] license = "Apache-2.0" +travis-ci = { repository = "https://github.com/pLesur/OptionalStruct", branch = "stable_channel" } + [dependencies] syn = "0.11.11" quote = "0.3" From 99b2453174e917e7229b74163e258c236bf64354 Mon Sep 17 00:00:00 2001 From: Paul Lesur Date: Tue, 24 Oct 2017 10:15:04 +0200 Subject: [PATCH 5/7] remove unstable feature macro from example/test --- tests/nested.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/nested.rs b/tests/nested.rs index a2da490..a4c121a 100644 --- a/tests/nested.rs +++ b/tests/nested.rs @@ -1,5 +1,3 @@ -#![feature(custom_attribute)] - #[macro_use] extern crate optional_struct; From 32d65449ee7e600397890438217fb93436c673b7 Mon Sep 17 00:00:00 2001 From: Paul Lesur Date: Tue, 24 Oct 2017 10:19:58 +0200 Subject: [PATCH 6/7] fix Cargo.toml --- Cargo.toml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9afb471..edc895a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,19 @@ [package] name = "optional_struct" -version = "0.1.1" +version = "0.1.3" authors = ["Paul Lesur "] -description = "Crate defining a macro that will generate, from a structure, another structure with only Option fields" +description = """ +Crate defining a macro that will generate, from a structure, another structure with only Option fields +""" readme = "README.md" categories = ["config"] +keywords = ["macro", "configuration"] license = "Apache-2.0" +repository = "https://github.com/pLesur/OptionalStruct" +exclude = [".travis.yml"] -travis-ci = { repository = "https://github.com/pLesur/OptionalStruct", branch = "stable_channel" } +[badges] +travis-ci = { repository = "pLesur/OptionalStruct", branch = "stable_channel" } [dependencies] syn = "0.11.11" From 9de042fc5dc269831df18adf09eb19c60131c185 Mon Sep 17 00:00:00 2001 From: Paul Lesur Date: Tue, 24 Oct 2017 10:36:35 +0200 Subject: [PATCH 7/7] add badges --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 540443d..23725f5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # 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] ## Goal