maple_core_macro/
lib.rs

1#![allow(clippy::eval_order_dependence)] // Needed when using `syn::parenthesized!`.
2
3mod attributes;
4mod children;
5mod component;
6mod element;
7mod text;
8
9use proc_macro::TokenStream;
10use quote::{quote, ToTokens};
11use syn::ext::IdentExt;
12use syn::parse::{Parse, ParseStream};
13use syn::token::Paren;
14use syn::{parse_macro_input, Ident, LitStr, Result, Token};
15
16pub(crate) enum HtmlType {
17    Component,
18    Element,
19    Text,
20}
21
22pub(crate) enum HtmlTree {
23    Component(component::Component),
24    Element(element::Element),
25    Text(text::Text),
26}
27
28impl HtmlTree {
29    fn peek_type(input: ParseStream) -> Option<HtmlType> {
30        let input = input.fork(); // do not affect original ParseStream
31
32        if input.peek(LitStr) || input.peek(Paren) {
33            Some(HtmlType::Text)
34        } else if input.peek(Token![::]) {
35            Some(HtmlType::Component)
36        } else if input.peek(Ident::peek_any) {
37            let ident: Ident = input.call(Ident::parse_any).ok()?;
38            let ident = ident.to_string();
39
40            if ident.chars().next().unwrap().is_ascii_uppercase() || input.peek(Token![::]) {
41                Some(HtmlType::Component)
42            } else {
43                Some(HtmlType::Element)
44            }
45        } else {
46            None
47        }
48    }
49}
50
51impl Parse for HtmlTree {
52    fn parse(input: ParseStream) -> Result<Self> {
53        let html_type = match Self::peek_type(input) {
54            Some(html_type) => html_type,
55            None => return Err(input.error("expected a valid HTML node")),
56        };
57
58        Ok(match html_type {
59            HtmlType::Component => Self::Component(input.parse()?),
60            HtmlType::Element => Self::Element(input.parse()?),
61            HtmlType::Text => Self::Text(input.parse()?),
62        })
63    }
64}
65
66impl ToTokens for HtmlTree {
67    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
68        match self {
69            Self::Component(component) => component.to_tokens(tokens),
70            Self::Element(element) => element.to_tokens(tokens),
71            Self::Text(text) => text.to_tokens(tokens),
72        }
73    }
74}
75
76/// A macro for ergonomically creating complex UI structures.
77///
78/// TODO: write some more docs
79#[proc_macro]
80pub fn template(input: TokenStream) -> TokenStream {
81    let input = parse_macro_input!(input as HtmlTree);
82
83    let quoted = quote! {
84        ::maple_core::TemplateResult::new(::std::convert::Into::<_>::into(#input))
85    };
86
87    TokenStream::from(quoted)
88}