py-rs-macros 0.1.1

derive macro for py-rs
Documentation
use std::collections::HashMap;

use syn::{parse_quote, Attribute, Ident, ItemEnum, Path, Result, Type, WherePredicate};

use super::{parse_assign_from_str, parse_bound, Attr, ContainerAttr, Serde};
use crate::{
    attr::{parse_assign_inflection, parse_assign_str, parse_concrete, Inflection},
    utils::{parse_attrs, parse_docs},
};

#[derive(Default)]
pub struct EnumAttr {
    crate_rename: Option<Path>,
    pub type_as: Option<Type>,
    pub type_override: Option<String>,
    pub rename_all: Option<Inflection>,
    pub rename_all_fields: Option<Inflection>,
    pub rename: Option<String>,
    pub export_to: Option<String>,
    pub export: bool,
    pub docs: String,
    pub concrete: HashMap<Ident, Type>,
    pub bound: Option<Vec<WherePredicate>>,
    pub tag: Option<String>,
    pub untagged: bool,
    pub content: Option<String>,
}

#[derive(Copy, Clone)]
pub enum Tagged<'a> {
    Externally,
    Adjacently { tag: &'a str, content: &'a str },
    Internally { tag: &'a str },
    Untagged,
}

impl EnumAttr {
    pub fn tagged(&self) -> Result<Tagged<'_>> {
        match (self.untagged, &self.tag, &self.content) {
            (false, None, None) => Ok(Tagged::Externally),
            (false, Some(tag), None) => Ok(Tagged::Internally { tag }),
            (false, Some(tag), Some(content)) => Ok(Tagged::Adjacently { tag, content }),
            (true, None, None) => Ok(Tagged::Untagged),
            (true, Some(_), None) => syn_err!("untagged cannot be used with tag"),
            (true, _, Some(_)) => syn_err!("untagged cannot be used with content"),
            (false, None, Some(_)) => syn_err!("content cannot be used without tag"),
        }
    }

    pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
        let mut result = parse_attrs::<Self>(attrs)?;

        if cfg!(feature = "serde-compat") {
            let serde_attr = crate::utils::parse_serde_attrs::<EnumAttr>(attrs);
            result = result.merge(serde_attr.0);
        }

        let docs = parse_docs(attrs)?;
        result.docs = docs;

        Ok(result)
    }

    pub fn crate_rename(&self) -> Path {
        self.crate_rename
            .clone()
            .unwrap_or_else(|| parse_quote!(::py_rs))
    }
}

impl Attr for EnumAttr {
    type Item = ItemEnum;

    fn merge(self, other: Self) -> Self {
        Self {
            crate_rename: self.crate_rename.or(other.crate_rename),
            type_as: self.type_as.or(other.type_as),
            type_override: self.type_override.or(other.type_override),
            rename: self.rename.or(other.rename),
            rename_all: self.rename_all.or(other.rename_all),
            rename_all_fields: self.rename_all_fields.or(other.rename_all_fields),
            tag: self.tag.or(other.tag),
            untagged: self.untagged || other.untagged,
            content: self.content.or(other.content),
            export: self.export || other.export,
            export_to: self.export_to.or(other.export_to),
            docs: other.docs,
            concrete: self.concrete.into_iter().chain(other.concrete).collect(),
            bound: match (self.bound, other.bound) {
                (Some(a), Some(b)) => Some(a.into_iter().chain(b).collect()),
                (Some(bound), None) | (None, Some(bound)) => Some(bound),
                (None, None) => None,
            },
        }
    }

    fn assert_validity(&self, item: &Self::Item) -> Result<()> {
        if self.type_override.is_some() {
            if self.type_as.is_some() {
                syn_err_spanned!(
                    item;
                    "`as` is not compatible with `type`"
                );
            }

            if self.rename_all.is_some() {
                syn_err_spanned!(
                    item;
                    "`rename_all` is not compatible with `type`"
                );
            }

            if self.rename_all_fields.is_some() {
                syn_err_spanned!(
                    item;
                    "`rename_all_fields` is not compatible with `type`"
                );
            }

            if self.tag.is_some() {
                syn_err_spanned!(
                    item;
                    "`tag` is not compatible with `type`"
                );
            }

            if self.content.is_some() {
                syn_err_spanned!(
                    item;
                    "`content` is not compatible with `type`"
                );
            }

            if self.untagged {
                syn_err_spanned!(
                    item;
                    "`untagged` is not compatible with `type`"
                );
            }
        }

        if self.type_as.is_some() {
            if self.rename_all.is_some() {
                syn_err_spanned!(
                    item;
                    "`rename_all` is not compatible with `as`"
                );
            }

            if self.rename_all_fields.is_some() {
                syn_err_spanned!(
                    item;
                    "`rename_all_fields` is not compatible with `as`"
                );
            }

            if self.tag.is_some() {
                syn_err_spanned!(
                    item;
                    "`tag` is not compatible with `as`"
                );
            }

            if self.content.is_some() {
                syn_err_spanned!(
                    item;
                    "`content` is not compatible with `as`"
                );
            }

            if self.untagged {
                syn_err_spanned!(
                    item;
                    "`untagged` is not compatible with `as`"
                );
            }
        }

        match (self.untagged, &self.tag, &self.content) {
            (true, Some(_), None) => syn_err_spanned!(
                item;
                "untagged cannot be used with tag"
            ),
            (true, _, Some(_)) => syn_err_spanned!(
                item;
                "untagged cannot be used with content"
            ),
            (false, None, Some(_)) => syn_err_spanned!(
                item;
                "content cannot be used without tag"
            ),
            _ => (),
        };

        Ok(())
    }
}

impl ContainerAttr for EnumAttr {
    fn crate_rename(&self) -> Path {
        self.crate_rename
            .clone()
            .unwrap_or_else(|| parse_quote!(::py_rs))
    }
}

impl_parse! {
    EnumAttr(input, out) {
        "crate" => out.crate_rename = Some(parse_assign_from_str(input)?),
        "as" => out.type_as = Some(parse_assign_from_str(input)?),
        "type" => out.type_override = Some(parse_assign_str(input)?),
        "rename" => out.rename = Some(parse_assign_str(input)?),
        "rename_all" => out.rename_all = Some(parse_assign_inflection(input)?),
        "rename_all_fields" => out.rename_all_fields = Some(parse_assign_inflection(input)?),
        "export_to" => out.export_to = Some(parse_assign_str(input)?),
        "export" => out.export = true,
        "tag" => out.tag = Some(parse_assign_str(input)?),
        "content" => out.content = Some(parse_assign_str(input)?),
        "untagged" => out.untagged = true,
        "concrete" => out.concrete = parse_concrete(input)?,
        "bound" => out.bound = Some(parse_bound(input)?),
    }
}

impl_parse! {
    Serde<EnumAttr>(input, out) {
        "rename" => out.0.rename = Some(parse_assign_str(input)?),
        "rename_all" => out.0.rename_all = Some(parse_assign_inflection(input)?),
        "rename_all_fields" => out.0.rename_all_fields = Some(parse_assign_inflection(input)?),
        "tag" => out.0.tag = Some(parse_assign_str(input)?),
        "content" => out.0.content = Some(parse_assign_str(input)?),
        "untagged" => out.0.untagged = true,
        "bound" => out.0.bound = Some(parse_bound(input)?),
    }
}