ircv3_parse_derive 4.0.0

Derive macro for ircv3_parse
Documentation
use proc_macro2::{Span, TokenTree};
use syn::{
    meta::ParseNestedMeta, spanned::Spanned, token::Eq, Attribute, Error, Ident, LitInt, LitStr,
    Path, Result,
};

use crate::{component_set::ComponentSet, error_msg};

use super::{
    parse_lit_str, parse_required_lit_str, Rename, Source, COMMAND, CRLF, DEFAULT, IRC, PARAM,
    PICK, PRESENT, RENAME_ALL, SKIP, SOURCE, TAG, TAG_FLAG, TRAILING, VALUE,
};

pub struct EnumAttrs {
    pub kind: EnumKind,
    pub rename_all: Rename,
    pub default: Option<LitStr>,
    pub crlf: bool,
    pub unknown: Vec<Path>,
}

pub enum EnumKind {
    Tag(LitStr),
    TagFlag(LitStr),
    Source(Source),
    Param(LitInt),
    Trailing,
    Command,
}

pub struct VariantAttrs {
    pub values: Vec<LitStr>,
    pub pick: Option<LitStr>,
    pub present: Option<Span>,
    pub skip: bool,
    pub unknown: Vec<Path>,
}

impl EnumAttrs {
    pub fn parse(attrs: &[Attribute], ident: &Ident) -> Result<Self> {
        let mut kind: Option<EnumKind> = None;
        let mut kind_span: Option<Span> = None;
        let mut rename_all: Option<Rename> = None;
        let mut rename_all_span: Option<Span> = None;
        let mut default: Option<LitStr> = None;
        let mut default_span: Option<Span> = None;
        let mut crlf = false;
        let mut crlf_span: Option<Span> = None;
        let mut unknown = Vec::new();

        for attr in attrs {
            if !attr.path().is_ident(IRC) {
                continue;
            }

            attr.parse_nested_meta(|meta| {
                if meta.path.is_ident(RENAME_ALL) {
                    let key = parse_required_lit_str(&meta, RENAME_ALL)?;

                    if rename_all.is_some() {
                        let mut err = meta.error(error_msg::duplicate_attribute(RENAME_ALL));
                        if let Some(first) = rename_all_span {
                            err.combine(Error::new(
                                first,
                                error_msg::first_defined_here(RENAME_ALL),
                            ));
                        }

                        return Err(err);
                    }

                    rename_all = Some(Rename::parse(&key)?);
                    rename_all_span = Some(meta.path.span());

                    return Ok(());
                }

                if meta.path.is_ident(DEFAULT) {
                    let name = parse_required_lit_str(&meta, DEFAULT)?;

                    if default.is_some() {
                        let mut err = meta.error(error_msg::duplicate_attribute(DEFAULT));
                        if let Some(first) = default_span {
                            err.combine(Error::new(first, error_msg::first_defined_here(DEFAULT)));
                        }

                        return Err(err);
                    }

                    default = Some(name);
                    default_span = Some(meta.path.span());

                    return Ok(());
                }

                if let Some(enum_kind) = EnumKind::try_parse(&meta, ident)? {
                    if let Some(existing) = &kind {
                        let mut err = meta.error(error_msg::multiple_extraction_attributes(
                            existing.name(),
                            enum_kind.name(),
                        ));

                        if let Some(first) = kind_span {
                            err.combine(Error::new(
                                first,
                                error_msg::first_defined_here(existing.name()),
                            ));
                        }

                        return Err(err);
                    }

                    kind = Some(enum_kind);
                    kind_span = Some(meta.path.span());

                    return Ok(());
                }

                if meta.path.is_ident(CRLF) {
                    if crlf {
                        let mut err = meta.error(error_msg::duplicate_attribute(CRLF));
                        if let Some(first) = crlf_span {
                            err.combine(Error::new(first, error_msg::first_defined_here(CRLF)));
                        }

                        return Err(err);
                    }

                    crlf = true;
                    crlf_span = Some(meta.path.span());

                    return Ok(());
                }

                unknown.push(meta.path.clone());
                if meta.input.peek(Eq) {
                    meta.value()?.parse::<TokenTree>()?;
                }

                Ok(())
            })?;
        }

        let kind = kind
            .ok_or_else(|| Error::new(Span::call_site(), error_msg::enum_requires_component()))?;

        if matches!(kind, EnumKind::Command) {
            if let Some(span) = rename_all_span {
                return Err(Error::new(
                    span,
                    error_msg::rename_not_allowed_with_command(),
                ));
            }
        }

        let rename = rename_all.unwrap_or(match kind {
            EnumKind::Command => Rename::Uppercase,
            _ => Rename::KebabCase,
        });

        Ok(Self {
            kind,
            rename_all: rename,
            default,
            crlf,
            unknown,
        })
    }
}

