use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use rstml::{
Infallible, Parser, ParserConfig,
node::{
KeyedAttribute, KeyedAttributeValue, Node, NodeAttribute, NodeBlock, NodeComment,
NodeDoctype, NodeElement, NodeFragment, NodeText, RawText,
},
};
pub fn parse_view(input: TokenStream) -> TokenStream {
let parser_config = ParserConfig::new().recover_block(true);
let parser = Parser::new(parser_config);
let result = parser.parse_recoverable(input);
let (nodes, diagnostics) = match result {
rstml::ParsingResult::Ok(nodes) => (nodes, vec![]),
rstml::ParsingResult::Failed(diagnostics) => (vec![], diagnostics),
rstml::ParsingResult::Partial(nodes, diagnostics) => (nodes, diagnostics),
};
if nodes.is_empty()
&& let Some(diagnostic) = diagnostics.first()
{
return diagnostic.clone().emit_as_expr_tokens();
}
nodes
.into_iter()
.map(parse_node)
.flat_map(TokenStream::into_iter)
.collect()
}
fn parse_node(node: Node) -> TokenStream {
match node {
Node::Comment(node_comment) => parse_comment(node_comment),
Node::Doctype(node_doctype) => parse_doctype(node_doctype),
Node::RawText(raw_text) => parse_raw_text(raw_text),
Node::Fragment(node_fragment) => parse_fragment(node_fragment),
Node::Block(node_block) => parse_block(node_block),
Node::Text(node_text) => parse_text(node_text),
Node::Element(node_element) => parse_element(node_element),
Node::Custom(_) => unimplemented!(),
}
}
fn parse_comment(comment: NodeComment) -> TokenStream {
quote! {}
}
fn parse_doctype(doctype: NodeDoctype) -> TokenStream {
syn::Error::new_spanned(
doctype,
"DOCTYPE is not supported. Do you think this is HTML?",
)
.into_compile_error()
}
fn parse_raw_text(raw_text: RawText) -> TokenStream {
syn::Error::new_spanned(
raw_text,
"Raw text is not supported. Please wrap with `\"\"` for a string or `{}` for an expr.",
)
.into_compile_error()
}
fn parse_fragment(fragment: NodeFragment<Infallible>) -> TokenStream {
syn::Error::new_spanned(fragment, "Fragments are coming soon. ")
.into_compile_error()
}
fn parse_block(block: NodeBlock) -> TokenStream {
block.into_token_stream()
}
fn parse_text(text: NodeText) -> TokenStream {
text.into_token_stream()
}
fn parse_element(element: NodeElement<Infallible>) -> TokenStream {
let open_tag = element.open_tag;
let children = element.children;
let close_tag = element.close_tag;
let open_name = open_tag.name;
let close_name = match close_tag {
Some(tag) => tag.name,
None => open_name.clone(),
};
let props = open_tag
.attributes
.into_iter()
.map(parse_attribute)
.collect::<Vec<_>>();
let children = children.into_iter().map(parse_node).collect::<Vec<_>>();
let children = if children.is_empty() {
quote! {}
} else {
quote! { .children((#(#children),*,)) }
};
quote! {
#open_name::construct_widget(
#close_name::builder()
#(#props)*
#children
.build()
)
}
}
fn parse_attribute(attribute: NodeAttribute) -> TokenStream {
match attribute {
NodeAttribute::Block(node_block) => {
syn::Error::new_spanned(node_block, "Blocks are not yet supported in widget tags.")
.into_compile_error()
}
NodeAttribute::Attribute(keyed_attribute) => parse_keyed_attribute(keyed_attribute),
}
}
fn parse_keyed_attribute(keyed_attribute: KeyedAttribute) -> TokenStream {
let key = keyed_attribute.key;
let possible_value = keyed_attribute.possible_value;
match possible_value {
KeyedAttributeValue::Binding(fn_binding) => {
syn::Error::new_spanned(fn_binding, "Let bindings are not yet supported.")
.into_compile_error()
}
KeyedAttributeValue::Value(attribute_value_expr) => {
let value = attribute_value_expr.value;
quote! { .#key(#value) }
}
KeyedAttributeValue::None => quote! { .#key(true) },
}
}