getargs-derive-macros 0.1.0

Internal implementation detail of getargs-derive. Not meant for direct use; depend on getargs-derive instead.
Documentation
use quote::ToTokens;
use syn::{Expr, Ident, Lit, LitStr, Token, meta::ParseNestedMeta, spanned::Spanned};

pub(crate) type Parser<T> = fn(&ParseNestedMeta, &mut T) -> syn::Result<()>;

pub(crate) struct Key<T> {
    name: &'static str,
    parse: Parser<T>,
}

impl<T> Key<T> {
    pub(crate) const fn new(name: &'static str, parse: Parser<T>) -> Self {
        Self { name, parse }
    }
}

fn supported_keys<T>(keys: &[Key<T>]) -> String {
    keys.iter()
        .map(|key| format!("`{}`", key.name))
        .collect::<Vec<_>>()
        .join(", ")
}

pub(crate) fn dispatch<T>(
    meta: ParseNestedMeta,
    attribute: &str,
    keys: &[Key<T>],
    target: &mut T,
) -> syn::Result<()> {
    let key = meta.path.get_ident().map(Ident::to_string);
    match key
        .as_deref()
        .and_then(|key| keys.iter().find(|entry| entry.name == key))
    {
        Some(entry) => (entry.parse)(&meta, target),
        None => {
            let path =
                key.unwrap_or_else(|| meta.path.to_token_stream().to_string().replace(' ', ""));
            Err(meta.error(format!(
                "unknown `{attribute}` attribute `{path}`; supported attributes are: {}",
                supported_keys(keys)
            )))
        }
    }
}

pub(crate) fn set_once<T>(
    slot: &mut Option<T>,
    value: T,
    meta: &ParseNestedMeta,
) -> syn::Result<()> {
    if slot.is_some() {
        let key = meta.path.to_token_stream().to_string();
        return Err(meta.error(format!("duplicate `{key}` attribute")));
    }
    *slot = Some(value);
    Ok(())
}

pub(crate) fn reject_value(meta: &ParseNestedMeta, key: &str) -> syn::Result<()> {
    if meta.input.peek(Token![=]) || meta.input.peek(syn::token::Paren) {
        return Err(meta.error(format!("`{key}` does not take a value")));
    }
    Ok(())
}

pub(crate) fn require_value<'a>(
    meta: &'a ParseNestedMeta,
    key: &str,
    example: &str,
) -> syn::Result<syn::parse::ParseStream<'a>> {
    if !meta.input.peek(Token![=]) {
        return Err(meta.error(format!("`{key}` requires a value, e.g. `{example}`")));
    }
    meta.value()
}

pub(crate) fn expected(key: &str, kind: &str, example: &str) -> String {
    match key.ends_with("aliases") {
        true => format!("`{key}` expects an array of {kind} literals, e.g. `{example}`"),
        false => format!("`{key}` expects a {kind} literal, e.g. `{example}`"),
    }
}

pub(crate) fn parse_list(
    meta: &ParseNestedMeta,
    key: &str,
    kind: &str,
    example: &str,
) -> syn::Result<Vec<Lit>> {
    let array = match require_value(meta, key, example)?.parse::<Expr>()? {
        Expr::Array(array) => array,
        other => {
            return Err(syn::Error::new(other.span(), expected(key, kind, example)));
        }
    };
    array
        .elems
        .into_iter()
        .map(|element| match element {
            Expr::Lit(literal) => Ok(literal.lit),
            other => Err(syn::Error::new(other.span(), expected(key, kind, example))),
        })
        .collect()
}

pub(crate) fn parse_string(
    meta: &ParseNestedMeta,
    key: &str,
    example: &str,
) -> syn::Result<LitStr> {
    match require_value(meta, key, example)?.parse::<Lit>()? {
        Lit::Str(literal) => Ok(literal),
        other => Err(syn::Error::new(
            other.span(),
            format!("`{key}` expects a string literal, e.g. `{example}`"),
        )),
    }
}