nitron-macros 0.1.1

Create native UIs in Rust
Documentation
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) },
    }
}

// For we are his workmanship, created in Christ Jesus for good works, which God prepared beforehand, that we should walk in them. - Ephesians 2:10