use proc_macro2::{Ident, Literal, TokenStream};
use crate::attributes::{Attributes, parse_attributes};
use proc_macro_error3::abort;
use quote::{ToTokens, quote};
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream};
use syn::{Block, Result, Token, punctuated::Punctuated};
pub type TagName = Punctuated<Ident, Token![-]>;
pub trait TagNameString {
fn to_string(&self) -> String;
fn is_custom_element(&self) -> bool;
}
impl TagNameString for TagName {
fn to_string(&self) -> String {
let mut items = self.iter().map(|a| a.to_string());
if items.len() == 0 {
return "".to_string();
}
let first = items.next().unwrap();
items.fold(first, |a, b| format!("{a}-{b}"))
}
fn is_custom_element(&self) -> bool {
let name = self.to_string();
if name.is_empty() {
return false;
}
let first = name.get(0..1).unwrap();
first.to_uppercase() == first
}
}
pub struct Element<'a> {
pub tag: OpeningTag<'a>,
pub children: Option<Nodes<'a>>,
}
impl Parse for Element<'_> {
fn parse(input: ParseStream) -> Result<Self> {
let span = input.span();
let tag = input.parse::<OpeningTag>()?;
let mut children = None;
if !tag.self_closing {
let nodes = input.parse::<Nodes>()?;
if !nodes.list.is_empty() {
children = Some(nodes);
}
let closing_tag = input.parse::<ClosingTag>()?;
if closing_tag.name != tag.name {
abort!(
span,
format!("Closing tag is missing for '{}'", tag.name.to_string())
);
}
}
Ok(Element { tag, children })
}
}
impl Element<'_> {
fn is_custom_element(&self) -> bool {
self.tag.name.is_custom_element()
}
fn children_stream(&self) -> TokenStream {
match &self.children {
Some(nodes) => {
let children = nodes.get_tuples();
quote!(children:Some(#children))
}
None => {
quote!(children: Option::<()>::None)
}
}
}
fn has_children(&self) -> bool {
match &self.children {
Some(nodes) => nodes.len() > 0,
None => false,
}
}
}
impl ToTokens for Element<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let children = self.children_stream();
let el = if self.is_custom_element() {
let name = &self.tag.name;
let (mut properties, events) = self.tag.attributes.to_properties(); if self.has_children() {
properties.push(children);
}
if properties.is_empty() {
quote!(#name {
..Default::default()
}#(#events)*)
} else {
quote!(#name {
#(#properties),*,
..Default::default()
}#(#events)*)
}
} else {
let (attributes, events) = self.tag.attributes.to_token_stream();
let tag = self.tag.name.to_string();
let is_fragment = tag.is_empty();
quote! {
workflow_html::Element {
is_fragment:#is_fragment,
tag:String::from(#tag),
onclick:std::sync::Arc::new(std::sync::Mutex::new(None)),
#attributes,
#children,
}#(#events)*
}
};
el.to_tokens(tokens);
}
}
pub struct OpeningTag<'a> {
pub name: TagName,
pub self_closing: bool,
pub attributes: Attributes<'a>,
}
fn get_fragment_tag_name() -> TagName {
Punctuated::new() }
impl Parse for OpeningTag<'_> {
fn parse(input: ParseStream) -> Result<Self> {
let mut self_closing = false;
let name;
let attributes;
input.parse::<Token![<]>()?;
if input.peek(Token![>]) {
name = get_fragment_tag_name();
attributes = Attributes::empty()
} else {
name = TagName::parse_separated_nonempty_with(input, syn::Ident::parse_any)?;
attributes = parse_attributes(input)?;
if input.peek(Token![/]) {
input.parse::<Token![/]>()?;
self_closing = true;
}
}
input.parse::<Token![>]>()?;
Ok(Self {
name,
self_closing,
attributes,
})
}
}
pub struct ClosingTag {
pub name: TagName,
}
impl Parse for ClosingTag {
fn parse(input: ParseStream) -> Result<Self> {
if input.is_empty() || !input.peek(Token![<]) {
return Ok(Self {
name: get_fragment_tag_name(),
});
}
input.parse::<Token![<]>()?;
if input.is_empty() || input.peek2(Token![/]) {
return Ok(Self {
name: get_fragment_tag_name(),
});
}
input.parse::<Token![/]>()?;
if input.is_empty() {
return Ok(Self {
name: get_fragment_tag_name(),
});
}
let name = if input.peek(Token![>]) {
get_fragment_tag_name()
} else {
match TagName::parse_separated_nonempty_with(input, syn::Ident::parse_any) {
Ok(tag_name) => tag_name,
Err(_e) => {
return Ok(Self {
name: get_fragment_tag_name(),
});
}
}
};
if input.is_empty() || !input.peek(Token![>]) {
return Ok(Self {
name: get_fragment_tag_name(),
});
}
input.parse::<Token![>]>()?;
Ok(Self { name })
}
}
pub struct Nodes<'a> {
list: Vec<Node<'a>>,
}
impl Nodes<'_> {
pub fn len(&self) -> usize {
self.list.len()
}
pub fn get_tuples(&self) -> TokenStream {
if self.list.len() == 1 {
let node = &self.list[0];
quote! {#node}
} else {
let mut group = vec![];
let list: Vec<TokenStream> = self.list.iter().map(|item| quote! {#item}).collect();
for chunk in list.chunks(10) {
group.push(quote! { ( #(#chunk),* ) });
if group.len() == 10 {
let combined = quote! { ( #(#group),* ) };
group = vec![combined];
}
}
let children = quote! {(#(#group),*)};
quote! {#children}
}
}
}
impl Parse for Nodes<'_> {
fn parse(input: ParseStream) -> Result<Self> {
let mut list: Vec<Node> = vec![];
while !input.is_empty() && (!input.peek(Token![<]) || !input.peek2(Token![/])) {
let node = input.parse::<Node>()?;
list.push(node);
}
Ok(Nodes { list })
}
}
impl ToTokens for Nodes<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.get_tuples().to_tokens(tokens);
}
}
pub enum Node<'a> {
Element(Element<'a>),
Block(Block),
Literal(Literal),
}
impl Parse for Node<'_> {
fn parse(input: ParseStream) -> Result<Self> {
let node = if input.peek(Token![<]) {
Node::Element(input.parse::<Element>()?)
} else if input.peek(syn::token::Brace) {
Node::Block(input.parse::<Block>()?)
} else {
Node::Literal(input.parse::<Literal>()?)
};
Ok(node)
}
}
impl ToTokens for Node<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Node::Element(el) => {
el.to_tokens(tokens);
}
Node::Literal(el) => {
el.to_tokens(tokens);
}
Node::Block(block) => {
if block.stmts.len() == 1 {
let stm = &block.stmts[0];
stm.to_tokens(tokens);
} else {
block.to_tokens(tokens);
}
}
}
}
}