impl EnumKind {
    pub fn try_parse(meta: &ParseNestedMeta, ident: &Ident) -> Result<Option<Self>> {
        if meta.path.is_ident(TAG) {
            let key = if meta.input.peek(Eq) {
                parse_lit_str(meta, TAG)?
            } else {
                use heck::ToKebabCase;
                LitStr::new(&ident.to_string().to_kebab_case(), ident.span())
            };
            return Ok(Some(Self::Tag(key)));
        }

        if meta.path.is_ident(TAG_FLAG) {
            let key = if meta.input.peek(Eq) {
                parse_lit_str(meta, TAG_FLAG)?
            } else {
                use heck::ToKebabCase;
                LitStr::new(&ident.to_string().to_kebab_case(), ident.span())
            };
            return Ok(Some(Self::TagFlag(key)));
        }

        if meta.path.is_ident(SOURCE) {
            let source = if meta.input.peek(Eq) {
                let lit = parse_lit_str(meta, SOURCE)?;

                Source::parse(lit)?
            } else {
                Source::Name
            };

            return Ok(Some(Self::Source(source)));
        }

        if meta.path.is_ident(PARAM) {
            let idx = if meta.input.peek(Eq) {
                meta.value()?.parse()?
            } else {
                LitInt::new("0", Span::call_site())
            };

            return Ok(Some(Self::Param(idx)));
        }

        if meta.path.is_ident(TRAILING) {
            return Ok(Some(Self::Trailing));
        }

        if meta.path.is_ident(COMMAND) {
            return Ok(Some(Self::Command));
        }

        Ok(None)
    }

    pub fn name(&self) -> &str {
        match self {
            Self::Tag(_) => TAG,
            Self::TagFlag(_) => TAG_FLAG,
            Self::Source(_) => SOURCE,
            Self::Param(_) => PARAM,
            Self::Trailing => TRAILING,
            Self::Command => COMMAND,
        }
    }

    pub fn add_to(&self, components: &mut ComponentSet) {
        match self {
            Self::Tag(_) | Self::TagFlag(_) => components.add_tags(),
            Self::Source(_) => components.add_source(),
            Self::Param(_) | Self::Trailing => components.add_params(),
            Self::Command => components.add_command(),
        }
    }
}

impl VariantAttrs {
    pub fn parse(attrs: &[Attribute]) -> Result<Self> {
        let mut values: Vec<LitStr> = Vec::new();
        let mut pick: Option<LitStr> = None;
        let mut present: Option<Span> = None;
        let mut skip: bool = false;
        let mut skip_span: Option<Span> = None;
        let mut unknown = Vec::new();

        for attr in attrs {
            if !attr.path().is_ident(IRC) {
                continue;
            }

            let mut last_value_in_attr: Option<LitStr> = None;

            attr.parse_nested_meta(|meta| {
                if meta.path.is_ident(VALUE) {
                    let lit = parse_required_lit_str(&meta, VALUE)?;
                    last_value_in_attr = Some(lit.clone());
                    values.push(lit);
                    return Ok(());
                }

                if meta.path.is_ident(PICK) {
                    if meta.input.peek(Eq) {
                        return Err(Error::new(
                            meta.path.span(),
                            error_msg::cannot_have_value(PICK),
                        ));
                    }

                    if let Some(first) = &pick {
                        let mut err = meta.error(error_msg::duplicate_attribute(PICK));
                        err.combine(Error::new(
                            first.span(),
                            error_msg::first_defined_here(PICK),
                        ));
                        return Err(err);
                    }

                    let Some(last) = &last_value_in_attr else {
                        return Err(Error::new(
                            meta.path.span(),
                            error_msg::pick_must_follow_value(),
                        ));
                    };

                    pick = Some(LitStr::new(&last.value(), meta.path.span()));
                    return Ok(());
                }

                if meta.path.is_ident(PRESENT) {
                    if meta.input.peek(Eq) {
                        return Err(Error::new(
                            meta.path.span(),
                            error_msg::cannot_have_value(PRESENT),
                        ));
                    }

                    if let Some(first_span) = present {
                        let mut err = meta.error(error_msg::duplicate_attribute(PRESENT));
                        err.combine(Error::new(
                            first_span,
                            error_msg::first_defined_here(PRESENT),
                        ));

                        return Err(err);
                    }

                    present = Some(meta.path.span());

                    return Ok(());
                }

                if meta.path.is_ident(SKIP) {
                    if skip {
                        let mut err = meta.error(error_msg::duplicate_attribute(SKIP));
                        if let Some(first) = skip_span {
                            err.combine(Error::new(first, error_msg::first_defined_here(SKIP)));
                        }

                        return Err(err);
                    }

                    skip = true;
                    skip_span = Some(meta.path.span());

                    return Ok(());
                }

                unknown.push(meta.path.clone());
                if meta.input.peek(Eq) {
                    meta.value()?.parse::<TokenTree>()?;
                }

                Ok(())
            })?;
        }

        Ok(Self {
            values,
            pick,
            present,
            skip,
            unknown,
        })
    }
}