From d002cabe95c51dc4802a2617c7bbb3501645fd2c Mon Sep 17 00:00:00 2001 From: Marc-Antoine Arnaud Date: Mon, 9 Apr 2018 19:04:11 +0200 Subject: [PATCH] initial commit --- Cargo.toml | 5 + yaserde/Cargo.toml | 12 + yaserde/src/lib.rs | 18 ++ yaserde/tests/deserializer.rs | 163 ++++++++++++++ yaserde_derive/Cargo.toml | 13 ++ yaserde_derive/src/der/attribute.rs | 83 +++++++ yaserde_derive/src/der/expand_struct.rs | 285 ++++++++++++++++++++++++ yaserde_derive/src/der/field_type.rs | 70 ++++++ yaserde_derive/src/der/mod.rs | 43 ++++ yaserde_derive/src/lib.rs | 38 ++++ 10 files changed, 730 insertions(+) create mode 100644 Cargo.toml create mode 100644 yaserde/Cargo.toml create mode 100644 yaserde/src/lib.rs create mode 100644 yaserde/tests/deserializer.rs create mode 100644 yaserde_derive/Cargo.toml create mode 100644 yaserde_derive/src/der/attribute.rs create mode 100644 yaserde_derive/src/der/expand_struct.rs create mode 100644 yaserde_derive/src/der/field_type.rs create mode 100644 yaserde_derive/src/der/mod.rs create mode 100644 yaserde_derive/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fe4f64d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "yaserde", + "yaserde_derive", +] diff --git a/yaserde/Cargo.toml b/yaserde/Cargo.toml new file mode 100644 index 0000000..4e191ed --- /dev/null +++ b/yaserde/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "yaserde" +version = "0.1.0" +authors = ["Marc-Antoine Arnaud "] + +[dependencies] +yaserde_derive = { path = "../yaserde_derive", optional = true } +regex = "0.2" +xml-rs = "0.7.0" + +[dev-dependencies] +yaserde_derive = { path = "../yaserde_derive" } diff --git a/yaserde/src/lib.rs b/yaserde/src/lib.rs new file mode 100644 index 0000000..45ec9bb --- /dev/null +++ b/yaserde/src/lib.rs @@ -0,0 +1,18 @@ + +extern crate xml; +#[cfg(feature = "yaserde_derive")] +#[allow(unused_imports)] +#[macro_use] +extern crate yaserde_derive; + +use std::io::Read; +use xml::EventReader; +use xml::attribute::OwnedAttribute; + +pub trait YaDeserialize : Sized { + fn derive_deserialize(read: &mut EventReader, parent_attributes: Option<&Vec>) -> Result; +} + +pub trait YaSerialize { + fn derive_serialize(); +} diff --git a/yaserde/tests/deserializer.rs b/yaserde/tests/deserializer.rs new file mode 100644 index 0000000..d75ac67 --- /dev/null +++ b/yaserde/tests/deserializer.rs @@ -0,0 +1,163 @@ + +extern crate yaserde; +#[macro_use] +extern crate yaserde_derive; +extern crate xml; + +use std::io::Read; +use xml::reader::EventReader; +use yaserde::{YaDeserialize, YaSerialize}; + +#[test] +fn test_basic() { + #[derive(YaDeserialize, YaSerialize, PartialEq, Debug)] + #[yaserde(root="base")] + pub struct XmlStruct { + item: String + } + + let content = "something".to_string(); + let mut parser = EventReader::from_str(content.as_str()); + + let loaded = XmlStruct::derive_deserialize(&mut parser, None); + assert_eq!(loaded, Ok(XmlStruct{ + item: "something".to_string() + })); +} + +#[test] +fn test_list_of_items() { + #[derive(YaDeserialize, YaSerialize, PartialEq, Debug)] + #[yaserde(root="base")] + pub struct XmlStruct { + items: Vec + } + + let content = "something1something2".to_string(); + let mut parser = EventReader::from_str(content.as_str()); + + let loaded = XmlStruct::derive_deserialize(&mut parser, None); + assert_eq!(loaded, Ok(XmlStruct{ + items: vec![ + "something1".to_string(), + "something2".to_string() + ] + })); +} + +#[test] +fn test_attributes() { + #[derive(YaDeserialize, YaSerialize, PartialEq, Debug)] + #[yaserde(root="base")] + pub struct XmlStruct { + #[yaserde(attribute)] + item: String, + sub: SubStruct + } + + #[derive(YaDeserialize, YaSerialize, PartialEq, Debug)] + #[yaserde(root="sub")] + pub struct SubStruct { + #[yaserde(attribute)] + subitem: String + } + + impl Default for SubStruct { + fn default() -> SubStruct { + SubStruct{ + subitem: "".to_string() + } + } + } + + let content = "".to_string(); + let mut parser = EventReader::from_str(content.as_str()); + + let loaded = XmlStruct::derive_deserialize(&mut parser, None); + assert_eq!(loaded, Ok(XmlStruct{ + item: "something".to_string(), + sub: SubStruct{ + subitem: "sub-something".to_string() + } + })); +} + +#[test] +fn test_rename() { + #[derive(YaDeserialize, YaSerialize, PartialEq, Debug)] + #[yaserde(root="base")] + pub struct XmlStruct { + #[yaserde(attribute, rename="Item")] + item: String, + #[yaserde(rename="sub")] + sub_struct: SubStruct + } + + #[derive(YaDeserialize, YaSerialize, PartialEq, Debug)] + #[yaserde(root="sub")] + pub struct SubStruct { + #[yaserde(attribute, rename="sub_item")] + subitem: String, + } + + impl Default for SubStruct { + fn default() -> SubStruct { + SubStruct{ + subitem: "".to_string() + } + } + } + + let content = "".to_string(); + let mut parser = EventReader::from_str(content.as_str()); + + let loaded = XmlStruct::derive_deserialize(&mut parser, None); + assert_eq!(loaded, Ok(XmlStruct{ + item: "something".to_string(), + sub_struct: SubStruct{ + subitem: "sub_something".to_string() + } + })); +} + +#[test] +fn test_text_content_with_attributes() { + #[derive(YaDeserialize, YaSerialize, PartialEq, Debug)] + #[yaserde(root="base")] + pub struct XmlStruct { + #[yaserde(attribute, rename="Item")] + item: String, + #[yaserde(rename="sub")] + sub_struct: SubStruct + } + + #[derive(YaDeserialize, YaSerialize, PartialEq, Debug)] + #[yaserde(root="sub")] + pub struct SubStruct { + #[yaserde(attribute, rename="sub_item")] + subitem: String, + #[yaserde(text)] + text: String + } + + impl Default for SubStruct { + fn default() -> SubStruct { + SubStruct{ + subitem: "".to_string(), + text: "".to_string(), + } + } + } + + let content = "text_content".to_string(); + let mut parser = EventReader::from_str(content.as_str()); + + let loaded = XmlStruct::derive_deserialize(&mut parser, None); + assert_eq!(loaded, Ok(XmlStruct{ + item: "something".to_string(), + sub_struct: SubStruct{ + subitem: "sub_something".to_string(), + text: "text_content".to_string() + } + })); +} diff --git a/yaserde_derive/Cargo.toml b/yaserde_derive/Cargo.toml new file mode 100644 index 0000000..8f883ed --- /dev/null +++ b/yaserde_derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "yaserde_derive" +version = "0.1.0" +authors = ["Marc-Antoine Arnaud "] + +[dependencies] +syn = { version = "0.12.14", features = ["visit", "extra-traits"] } +proc-macro2 = "0.2.3" +quote = "0.4.2" + +[lib] +name = "yaserde_derive" +proc-macro = true diff --git a/yaserde_derive/src/der/attribute.rs b/yaserde_derive/src/der/attribute.rs new file mode 100644 index 0000000..156c12a --- /dev/null +++ b/yaserde_derive/src/der/attribute.rs @@ -0,0 +1,83 @@ + +use proc_macro2::TokenNode::*; +use proc_macro2::Delimiter::Parenthesis; +use syn::Attribute; + +#[derive(Debug, Clone)] +pub struct YaSerdeAttribute { + pub root: Option, + pub rename: Option, + pub attribute: bool, + pub text: bool, +} + +impl YaSerdeAttribute { + pub fn parse(attrs: &Vec) -> YaSerdeAttribute { + + let mut root = None; + let mut rename = None; + let mut attribute = false; + let mut text = false; + + for attr in attrs.iter() { + let mut attr_iter = attr.clone().tts.into_iter(); + match attr_iter.next() { + Some(token) => { + match token.kind { + Group(Parenthesis, token_stream) => { + let mut attr_iter = token_stream.into_iter(); + + while let Some(item) = attr_iter.next() { + match item.kind { + Term(t) => { + match t.as_str() { + "root" => { + attr_iter.next(); + let v = attr_iter.next().map(|s| + match s.kind { + Literal(l) => { + Some(l.to_string().replace("\"", "")) + }, + _ => None + }); + root = v.unwrap_or(None); + }, + "rename" => { + attr_iter.next(); + let v = attr_iter.next().map(|s| + match s.kind { + Literal(l) => { + Some(l.to_string().replace("\"", "")) + }, + _ => None + }); + rename = v.unwrap_or(None); + }, + "attribute" => { + attribute = true; + } + "text" => { + text = true; + } + _ => {}, + } + }, + _ => {} + } + } + }, + _ => {}, + } + }, + None => {}, + } + } + + YaSerdeAttribute { + root: root, + rename: rename, + attribute: attribute, + text: text, + } + } +} diff --git a/yaserde_derive/src/der/expand_struct.rs b/yaserde_derive/src/der/expand_struct.rs new file mode 100644 index 0000000..8de9ae5 --- /dev/null +++ b/yaserde_derive/src/der/expand_struct.rs @@ -0,0 +1,285 @@ + +use der::attribute::*; +use der::field_type::*; +use quote::Tokens; +use syn::Ident; +use syn::DataStruct; +use syn::punctuated::Pair; +use syn::Type::Path; +use proc_macro2::Span; + +pub fn parse(data_struct: &DataStruct, name: &Ident, root: &String, _root_attributes: &YaSerdeAttribute) -> Tokens { + let variables : Tokens = data_struct.fields.iter().map(|ref field| + { + let label = field.ident; + + match field.ty { + Path(ref path) => { + match path.path.segments.first() { + Some(Pair::End(t)) => { + let pair = path.path.segments.first().unwrap(); + + match t.ident.to_string().as_str() { + "String" => { + Some(quote!{ + let mut #label : #pair = "".to_string(); + }) + }, + "Vec" => { + Some(quote!{ + let mut #label : #pair = vec![]; + }) + }, + _ => { + Some(quote!{ + let mut #label : #pair = #pair::default(); + }) + }, + } + }, + _ => { + None + }, + } + }, + _ => {None}, + } + }) + .filter(|x| x.is_some()) + .map(|x| x.unwrap()) + .fold(Tokens::new(), |mut sum, val| {sum.append_all(val); sum}); + + let attributes_loading: Tokens = data_struct.fields.iter().map(|ref field| + match get_field_type(field) { + Some(FieldType::FieldTypeString) => { + let label = field.ident; + let field_attrs = YaSerdeAttribute::parse(&field.attrs); + + match (field_attrs.attribute, field_attrs.rename) { + (true, Some(value)) => { + let label_name = Ident::new(&format!("{}", value), Span::call_site()).to_string(); + Some(quote!{ + match current_attributes { + Some(attributes) => + for attr in attributes { + if attr.name.local_name == #label_name { + #label = attr.value.to_owned(); + } + }, + None => {}, + } + }) + }, + (true, None) => { + let label_name = field.ident.unwrap().to_string(); + Some(quote!{ + match current_attributes { + Some(attributes) => + for attr in attributes { + if attr.name.local_name == #label_name { + #label = attr.value.to_owned(); + } + }, + None => {}, + } + }) + } + _ => None + } + } + _ => { + None + } + }) + .filter(|x| x.is_some()) + .map(|x| x.unwrap()) + .fold(Tokens::new(), |mut sum, val| {sum.append_all(val); sum}); + + let assign_text_field: Tokens = data_struct.fields.iter().map(|ref field| + match get_field_type(field) { + Some(FieldType::FieldTypeString) => { + let label = field.ident; + let field_attrs = YaSerdeAttribute::parse(&field.attrs); + + match field_attrs.text { + true => { + Some(quote!{ + #label = characters_content.to_owned(); + }) + }, + false => None + } + } + _ => { + None + } + }) + .filter(|x| x.is_some()) + .map(|x| x.unwrap()) + .fold(Tokens::new(), |mut sum, val| {sum.append_all(val); sum}); + + let fields : Tokens = data_struct.fields.iter().map(|ref field| + { + let field_attrs = YaSerdeAttribute::parse(&field.attrs); + let label = field.ident; + let renamed_label = + match field_attrs.rename { + Some(value) => Some(Ident::new(&format!("{}", value), Span::call_site())), + None => field.ident + }; + + let label_name = renamed_label.unwrap().to_string(); + match get_field_type(field) { + Some(FieldType::FieldTypeString) => { + Some(quote!{ + #label_name => { + match read.next() { + Ok(xml::reader::XmlEvent::Characters(characters_content)) => { + #label = characters_content.trim().to_string(); + }, + _ => {}, + } + }, + }) + }, + Some(FieldType::FieldTypeStruct{name}) => { + let struct_ident = Ident::new(&format!("{}", name), Span::def_site()); + + Some(quote!{ + #label_name => { + match #struct_ident::derive_deserialize(read, Some(&attributes)) { + Ok(parsed_structure) => { + prev_level -= 1; + #label = parsed_structure; + }, + Err(msg) => { + println!("ERROR {:?}", msg); + }, + } + }, + }) + }, + Some(FieldType::FieldTypeVec) => { + match get_vec_type(field) { + Some(identifier) => { + match identifier.to_string().as_str() { + "String" => { + Some(quote!{ + #label_name => { + match read.next() { + Ok(xml::reader::XmlEvent::Characters(characters_content)) => { + #label.push(characters_content.trim().to_string()); + }, + _ => {}, + } + }, + }) + }, + struct_name => { + let struct_ident = Ident::new(&format!("{}", struct_name), Span::def_site()); + Some(quote!{ + #label_name => { + match #struct_ident::derive_deserialize(read, Some(&attributes)) { + Ok(parsed_item) => { + prev_level -= 1; + #label.push(parsed_item); + }, + Err(msg) => { + println!("ERROR {:?}", msg); + }, + } + }, + }) + } + } + }, + None => None + } + }, + _ => None + } + }) + .filter(|x| x.is_some()) + .map(|x| x.unwrap()) + .fold(Tokens::new(), |mut sum, val| {sum.append_all(val); sum}); + + let struct_builder : Tokens = data_struct.fields.iter().map(|ref field| + { + let label = field.ident; + + match get_field_type(field) { + Some(FieldType::FieldTypeString) | + Some(FieldType::FieldTypeStruct{..}) | + Some(FieldType::FieldTypeVec) => + Some(quote!{ + #label: #label, + }), + None => None, + } + }) + .filter(|x| x.is_some()) + .map(|x| x.unwrap()) + .fold(Tokens::new(), |mut tokens, token| {tokens.append_all(token); tokens}); + + quote! { + use xml::reader::XmlEvent; + + impl YaDeserialize for #name { + #[allow(unused_variables)] + fn derive_deserialize(read: &mut xml::EventReader, parent_attributes: Option<&Vec>) -> Result { + let mut prev_level = 0; + let mut current_level = 0; + + #variables + let current_attributes = parent_attributes; + #attributes_loading + + loop { + match read.next() { + Ok(XmlEvent::StartDocument{..}) => { + }, + Ok(XmlEvent::EndDocument) => { + break; + }, + Ok(XmlEvent::StartElement{name, attributes, namespace: _namespace}) => { + // println!("{} | {} - {}: {}", #root, prev_level, current_level, name.local_name.as_str()); + if prev_level == current_level { + match name.local_name.as_str() { + #root => { + let root_attributes = attributes.clone(); + let current_attributes = Some(&root_attributes); + #attributes_loading + + current_level += 1; + }, + #fields + _ => {} + }; + } + + prev_level += 1; + }, + Ok(XmlEvent::EndElement{name}) => { + if #root == name.local_name.as_str() { + // println!("BREAK {}", #root); + break; + } + prev_level -= 1; + } + Ok(xml::reader::XmlEvent::Characters(characters_content)) => { + if prev_level == current_level { + #assign_text_field + } + }, + Ok(_event) => { + }, + Err(_msg) => { + break; + }, + } + } + Ok(#name{#struct_builder}) + } + } + } +} diff --git a/yaserde_derive/src/der/field_type.rs b/yaserde_derive/src/der/field_type.rs new file mode 100644 index 0000000..79f2ff5 --- /dev/null +++ b/yaserde_derive/src/der/field_type.rs @@ -0,0 +1,70 @@ + +use syn; +use syn::punctuated::Pair; +use syn::Type::Path; + +#[derive(Debug)] +pub enum FieldType { + FieldTypeString, + FieldTypeVec, + FieldTypeStruct{name: String}, +} + +pub fn get_field_type(field: &syn::Field) -> Option { + match field.ty { + Path(ref path) => { + match path.path.segments.first() { + Some(Pair::End(t)) => { + match t.ident.to_string().as_str() { + "String" => Some(FieldType::FieldTypeString), + "Vec" => Some(FieldType::FieldTypeVec), + name => Some(FieldType::FieldTypeStruct{name: name.to_string()}), + } + }, + _ => { + None + }, + } + }, + _ => {None}, + } +} + +pub fn get_vec_type(field: &syn::Field) -> Option { + match field.ty { + Path(ref path) => { + match path.path.segments.first() { + Some(Pair::End(t)) => { + match t.arguments { + syn::PathArguments::AngleBracketed(ref args) => { + match args.args.first() { + Some(Pair::End(tt)) => { + match tt { + &syn::GenericArgument::Type(ref argument) => { + match argument { + &Path(ref path2) => { + match path2.path.segments.first() { + Some(Pair::End(ttt)) => { + Some(ttt.ident) + }, + _ => None + } + }, + _ => None + } + }, + _ => None + } + }, + _ => None + } + }, + _ => None + } + }, + _ => None + } + } + _ => None + } +} \ No newline at end of file diff --git a/yaserde_derive/src/der/mod.rs b/yaserde_derive/src/der/mod.rs new file mode 100644 index 0000000..ff0d6a1 --- /dev/null +++ b/yaserde_derive/src/der/mod.rs @@ -0,0 +1,43 @@ + +pub mod attribute; +pub mod expand_struct; +pub mod field_type; + +use proc_macro2::Span; +use quote; +use syn; +use syn::Ident; + +pub fn expand_derive_deserialize(ast: &syn::DeriveInput) -> Result { + let name = &ast.ident; + let attrs = &ast.attrs; + let data = &ast.data; + + let root_attrs = attribute::YaSerdeAttribute::parse(&attrs); + let root = root_attrs.clone().root.unwrap_or(name.to_string()); + + let impl_block = + match data { + &syn::Data::Struct(ref data_struct) => { + expand_struct::parse(data_struct, &name, &root, &root_attrs) + }, + &syn::Data::Enum(ref _data_enum) => { + unimplemented!() + }, + &syn::Data::Union(ref _data_union) => { + unimplemented!() + }, + }; + + let dummy_const = Ident::new(&format!("_IMPL_DESERIALIZE_FOR_{}", name), Span::def_site()); + + let generated = quote! { + #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] + const #dummy_const: () = { + extern crate yaserde as _yaserde; + #impl_block + }; + }; + + Ok(generated) +} diff --git a/yaserde_derive/src/lib.rs b/yaserde_derive/src/lib.rs new file mode 100644 index 0000000..0f9772e --- /dev/null +++ b/yaserde_derive/src/lib.rs @@ -0,0 +1,38 @@ +#![recursion_limit="128"] + +extern crate proc_macro; +extern crate proc_macro2; +#[macro_use] +extern crate quote; +extern crate syn; + +mod der; + +use proc_macro::TokenStream; + +fn expand_derive_serialize(ast: &syn::DeriveInput) -> quote::Tokens { + let name = &ast.ident; + quote! { + impl YaSerialize for #name { + fn derive_serialize() { + println!("serialize {}", stringify!(#name)); + } + } + } +} + +#[proc_macro_derive(YaDeserialize, attributes(yaserde))] +pub fn derive_deserialize(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + match der::expand_derive_deserialize(&ast) { + Ok(expanded) => expanded.into(), + Err(msg) => panic!(msg), + } +} + +#[proc_macro_derive(YaSerialize)] +pub fn derive_serialize(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + let gen = expand_derive_serialize(&ast); + gen.into() +}