logisheets_derives 0.3.0

macros that help LogiSheets serde the xml files
Documentation
use syn::DeriveInput;

use crate::container::{Container, Derive, FieldsSummary, Generic, StructField};

pub fn get_ser_impl_block(input: DeriveInput) -> proc_macro2::TokenStream {
    let container = Container::from_ast(&input, Derive::Serialize);
    if container.is_enum() {
        get_ser_enum_impl_block(container)
    } else {
        get_ser_struct_impl_block(container)
    }
}

fn get_ser_enum_impl_block(container: Container) -> proc_macro2::TokenStream {
    let ident = &container.original.ident;
    let branches = container.enum_variants.iter().map(|v| {
        let f = v.ident;
        let name = &v.name;
        quote! {
            Self::#f(c) => c.serialize(#name, writer),
        }
    });
    quote! {
        #[allow(unused_must_use)]
        impl crate::XmlSerialize for #ident {
            fn serialize<W: std::io::Write>(
                &self,
                tag: &[u8],
                writer: &mut quick_xml::Writer<W>,
            ) {
                use quick_xml::events::*;
                let _ = writer.write_event(Event::Start(BytesStart::borrowed_name(tag)));
                match self {
                    #(#branches)*
                }
                let _ = writer.write_event(Event::End(BytesEnd::borrowed(tag)));
            }
        }
    }
}

fn get_ser_struct_impl_block(container: Container) -> proc_macro2::TokenStream {
    let write_ns = match container.with_ns {
        Some(ns) => quote! {
            attrs.push(Attribute::from((b"xmlns".as_ref(), #ns.as_ref())));
        },
        None => quote! {},
    };
    let write_custom_ns = if container.custom_ns.len() == 0 {
        quote! {}
    } else {
        let cns = container.custom_ns.into_iter().map(|(ns, value)| {
            quote! {
                let mut __vec = b"xmlns:".to_vec();
                __vec.extend(#ns.to_vec());
                attrs.push(Attribute::from((__vec.as_ref(), #value.as_ref())));
            }
        });
        quote! {#(#cns)*}
    };
    let FieldsSummary {
        children,
        text,
        attrs,
        self_closed_children,
    } = FieldsSummary::from_fields(container.struct_fields);
    if text.is_some() && (children.len() > 0 || self_closed_children.len() > 0) {
        panic!("Cannot owns the text and children at the same time.")
    }
    let init = init_is_empty(&children, &self_closed_children, &text);
    let build_attr_and_push = attrs.into_iter().map(|attr| {
        let name = attr.name.as_ref().unwrap();
        let ident = attr.original.ident.as_ref().unwrap();
        match &attr.generic {
            Generic::Vec(_) => panic!("cannot use a vector in attribute"),
            Generic::Opt(_) => {
                quote! {
                    let mut sr: String;
                    match &self.#ident {
                        Some(v) => {
                            sr = v.serialize();
                            attrs.push(Attribute::from((#name.as_ref(), sr.as_bytes())));
                        },
                        None => {},
                    }
                }
            }
            Generic::None => match &attr.default {
                Some(path) => quote! {
                    let mut ser;
                    if #path() != self.#ident {
                        ser = self.#ident.serialize();
                        attrs.push(Attribute::from((#name.as_ref(), ser.as_bytes())));
                    }
                },
                None => quote! {
                    let ser = self.#ident.serialize();
                    attrs.push(Attribute::from((#name.as_ref(), ser.as_bytes())));
                },
            },
        }
    });
    let write_text_or_children = if let Some(t) = text {
        let ident = t.original.ident.as_ref().unwrap();
        if t.generic.is_opt() {
            quote! {
                match &self.#ident {
                    None => {},
                    Some(__d) => {
                        let r = __d.serialize();
                        let event = BytesText::from_plain_str(&r);
                        writer.write_event(Event::Text(event));
                    }
                }
            }
        } else {
            quote! {
                let r = self.#ident.serialize();
                let event = BytesText::from_plain_str(&r);
                writer.write_event(Event::Text(event));
            }
        }
    } else {
        let write_scf = self_closed_children.into_iter().map(|f| {
            let ident = f.original.ident.as_ref().unwrap();
            let name = f.name.as_ref().unwrap();
            quote! {
                if self.#ident {
                    let event = BytesStart::borrowed_name(#name);
                    writer.write_event(Event::Empty(event));
                }
            }
        });
        let write_children = children.into_iter().map(|f| {
            if f.skip_serializing {
                quote! {}
            } else {
                let ident = f.original.ident.as_ref().unwrap();
                let name = f.name.as_ref().unwrap();
                quote! {
                    self.#ident.serialize(#name, writer);
                }
            }
        });
        quote! {
            #(#write_scf)*
            #(#write_children)*
        }
    };
    let ident = &container.original.ident;
    let write_event = quote! {
        if is_empty {
            writer.write_event(Event::Empty(start));
        } else {
            writer.write_event(Event::Start(start));
            #write_text_or_children
            let end = BytesEnd::borrowed(tag);
            writer.write_event(Event::End(end));
        }
    };
    quote! {
        #[allow(unused_must_use)]
        impl crate::XmlSerialize for #ident {
            fn serialize<W: std::io::Write>(
                &self,
                tag: &[u8],
                writer: &mut quick_xml::Writer<W>,
            ) {
                use quick_xml::events::*;
                use quick_xml::events::attributes::Attribute;
                use crate::XmlValue;
                let start = BytesStart::borrowed_name(tag);
                let mut attrs = Vec::<Attribute>::new();
                #write_ns
                #write_custom_ns
                #(#build_attr_and_push)*
                let start = start.with_attributes(attrs);
                #init
                #write_event
            }
        }
    }
}

fn init_is_empty(
    children: &Vec<StructField>,
    scf: &Vec<StructField>,
    text: &Option<StructField>,
) -> proc_macro2::TokenStream {
    let children_init = children.iter().map(|c| {
        let ident = c.original.ident.as_ref().unwrap();
        match &c.generic {
            Generic::Vec(_) => quote! {
                let #ident = self.#ident.len() > 0;
            },
            Generic::Opt(_) => quote! {
                let #ident = self.#ident.is_some();
            },
            Generic::None => match &c.default {
                Some(d) => quote! {
                    let #ident = self.#ident != #d();
                },
                None => quote! {let #ident = true;},
            },
        }
    });
    let scf_init = scf.iter().map(|s| {
        let ident = s.original.ident.as_ref().unwrap();
        quote! {
            let #ident = self.#ident;
        }
    });
    let text_init = match text {
        Some(tf) => {
            let ident = tf.original.ident.as_ref().unwrap();
            if tf.generic.is_opt() {
                quote! {
                    let mut has_text = true;
                    if self.#ident.is_none() {
                        has_text = false;
                    }
                }
            } else if tf.default.is_none() {
                quote! {let has_text = true;}
            } else {
                let path = tf.default.as_ref().unwrap();
                quote! {
                    let mut has_text = true;
                    if self.#ident == #path() {
                        has_text = false;
                    }
                }
            }
        }
        None => quote! {let has_text = false;},
    };
    let is_empty = {
        let idents = children.iter().chain(scf.iter()).map(|c| {
            let ident = c.original.ident.as_ref().unwrap();
            quote! {#ident}
        });
        quote! {
            let has_child_to_write = #(#idents ||)* has_text;
            let is_empty = !has_child_to_write;
        }
    };
    quote! {
        #(#children_init)*
        #(#scf_init)*
        #text_init
        #is_empty
    }
}