parsable-macro 0.1.1

Macro to generate the `parsable` trait on enums or structs.
Documentation
use proc_macro2::TokenStream;
use proc_macro_error::emit_call_site_error;
use syn::{*, parse::{Parse, ParseStream}};
use crate::markers::MarkerOutput;

#[derive(Default)]
pub struct FieldAttributes {
    pub value: Option<String>,
    pub regex: Option<String>,
    pub prefix: Option<String>,
    pub suffix: Option<String>,
    pub min: Option<usize>,
    pub separator: Option<String>,
    pub optional: Option<bool>,
    pub cascade: Option<bool>,
    pub consume_spaces: Option<bool>,
    pub consume_spaces_after_prefix: Option<bool>,
    pub consume_spaces_after_suffix: Option<bool>,
    pub consume_spaces_between_items: Option<bool>,
    pub exclude: Option<String>,
    pub followed_by: Option<String>,
    pub not_followed_by: Option<String>,
    pub declared_markers: Vec<LitStr>,
    pub set_markers: Vec<LitStr>,
    pub unset_markers: Vec<LitStr>,
    pub ignore_if_marker: Vec<LitStr>,
    pub ignore_if_not_marker: Vec<LitStr>,
    pub ignore: bool,
}

impl Parse for FieldAttributes {
    #[allow(unused_must_use)]
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut attributes = FieldAttributes::default();
        let content;

        parenthesized!(content in input);

        while !content.is_empty() {
            let name = content.parse::<Ident>()?.to_string();

            if name.as_str() == "ignore" {
                attributes.ignore = true;
            } else {
                content.parse::<Token![=]>()?;

                match name.as_str() {
                    "value" => attributes.value = Some(content.parse::<LitStr>()?.value()),
                    "regex" => attributes.regex = Some(content.parse::<LitStr>()?.value()),
                    "prefix" => attributes.prefix = Some(content.parse::<LitStr>()?.value()),
                    "suffix" => attributes.suffix = Some(content.parse::<LitStr>()?.value()),
                    "brackets" => {
                        let brackets = content.parse::<LitStr>()?.value();

                        if brackets.len() == 2 {
                            attributes.prefix = Some((brackets.as_bytes()[0] as char).to_string());
                            attributes.suffix = Some((brackets.as_bytes()[1] as char).to_string());
                        }
                    },
                    "min" => attributes.min = Some(content.parse::<LitInt>()?.base10_parse::<usize>()?),
                    "sep" => attributes.separator = Some(content.parse::<LitStr>()?.value()),
                    "separator" => attributes.separator = Some(content.parse::<LitStr>()?.value()),
                    "optional" => attributes.optional = Some(content.parse::<LitBool>()?.value()),
                    "cascade" => attributes.cascade = Some(content.parse::<LitBool>()?.value()),
                    "followed_by" => attributes.followed_by = Some(content.parse::<LitStr>()?.value()),
                    "not_followed_by" => attributes.not_followed_by = Some(content.parse::<LitStr>()?.value()),
                    "exclude" => attributes.exclude = Some(content.parse::<LitStr>()?.value()),
                    "declare_marker" => attributes.declared_markers.push(content.parse::<LitStr>()?),
                    "set_marker" => attributes.set_markers.push(content.parse::<LitStr>()?),
                    "unset_marker" => attributes.unset_markers.push(content.parse::<LitStr>()?),
                    "ignore_if_marker" => attributes.ignore_if_marker.push(content.parse::<LitStr>()?),
                    "ignore_if_not_marker" => attributes.ignore_if_not_marker.push(content.parse::<LitStr>()?),
                    "consume_spaces" => attributes.consume_spaces = Some(content.parse::<LitBool>()?.value()),
                    "consume_spaces_after_prefix" => attributes.consume_spaces_after_prefix = Some(content.parse::<LitBool>()?.value()),
                    "consume_spaces_after_suffix" => attributes.consume_spaces_after_suffix = Some(content.parse::<LitBool>()?.value()),
                    "consume_spaces_between_items" => attributes.consume_spaces_between_items = Some(content.parse::<LitBool>()?.value()),
                    _ => {}
                }
            }

            if !content.is_empty() {
                content.parse::<Token![,]>()?;
            }
        }

        Ok(attributes)
    }
}

impl FieldAttributes {
    pub fn from_field_attributes(attrs: &mut Vec<Attribute>) -> Self {
        let mut attributes = Self::default();

        if let Some((i, attr)) = attrs.iter().enumerate().find(|(_, attr)| attr.path.segments.last().unwrap().ident == "parsable") {
            let result = syn::parse2::<FieldAttributes>(attr.tokens.clone());

            match result {
                Ok(value) => attributes = value,
                Err(error) => emit_call_site_error!(error)
            };

            attrs.remove(i);
        }

        attributes
    }

    pub fn get_push_pop_markers(&self, field_index: usize) -> (TokenStream, TokenStream, TokenStream) {
        MarkerOutput::from_attributes(&self.declared_markers, &self.set_markers, &self.unset_markers, Some(field_index)).to_tuple()
    }
}