use crate::maybe_dollar::TypeAndMaybeDollar;
use crate::{Html, HtmlToTokensConfig};
use syn::Token;
use syn::parse::{Parse, ParseStream};
pub(super) struct HtmlAndConfig {
pub html: Html,
pub config: HtmlConfig,
}
#[derive(Debug)]
pub(crate) struct HtmlConfig {
pub real_dom_ty: TypeAndMaybeDollar,
}
#[derive(Default)]
struct ParsedConfig {
real_dom: Option<TypeAndMaybeDollar>,
root_node_ident_override: Option<syn::Ident>,
}
pub(crate) enum ParsedHtmlConfigField {
RealDom(TypeAndMaybeDollar),
RootNode(syn::Ident),
}
impl Parse for HtmlAndConfig {
fn parse(input: ParseStream) -> syn::Result<Self> {
let config = HtmlConfig::parse(input)?;
input.parse::<Token![;]>()?;
let html = input.parse::<Html>()?;
Ok(Self { html, config })
}
}
impl Parse for HtmlConfig {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut fields = Vec::new();
loop {
let next_is_semicolon = input.peek(Token![;]);
let has_ended = input.is_empty();
if next_is_semicolon {
break;
} else if has_ended {
break;
}
let field = input.parse::<ParsedHtmlConfigField>()?;
fields.push(field);
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
}
Ok(HtmlConfig::from_fields(fields))
}
}
impl HtmlConfig {
pub(crate) fn from_fields(fields: Vec<ParsedHtmlConfigField>) -> Self {
let mut parsed_config = ParsedConfig::default();
for field in fields {
match field {
ParsedHtmlConfigField::RealDom(real_dom_ty) => {
parsed_config.real_dom = Some(real_dom_ty);
}
ParsedHtmlConfigField::RootNode(root_node) => {
parsed_config.root_node_ident_override = Some(root_node);
}
}
}
HtmlConfig {
real_dom_ty: parsed_config.real_dom.unwrap(),
}
}
}
impl Parse for ParsedHtmlConfigField {
fn parse(input: ParseStream) -> syn::Result<Self> {
let key = input.parse::<syn::Ident>()?;
let _equals = input.parse::<Token![=]>()?;
let field = match key.to_string().as_str() {
"real_dom" => {
let ty = input.parse::<TypeAndMaybeDollar>()?;
ParsedHtmlConfigField::RealDom(ty)
}
"root_node_ident" => {
let ident = input.parse::<syn::Ident>()?;
ParsedHtmlConfigField::RootNode(ident)
}
_ => todo!("add UI test that we error when there is an unrecognized key"),
};
Ok(field)
}
}
impl HtmlAndConfig {
pub fn into_tokens(self) -> proc_macro2::TokenStream {
self.html.into_tokens(HtmlToTokensConfig {
real_dom_ty: self.config.real_dom_ty.ty,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use proc_macro2::TokenStream;
use quote::quote;
#[test]
fn configure_real_dom() {
let tokens = quote! {
real_dom = SomeType ;
<div></div>
};
let expected = quote! {
{
let mut node_0 = VirtualNode::<SomeType>::new_element("div");
node_0
}
};
assert_expected_html_with_config_tokens(tokens, expected);
}
#[test]
fn supports_trailing_comma() {
let tokens = quote! {
real_dom = SomeType, ;
<div></div>
};
let expected = quote! {
{
let mut node_0 = VirtualNode::<SomeType>::new_element("div");
node_0
}
};
assert_expected_html_with_config_tokens(tokens, expected);
}
#[track_caller]
fn assert_expected_html_with_config_tokens(start: TokenStream, expected: TokenStream) {
let html: HtmlAndConfig = syn::parse2(start).unwrap();
assert_eq!(html.into_tokens().to_string(), expected.to_string());
}
}