use syn::{LitStr, Type};
use crate::attr_name;
use crate::case::Case;
use crate::marker::{Fallback, Marker};
#[derive(Default)]
pub(crate) struct Attrs {
pub(crate) rename: Option<String>,
pub(crate) rename_all: Option<Case>,
pub(crate) default: Option<Fallback>,
pub(crate) tag: Option<String>,
pub(crate) with: Option<Type>,
pub(crate) range: Option<(f64, f64)>,
pub(crate) krate: Option<syn::Path>,
pub(crate) markers: Vec<Marker>,
}
impl Attrs {
pub(crate) fn has(&self, marker: Marker) -> bool {
self.markers.contains(&marker)
}
}
pub(crate) fn attrs(list: &[syn::Attribute]) -> syn::Result<Attrs> {
let mut a = Attrs::default();
for attr in list.iter().filter(|at| at.path().is_ident(attr_name::DATA)) {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident(attr_name::RENAME) {
a.rename = Some(meta.value()?.parse::<LitStr>()?.value());
} else if meta.path.is_ident(attr_name::RENAME_ALL) {
a.rename_all = Some(Case::parse(&meta.value()?.parse::<LitStr>()?)?);
} else if meta.path.is_ident(attr_name::TAG) {
a.tag = Some(meta.value()?.parse::<LitStr>()?.value());
} else if meta.path.is_ident(attr_name::DEFAULT) {
a.default = Some(if meta.input.peek(syn::Token![=]) {
Fallback::Call(meta.value()?.parse::<LitStr>()?.parse()?)
} else {
Fallback::Default
});
} else if meta.path.is_ident(attr_name::RANGE) {
a.range = Some(parse_range(&meta.value()?.parse::<LitStr>()?)?);
} else if meta.path.is_ident(attr_name::WITH) {
a.with = Some(meta.value()?.parse::<LitStr>()?.parse()?);
} else if meta.path.is_ident(attr_name::SKIP) {
a.markers.push(Marker::Skip);
} else if meta.path.is_ident(attr_name::VIEW) {
a.markers.push(Marker::View);
} else if meta.path.is_ident(attr_name::OTHER) {
a.markers.push(Marker::Other);
} else if meta.path.is_ident(attr_name::CRATE) {
a.krate = Some(meta.value()?.parse::<LitStr>()?.parse()?);
} else {
return Err(meta
.error("unknown `data` attribute (rename, rename_all, default, skip, tag, with, range, other, crate, view)"));
}
Ok(())
})?;
}
Ok(a)
}
fn parse_range(lit: &LitStr) -> syn::Result<(f64, f64)> {
let text = lit.value();
let parsed = text.split_once("..=").and_then(|(lo, hi)| {
Some((
lo.trim().parse::<f64>().ok()?,
hi.trim().parse::<f64>().ok()?,
))
});
parsed.ok_or_else(|| syn::Error::new_spanned(lit, "range = \"lo..=hi\" (numbers)"))
}