use crate::*;
pub fn parse_html(input: TokenStream) -> TokenStream {
let tokens: proc_macro2::TokenStream = match syn::parse::<HtmlRoot>(input) {
Ok(nodes) => nodes.into_token_stream(),
Err(error) => return error.to_compile_error().into(),
};
TokenStream::from(tokens)
}
pub(crate) fn camel_case_event_name(name: &str) -> String {
let mut result: String = String::new();
let mut capitalize_next: bool = true;
for ch in name.chars() {
if ch == '_' {
capitalize_next = true;
} else if capitalize_next {
result.push(ch.to_ascii_uppercase());
capitalize_next = false;
} else {
result.push(ch);
}
}
result
}
pub(crate) fn parse_html_children(content: ParseStream) -> syn::Result<Vec<HtmlNode>> {
let mut children: Vec<HtmlNode> = Vec::new();
while !content.is_empty() {
if content.peek(LitStr) {
let lit: LitStr = content.parse()?;
children.push(HtmlNode::Text(lit.value()));
} else if content.peek(Token![if]) {
let html_if: HtmlIf = content.parse()?;
children.push(HtmlNode::If(html_if));
} else if content.peek(Token![match]) {
let html_match: HtmlMatch = content.parse()?;
children.push(HtmlNode::Match(html_match));
} else if content.peek(Token![for]) {
let html_for: HtmlFor = content.parse()?;
children.push(HtmlNode::For(html_for));
} else if content.peek(syn::token::Brace) {
let child_content;
braced!(child_content in content);
let expr: Expr = child_content.parse()?;
children.push(HtmlNode::Dynamic(expr));
} else if (content.peek(Ident) || content.peek(syn::LitStr)) && content.peek2(Colon) {
break;
} else if content.peek(Ident) {
if content.peek2(syn::token::Brace) {
let element: HtmlElement = content.parse()?;
children.push(HtmlNode::Element(element));
} else {
let expr: Expr = content.parse()?;
children.push(HtmlNode::Expr(expr));
}
} else {
return Err(content.error("unexpected token in HTML"));
}
}
Ok(children)
}
pub(crate) fn children_to_node_tokens(children: &[HtmlNode]) -> proc_macro2::TokenStream {
match children.len() {
0 => quote! { euv_core::VirtualNode::Empty },
1 => {
let mut ts: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
children[0].to_tokens(&mut ts);
ts
}
_ => {
let mut child_tokens: Vec<proc_macro2::TokenStream> =
Vec::with_capacity(children.len());
for child in children {
let mut ts: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
child.to_tokens(&mut ts);
child_tokens.push(ts);
}
quote! { euv_core::VirtualNode::Fragment(vec![#(#child_tokens),*]) }
}
}
}
pub(crate) fn children_to_tokens(children: &[HtmlNode]) -> proc_macro2::TokenStream {
let mut child_tokens: Vec<proc_macro2::TokenStream> = Vec::with_capacity(children.len());
for child in children {
let mut ts: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
child.to_tokens(&mut ts);
child_tokens.push(ts);
}
quote! { vec![#(#child_tokens),*] }
}
pub(crate) fn children_to_node_tokens_inline(children: &[HtmlNode]) -> proc_macro2::TokenStream {
match children.len() {
0 => quote! { euv_core::VirtualNode::Empty },
1 => match &children[0] {
HtmlNode::Dynamic(expr) => quote! { euv_core::IntoNode::into_node(#expr) },
child => {
let mut ts: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
child.to_tokens(&mut ts);
ts
}
},
_ => {
let mut child_tokens: Vec<proc_macro2::TokenStream> =
Vec::with_capacity(children.len());
for child in children {
let ts: proc_macro2::TokenStream = match child {
HtmlNode::Dynamic(expr) => quote! { euv_core::IntoNode::into_node(#expr) },
_ => {
let mut ts: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
child.to_tokens(&mut ts);
ts
}
};
child_tokens.push(ts);
}
quote! { euv_core::VirtualNode::Fragment(vec![#(#child_tokens),*]) }
}
}
}
pub(crate) fn parse_attr_if(content: ParseStream) -> syn::Result<HtmlAttrIf> {
let mut branches: Vec<(Option<Expr>, Expr)> = Vec::new();
content.parse::<Token![if]>()?;
let cond_content;
braced!(cond_content in content);
let condition: Expr = cond_content.parse()?;
let body_content;
braced!(body_content in content);
let body: Expr = body_content.parse()?;
branches.push((Some(condition), body));
while content.peek(Token![else]) {
content.parse::<Token![else]>()?;
if content.peek(Token![if]) {
content.parse::<Token![if]>()?;
let cond_content;
braced!(cond_content in content);
let condition: Expr = cond_content.parse()?;
let body_content;
braced!(body_content in content);
let body: Expr = body_content.parse()?;
branches.push((Some(condition), body));
} else {
let body_content;
braced!(body_content in content);
let body: Expr = body_content.parse()?;
branches.push((None, body));
break;
}
}
Ok(HtmlAttrIf { branches })
}
pub(crate) fn strip_braces_from_expr(expr: &Expr) -> &Expr {
if let Expr::Block(expr_block) = expr {
let stmts: &Vec<syn::Stmt> = &expr_block.block.stmts;
if stmts.len() == 1
&& let syn::Stmt::Expr(inner, None) = &stmts[0]
{
return inner;
}
}
expr
}
pub(crate) fn attr_if_to_tokens(html_attr_if: &HtmlAttrIf) -> proc_macro2::TokenStream {
let mut if_chain: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
for (i, (condition, body)) in html_attr_if.branches.iter().enumerate() {
match (i, condition) {
(0, Some(cond)) => {
let stripped_cond: &Expr = strip_braces_from_expr(cond);
let stripped_body: &Expr = strip_braces_from_expr(body);
if_chain.extend(quote! {
if #stripped_cond { #stripped_body }
});
}
(_, Some(cond)) => {
let stripped_cond: &Expr = strip_braces_from_expr(cond);
let stripped_body: &Expr = strip_braces_from_expr(body);
if_chain.extend(quote! {
else if #stripped_cond { #stripped_body }
});
}
(_, None) => {
let stripped_body: &Expr = strip_braces_from_expr(body);
if_chain.extend(quote! {
else { #stripped_body }
});
}
}
}
if_chain
}
pub(crate) fn parse_attr_value(content: ParseStream, key_str: &str) -> syn::Result<HtmlAttrValue> {
if content.peek(Token![if]) {
let html_attr_if: HtmlAttrIf = parse_attr_if(content)?;
return Ok(HtmlAttrValue::If(html_attr_if));
}
if key_str == "style" && content.peek(syn::token::Brace) {
let style_content;
braced!(style_content in content);
let is_style_object: bool = style_content.peek(LitStr) || style_content.peek(Ident);
if is_style_object {
let mut style_props: Vec<(String, HtmlStylePropValue)> = Vec::new();
while !style_content.is_empty() {
let css_key: String = parse_kebab_name(&style_content)?;
style_content.parse::<Colon>()?;
let prop_value: HtmlStylePropValue = if style_content.peek(Token![if]) {
let html_attr_if: HtmlAttrIf = parse_attr_if(&style_content)?;
HtmlStylePropValue::If(html_attr_if)
} else if style_content.peek(LitStr) {
let lit: LitStr = style_content.parse()?;
HtmlStylePropValue::Literal(lit.value())
} else if style_content.peek(syn::token::Brace) {
let expr_content;
braced!(expr_content in style_content);
if expr_content.peek(Token![if]) {
let html_attr_if: HtmlAttrIf = parse_attr_if(&expr_content)?;
HtmlStylePropValue::If(html_attr_if)
} else {
let expr: Expr = expr_content.parse()?;
HtmlStylePropValue::Expr(expr)
}
} else {
let expr: Expr = style_content.parse()?;
HtmlStylePropValue::Expr(expr)
};
style_props.push((css_key, prop_value));
if style_content.peek(Semi) {
style_content.parse::<Semi>()?;
}
}
Ok(HtmlAttrValue::Style(style_props))
} else {
Ok(HtmlAttrValue::Expr(style_content.parse()?))
}
} else {
Ok(HtmlAttrValue::Expr(content.parse()?))
}
}