mirror of
https://github.com/stoatchat/rust-optional-struct.git
synced 2026-07-21 02:15:36 -04:00
Merge remote-tracking branch 'origin/stable_channel' into support_generics
This commit is contained in:
+13
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# OptionalStruct
|
||||
[](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
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user