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 ab08c0b..edc895a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,19 @@ [package] name = "optional_struct" -version = "0.1.0" +version = "0.1.3" 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"] +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 + + diff --git a/README.md b/README.md index 020b265..23725f5 100644 --- a/README.md +++ b/README.md @@ -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. 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, log_config: LogConfig, diff --git a/src/lib.rs b/src/lib.rs index 083a1ed..44ab34c 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] @@ -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, 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"), - }, - &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, data: Data, generics: &Generics) -> Tokens { +fn create_struct(fields: &Vec, 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, data: Data, generics: &Generics) } } -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..a4c121a 100644 --- a/tests/nested.rs +++ b/tests/nested.rs @@ -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, log_config: LogConfig,