use quote::quote;
use syn::punctuated::Punctuated;
use crate::core::Arg;
#[repr(transparent)]
#[derive(Clone)]
pub struct Attrs(Vec<Attr>);
impl Attrs {
pub fn parse(attrs: &[syn::Attribute]) -> syn::Result<Self> {
Ok(Self(
attrs
.iter()
.filter_map(|attr| Attr::parse(attr).ok())
.collect(),
))
}
pub fn exists(&self, name: &str) -> bool {
self.0.iter().any(|a| a.exists(name))
}
pub fn get(&self, name: &str) -> syn::Result<Vec<Arg>> {
let raw: Vec<Arg> = self
.0
.iter()
.filter(|a| a.path().is_ident("moxy") && a.exists(name))
.flat_map(|a| a.get(name).cloned())
.collect();
let mut result: Vec<Arg> = vec![];
for arg in raw {
match &arg {
Arg::Attr(incoming) => {
let pos = result.iter().position(|existing| {
matches!(existing, Arg::Attr(ea)
if ea.path().get_ident() == incoming.path().get_ident())
});
if let Some(i) = pos {
if let Arg::Attr(existing) = result[i].clone() {
result[i] = Arg::Attr(existing.merge(incoming)?);
}
} else {
result.push(arg);
}
}
_ => {
if !result.contains(&arg) {
result.push(arg);
}
}
}
}
Ok(result)
}
}
impl quote::ToTokens for Attrs {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let items = &self.0;
tokens.extend(quote!(#(#items,)*));
}
}
impl std::ops::Deref for Attrs {
type Target = [Attr];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Attr {
path: syn::Path,
args: Punctuated<Arg, syn::Token![,]>,
}
impl Attr {
pub fn new(path: syn::Path, args: Punctuated<Arg, syn::Token![,]>) -> Self {
Self { path, args }
}
pub fn parse(attr: &syn::Attribute) -> syn::Result<Self> {
let args = attr.parse_args_with(
syn::punctuated::Punctuated::<Arg, syn::Token![,]>::parse_terminated,
)?;
Ok(Self {
path: attr.path().clone(),
args,
})
}
pub fn path(&self) -> &syn::Path {
&self.path
}
pub fn args(&self) -> &Punctuated<Arg, syn::Token![,]> {
&self.args
}
pub fn exists(&self, ident: &str) -> bool {
self.args.iter().any(|arg| arg.path().is_ident(ident))
}
pub fn get(&self, ident: &str) -> Option<&Arg> {
self.args.iter().find(|arg| arg.path().is_ident(ident))
}
pub fn get_string(&self, name: &str) -> Option<String> {
self.args.iter().find_map(|arg| {
if arg.path().is_ident(name) {
arg.as_lit().and_then(|lit| match lit {
syn::Lit::Str(s) => Some(s.value()),
_ => None,
})
} else {
None
}
})
}
pub fn get_value_tokens(&self, name: &str) -> Option<proc_macro2::TokenStream> {
self.args
.iter()
.find(|a| a.path().is_ident(name))
.and_then(|a| a.as_value_tokens())
}
pub fn get_custom_name(&self) -> Option<syn::Ident> {
self.args.iter().find_map(|a| {
if !a.path().is_ident("__value") {
return None;
}
a.as_lit().and_then(|lit| match lit {
syn::Lit::Str(s) => Some(quote::format_ident!("{}", s.value())),
_ => None,
})
})
}
pub fn merge(&self, other: &Attr) -> syn::Result<Attr> {
let mut merged: Punctuated<Arg, syn::Token![,]> = self.args.clone();
for arg in other.args.iter() {
let existing = merged
.iter()
.find(|a| a.path().get_ident() == arg.path().get_ident());
match existing {
Some(a) if a == arg => {
}
Some(_) => {
return Err(syn::Error::new_spanned(
arg.path(),
format!(
"conflicting values for `{}`",
arg.path()
.get_ident()
.map(|i| i.to_string())
.unwrap_or_default()
),
));
}
None => {
merged.push(arg.clone());
}
}
}
Ok(Attr::new(self.path.clone(), merged))
}
}
impl quote::ToTokens for Attr {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let path = self.path();
let args = self.args();
tokens.extend(quote!(#path(#args)));
}
}