use crate::children::Children;
use crate::element_attribute::{parse_attr_value, AttributeKey, ElementAttribute};
use proc_macro_error::emit_error;
use quote::{quote, ToTokens};
use std::collections::HashSet;
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Result};
use syn::spanned::Spanned;
pub type Attributes = HashSet<ElementAttribute>;
#[derive(Default)]
pub struct ElementAttributes {
pub attributes: Attributes,
}
impl ElementAttributes {
pub fn new(attributes: Attributes) -> Self {
Self { attributes }
}
pub fn for_custom_element<'c>(
&self,
children: &'c Children,
) -> CustomElementAttributes<'_, 'c> {
CustomElementAttributes {
attributes: &self.attributes,
children,
}
}
pub fn for_simple_element(&self) -> SimpleElementAttributes<'_> {
SimpleElementAttributes {
attributes: &self.attributes,
}
}
pub fn parse(input: ParseStream, is_custom_element: bool) -> Result<Self> {
let mut parsed_self = Self::parse_with_context(input, is_custom_element)?;
let new_attributes: Attributes = parsed_self
.attributes
.drain()
.filter_map(|attribute| match attribute.validate(is_custom_element) {
Ok(x) => Some(x),
Err(err) => {
emit_error!(err.span(), "Invalid attribute: {}", err);
None
}
})
.collect();
Ok(ElementAttributes::new(new_attributes))
}
fn parse_with_context(input: ParseStream, is_custom_element: bool) -> Result<Self> {
let mut attributes: HashSet<ElementAttribute> = HashSet::new();
while input.peek(syn::Ident::peek_any) || input.peek(syn::token::Brace) {
if input.peek(syn::token::Brace) {
let content;
syn::braced!(content in input);
let ident = syn::Ident::parse_any(&content)?;
let mut key = AttributeKey::new();
key.push_value(ident);
let attribute = ElementAttribute::Punned(key);
let ident = attribute.ident();
if attributes.contains(&attribute) {
emit_error!(
ident.span(),
"There is a previous definition of the {} attribute",
quote!(#ident)
);
}
attributes.insert(attribute);
} else {
let name =
AttributeKey::parse_separated_nonempty_with(input, syn::Ident::parse_any)?;
let has_value = input.peek(syn::Token![=]);
if has_value {
input.parse::<syn::Token![=]>()?;
let value = parse_attr_value(input)?;
let attribute = ElementAttribute::WithValue(name, value);
let ident = attribute.ident();
if attributes.contains(&attribute) {
emit_error!(
ident.span(),
"There is a previous definition of the {} attribute",
quote!(#ident)
);
}
attributes.insert(attribute);
} else if is_custom_element {
let attribute = ElementAttribute::Punned(name);
let ident = attribute.ident();
if attributes.contains(&attribute) {
emit_error!(
ident.span(),
"There is a previous definition of the {} attribute",
quote!(#ident)
);
}
attributes.insert(attribute);
} else {
let attribute = ElementAttribute::BooleanAttribute(name);
let ident = attribute.ident();
if attributes.contains(&attribute) {
emit_error!(
ident.span(),
"There is a previous definition of the {} attribute",
quote!(#ident)
);
}
attributes.insert(attribute);
}
}
}
Ok(ElementAttributes::new(attributes))
}
}
impl Parse for ElementAttributes {
fn parse(input: ParseStream) -> Result<Self> {
let mut attributes: HashSet<ElementAttribute> = HashSet::new();
while input.peek(syn::Ident::peek_any) || input.peek(syn::token::Brace) {
if input.peek(syn::token::Brace) {
let content;
syn::braced!(content in input);
let ident = syn::Ident::parse_any(&content)?;
let mut key = AttributeKey::new();
key.push_value(ident);
let attribute = ElementAttribute::Punned(key);
let ident = attribute.ident();
if attributes.contains(&attribute) {
emit_error!(
ident.span(),
"There is a previous definition of the {} attribute",
quote!(#ident)
);
}
attributes.insert(attribute);
} else {
let attribute = input.parse::<ElementAttribute>()?;
let ident = attribute.ident();
if attributes.contains(&attribute) {
emit_error!(
ident.span(),
"There is a previous definition of the {} attribute",
quote!(#ident)
);
}
attributes.insert(attribute);
}
}
Ok(ElementAttributes::new(attributes))
}
}
pub struct CustomElementAttributes<'a, 'c> {
attributes: &'a Attributes,
children: &'c Children,
}
impl<'a, 'c> ToTokens for CustomElementAttributes<'a, 'c> {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let mut attrs: Vec<_> = self
.attributes
.iter()
.map(|attribute| {
let ident = attribute.ident();
let value = attribute.value_tokens();
quote! {
#ident: #value
}
})
.collect();
if self.children.len() > 0 {
let children_tuple = self.children.as_option_of_tuples_tokens();
attrs.push(quote! {
children: #children_tuple
});
}
let quoted = if attrs.len() == 0 {
quote!()
} else {
quote!({ #(#attrs),* })
};
quoted.to_tokens(tokens);
}
}
pub struct SimpleElementAttributes<'a> {
attributes: &'a Attributes,
}
impl<'a> ToTokens for SimpleElementAttributes<'a> {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
if self.attributes.is_empty() {
quote!(None).to_tokens(tokens);
} else {
let attrs: Vec<_> = self
.attributes
.iter()
.map(|attribute| {
let mut iter = attribute.ident().iter();
let first_word = iter.next().unwrap().unraw();
let ident = iter.fold(first_word.to_string(), |acc, curr| {
format!("{}-{}", acc, curr.unraw())
});
if attribute.is_boolean() {
quote! {
hm.insert(#ident, workers_rsx::simple_element::AttrValue::Boolean);
}
} else {
let value = attribute.value_tokens();
quote! {
hm.insert(#ident, workers_rsx::simple_element::AttrValue::Value(::std::borrow::Cow::from(#value)));
}
}
})
.collect();
let hashmap_declaration = quote! {{
let mut hm = std::collections::HashMap::<&str, workers_rsx::simple_element::AttrValue<'_>>::new();
#(#attrs)*
Some(hm)
}};
hashmap_declaration.to_tokens(tokens);
}
}
}