custom_attrs 1.5.3

A library that allows you to configure values specific to each variants of an enum.
Documentation
use proc_macro2::Ident;
use proc_macro_error::emit_error;
use syn::{
    bracketed,
    parse::{Parse, ParseStream},
    punctuated::Punctuated,
    token::{self, Comma},
    LitStr, Token,
};

use crate::{derive::error_duplicate, value::PathOptionalValueAssignment};

macro_rules! unwrap_as {
    ($expr: expr, as $type: path, $error: expr) => {
        match $expr {
            $type(value) => value,
            _ => {
                emit_error!($expr, $error);
                return;
            }
        }
    };
}

pub struct ConfigDeclarationList {
    _pound: Token!(#),
    _bracket: token::Bracket,
    declarations: Punctuated<PathOptionalValueAssignment, Comma>,
}

impl ConfigDeclarationList {
    pub fn parse_all(input: ParseStream) -> syn::Result<Vec<Self>> {
        let mut all_declarations = Vec::new();
        while input.peek(Token!(#)) {
            all_declarations.push(input.call(ConfigDeclarationList::parse)?)
        }

        Ok(all_declarations)
    }
}

impl Parse for ConfigDeclarationList {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let content;
        Ok(Self {
            _pound: input.parse()?,
            _bracket: bracketed!(content in input),
            declarations: content.parse_terminated(PathOptionalValueAssignment::parse)?,
        })
    }
}

#[derive(Default)]
pub struct Config {
    comment: String,
    function_name: Option<LitStr>,
}

impl Config {
    pub fn new(attributes: Vec<ConfigDeclarationList>) -> Self {
        let mut self_ = Self::default();

        for list in attributes {
            for config in list.declarations {
                let path = config
                    .ident()
                    .segments
                    .iter()
                    .map(|s| s.ident.to_string())
                    .collect::<Vec<_>>();

                let path = path.iter().map(|s| s.as_str()).collect::<Vec<_>>();

                match path.as_slice() {
                    &["doc"] => self_.parse_documentation(config),
                    &["function"] => self_.parse_function(config, path),

                    _ => emit_error!(config.ident(), "Unknown config."),
                }
            }
        }

        self_
    }

    fn parse_documentation(&mut self, attr: PathOptionalValueAssignment) {
        if attr.value().is_none() {
            emit_error!(attr.ident(), "Expected `doc = ...`");
            return;
        }

        let lit =
            unwrap_as!(attr.value().unwrap(), as syn::Expr::Lit, "Expected a literal expression.");
        let str = unwrap_as!(&lit.lit, as syn::Lit::Str, "Expected a string literal.");

        if !self.comment.is_empty() {
            self.comment.push('\n');
        }
        self.comment += str.value().as_str();
    }

    fn parse_function(&mut self, attr: PathOptionalValueAssignment, path_str: Vec<&str>) {
        if attr.value().is_none() {
            emit_error!(attr.ident(), "Expected `function = ...`");
            return;
        }

        let lit =
            unwrap_as!(attr.value().unwrap(), as syn::Expr::Lit, "Expected a literal expression.");
        let str = unwrap_as!(&lit.lit, as syn::Lit::Str, "Expected a string literal.");

        match &self.function_name {
            Some(str2) => {
                error_duplicate!(
                    attr, "This config is already set." ;
                    str2, "Value for config `{}` is already set here.", path_str.join("::")
                );
            }
            None => {
                if str.value().is_empty() {
                    emit_error!(str, "The function name must not be empty.");
                    return;
                }

                self.function_name = Some(str.to_owned())
            },
        }
    }

    pub fn comment(&self) -> &str {
        self.comment.as_ref()
    }

    pub fn function_name(&self) -> Option<Ident> {
        self.function_name.as_ref().map(|l| Ident::new(&l.value(), l.span()))
    }

    pub fn function_name_lit(&self) -> Option<&LitStr> {
        self.function_name.as_ref()
    }
}