use crate::{MaybeError, OkMaybe};
use std::{
collections::{HashMap, HashSet},
fmt::Display,
str::FromStr,
};
#[doc = include_str!("./fixtures/hidden_parse_attribute_rename_ignore.md")]
#[cfg(feature = "strum")]
pub fn unique<T>(
parsed_attributes: impl IntoIterator<Item = WithSpan<T>>,
) -> OkMaybe<HashMap<T::Discriminant, WithSpan<T>>, syn::Error>
where
T: strum::IntoDiscriminant,
T::Discriminant: Eq + Display + std::hash::Hash + Copy,
{
let mut seen = HashMap::new();
let mut errors = MaybeError::new();
for attribute in parsed_attributes {
let WithSpan(ref parsed, span) = attribute;
let key = parsed.discriminant();
if let Some(WithSpan(_, prior)) = seen.insert(key, attribute) {
errors.push_back(syn::Error::new(
span,
format!("Duplicate attribute: `{key}`"),
));
errors.push_back(syn::Error::new(
prior,
format!("previously `{key}` defined here"),
));
}
}
OkMaybe(seen, errors.maybe())
}
#[doc = include_str!("./fixtures/hidden_parse_attribute_rename_ignore.md")]
#[cfg(feature = "strum")]
pub fn check_exclusive<T>(
exclusive: T::Discriminant,
collection: &[WithSpan<T>],
) -> OkMaybe<(), syn::Error>
where
T: strum::IntoDiscriminant + syn::parse::Parse,
T::Discriminant: Eq + Display + std::hash::Hash + Copy,
{
let mut errors = MaybeError::new();
let mut keys = collection
.iter()
.map(|WithSpan(value, _)| value.discriminant())
.collect::<HashSet<T::Discriminant>>();
if keys.remove(&exclusive) && !keys.is_empty() {
let other_keys = keys
.iter()
.map(|key| format!("`{key}`"))
.collect::<Vec<_>>()
.join(", ");
for WithSpan(value, span) in collection {
if value.discriminant() == exclusive {
errors.push_front(syn::Error::new(
*span,
format!("Exclusive attribute. Remove either `{exclusive}` or {other_keys}",),
))
} else {
errors.push_back(syn::Error::new(
*span,
format!("cannot be used with `{exclusive}`"),
))
}
}
}
OkMaybe((), errors.maybe())
}
#[cfg(feature = "strum")]
pub fn known_attribute<T>(identity: &syn::Ident) -> Result<T, syn::Error>
where
T: FromStr + strum::IntoEnumIterator + Display,
{
let name_str = &identity.to_string();
T::from_str(name_str).map_err(|_| {
syn::Error::new(
identity.span(),
format!(
"Unknown attribute: `{identity}`. Must be one of {valid_keys}",
valid_keys = T::iter()
.map(|key| format!("`{key}`"))
.collect::<Vec<String>>()
.join(", ")
),
)
})
}
#[doc = include_str!("./fixtures/hidden_parse_attribute_rename_ignore.md")]
#[doc = include_str!("./fixtures/hidden_parse_attribute_rename_ignore.md")]
pub fn parse_attrs<T>(
namespace: &AttrNamespace,
attrs: &[syn::Attribute],
) -> OkMaybe<Vec<T>, syn::Error>
where
T: syn::parse::Parse,
{
let mut attributes = Vec::new();
let mut errors = MaybeError::new();
for attr in attrs.iter().filter(|attr| attr.path().is_ident(namespace)) {
match attr
.parse_args_with(syn::punctuated::Punctuated::<T, syn::Token![,]>::parse_terminated)
{
Ok(attrs) => {
for attribute in attrs {
attributes.push(attribute);
}
}
Err(error) => errors.push_back(error),
}
}
OkMaybe(attributes, errors.maybe())
}
#[derive(Debug)]
pub struct WithSpan<T>(pub T, pub proc_macro2::Span);
impl<T: syn::parse::Parse> syn::parse::Parse for WithSpan<T> {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let span = input.span();
Ok(WithSpan(input.parse()?, span))
}
}
#[derive(Debug, Clone)]
pub struct AttrNamespace<'a>(pub &'a str);
impl AttrNamespace<'_> {
#[cfg(feature = "strum")]
pub fn parse_attrs<T>(&self, attrs: &[syn::Attribute]) -> OkMaybe<Vec<T>, syn::Error>
where
T: syn::parse::Parse,
{
crate::parse_attrs(self, attrs)
}
}
impl AsRef<str> for AttrNamespace<'_> {
fn as_ref(&self) -> &str {
self.0
}
}
impl<'a> std::ops::Deref for AttrNamespace<'a> {
type Target = &'a str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Display for AttrNamespace<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}