use std::ops::ControlFlow::{self, Break, Continue};
use proc_macro2::Span;
use quote::ToTokens;
use syn::{meta::ParseNestedMeta, punctuated::Punctuated, Error, Expr, Meta, Path, Result, Token};
#[derive(Clone, Debug)]
pub struct MetaItem {
pub key: Path,
pub value: Option<(Token![=], Expr)>,
}
impl MetaItem {
pub fn parse(parser: ParseNestedMeta) -> Result<Self> {
let key = parser.path;
let value = if parser.input.peek(Token![=]) {
let eq = parser.input.parse().unwrap();
let expr = parser.input.parse()?;
Some((eq, expr))
} else {
None
};
Ok(Self { key, value })
}
}
impl ToTokens for MetaItem {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
self.key.to_tokens(tokens);
if let Some((eq, expr)) = &self.value {
eq.to_tokens(tokens);
expr.to_tokens(tokens);
}
}
}
pub fn find_attr_meta(
attrs: &[syn::Attribute],
attr_name: &str,
meta_key: &str,
) -> Option<MetaItem> {
visit_attr_metas(attrs, attr_name, |meta_item| {
if meta_item.key.is_ident(meta_key) {
Break(meta_item)
} else {
Continue(())
}
})
}
pub fn visit_attr_metas<T>(
attrs: &[syn::Attribute],
attr_name: &str,
mut visitor: impl FnMut(MetaItem) -> ControlFlow<T>,
) -> Option<T> {
attrs.iter().find_map(move |attr| {
if !attr.path().is_ident(attr_name) {
return None;
}
let mut out = None;
let _ = attr.parse_nested_meta(|meta_item| {
let meta_item = MetaItem::parse(meta_item)?;
if let Break(value) = (visitor)(meta_item) {
out = Some(value);
Err(Error::new(Span::call_site(), ""))
} else {
Ok(())
}
});
out
})
}
pub fn remove_attr_meta(
attrs: &mut [syn::Attribute],
attr_name: &str,
meta_key: &str,
) -> Option<MetaItem> {
attrs.iter_mut().find_map(|attr| {
if !attr.path().is_ident(attr_name) {
return None;
}
let Meta::List(meta_list) = &mut attr.meta else {
return None;
};
let mut out = None;
let mut transformed = Punctuated::<MetaItem, Token![,]>::new();
let result = meta_list.parse_nested_meta(|meta_item| {
let meta_item = MetaItem::parse(meta_item)?;
if meta_item.key.is_ident(meta_key) && out.is_none() {
out = Some(meta_item);
} else {
transformed.push(meta_item.clone());
}
Ok(())
});
if result.is_err() {
None
} else {
meta_list.tokens = transformed.into_token_stream();
out
}
})
}