use std::fmt::Display;
use proc_macro2::{Ident, Span};
use syn::{meta::ParseNestedMeta, spanned::Spanned, Attribute, Error, Lit, Result};
pub struct NamedAttrs {
attr_span: Span,
values: Vec<(Ident, Lit)>,
}
impl NamedAttrs {
pub fn parse(attr: &Attribute, valid: &[&str]) -> Result<Self> {
let mut parser = Self {
attr_span: attr.span(),
values: Vec::new(),
};
attr.parse_nested_meta(|meta| parser.parse_meta(meta, valid))?;
Ok(parser)
}
fn parse_meta(&mut self, meta: ParseNestedMeta, valid: &[&str]) -> Result<()> {
let is_valid = |ident| valid.iter().any(|name| ident == name);
let Some(ident) = meta.path.get_ident().filter(|i| is_valid(*i)) else {
let expected = valid.join(", ");
return Err(Error::new_spanned(
meta.path,
format!("invalid argument name (expected one of {expected})"),
));
};
let lit: Lit = meta.value()?.parse()?;
self.values.push((ident.clone(), lit));
Ok(())
}
pub fn optional<T: ParseAttribute>(&mut self, name: &str) -> Result<Option<T>> {
let Some(index) = self.values.iter().position(|(ident, _)| ident == name) else {
return Ok(None);
};
let (_, lit) = self.values.remove(index);
let parsed = T::parse_attribute(lit)?;
Ok(Some(parsed))
}
pub fn required<T: ParseAttribute>(&mut self, name: &str) -> Result<T> {
let Some(parsed) = self.optional::<T>(name)? else {
return Err(Error::new(
self.attr_span,
format!("missing required `{name}` argument"),
));
};
Ok(parsed)
}
}
pub trait ParseAttribute: Sized {
fn parse_attribute(input: Lit) -> Result<Self>;
}
impl ParseAttribute for String {
fn parse_attribute(input: Lit) -> Result<Self> {
let Lit::Str(lit) = input else {
return Err(Error::new_spanned(input, "expected string literal"));
};
Ok(lit.value())
}
}
impl ParseAttribute for bool {
fn parse_attribute(input: Lit) -> Result<Self> {
let Lit::Bool(lit) = input else {
return Err(Error::new_spanned(input, "expected boolean literal"));
};
Ok(lit.value)
}
}
impl ParseAttribute for u16 {
fn parse_attribute(input: Lit) -> Result<Self> {
let Lit::Int(lit) = input else {
return Err(Error::new_spanned(input, "expected integer literal"));
};
lit.base10_parse()
}
}
pub struct ParseSpanned<T> {
pub span: Span,
pub inner: T,
}
impl<T> ParseSpanned<T> {
pub fn error(&self, message: impl Display) -> Error {
Error::new(self.span, message)
}
}
impl<T: ParseAttribute> ParseAttribute for ParseSpanned<T> {
fn parse_attribute(input: Lit) -> Result<Self> {
let span = input.span();
let inner = T::parse_attribute(input)?;
Ok(Self { span, inner })
}
}