Skip to main content

render_macros/
lib.rs

1#![feature(proc_macro_diagnostic)]
2
3extern crate proc_macro;
4
5mod child;
6mod children;
7mod element;
8mod element_attribute;
9mod element_attributes;
10mod function_component;
11mod tags;
12
13use element::Element;
14use proc_macro::TokenStream;
15use quote::quote;
16use syn::parse_macro_input;
17
18/// Render a component tree to an HTML string, using XML-like tags.
19///
20/// The ergonomics are based on JSX:
21///
22/// ### Simple HTML elements start with a lowercase
23///
24/// ```rust
25/// # #![feature(proc_macro_hygiene)]
26/// # use pretty_assertions::assert_eq;
27/// # use render_macros::html;
28/// let rendered = html! { <div id={"main"}>{"Hello"}</div> };
29/// assert_eq!(rendered, r#"<div id="main">Hello</div>"#);
30/// ```
31///
32/// ### Custom components start with an uppercase
33///
34/// ```rust
35/// # #![feature(proc_macro_hygiene)]
36/// # use pretty_assertions::assert_eq;
37/// # use render_macros::{html, rsx};
38/// use render::Render;
39///
40/// #[derive(Debug)]
41/// struct Heading<'t> { title: &'t str }
42///
43/// impl<'t> Render for Heading<'t> {
44///     fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
45///         Render::render_into(rsx! { <h1>{self.title}</h1> }, writer)
46///     }
47/// }
48///
49/// let rendered = html! { <Heading title={"Hello world!"} /> };
50///
51/// assert_eq!(rendered, r#"<h1>Hello world!</h1>"#);
52/// ```
53///
54/// ### Values are always surrounded by curly braces
55///
56/// ```rust
57/// # #![feature(proc_macro_hygiene)]
58/// # use render_macros::html;
59/// # use pretty_assertions::assert_eq;
60/// let rendered = html! {
61///     <div id={"main"} />
62/// };
63///
64/// assert_eq!(rendered, r#"<div id="main"/>"#);
65/// ```
66///
67/// ### HTML entities can accept dashed-separated value
68///
69/// ```rust
70/// # #![feature(proc_macro_hygiene)]
71/// # use render_macros::html;
72/// # use pretty_assertions::assert_eq;
73/// let rendered = html! {
74///     <div data-testid={"sometestid"} />
75/// };
76///
77/// assert_eq!(rendered, r#"<div data-testid="sometestid"/>"#);
78/// ```
79///
80/// ### Custom components can't accept dashed-separated values
81///
82/// ```compile_fail
83/// # #![feature(proc_macro_hygiene)]
84/// # use render_macros::html;
85/// // This will fail the compilation:
86/// let rendered = html! {
87///     <MyElement data-testid={"some test id"} />
88/// };
89/// ```
90///
91/// ### Punning is supported
92/// but instead of expanding to `value={true}`, it expands to
93/// `value={value}` like Rust's punning
94///
95/// ```rust
96/// # #![feature(proc_macro_hygiene)]
97/// # use render_macros::html;
98/// # use pretty_assertions::assert_eq;
99/// let class = "someclass";
100///
101/// let rendered = html! {
102///     <div class />
103/// };
104///
105/// assert_eq!(rendered, r#"<div class="someclass"/>"#);
106/// ```
107///
108/// ### Punning is not supported for dashed-delimited attributes
109///
110/// ```compile_fail
111/// # #![feature(proc_macro_hygiene)]
112/// # use render_macros::html;
113///
114/// let rendered = html! {
115///     <div this-wont-work />
116/// };
117///
118/// assert_eq!(rendered, r#"<div class="some_class"/>"#);
119/// ```
120#[proc_macro]
121pub fn html(input: TokenStream) -> TokenStream {
122    let el = proc_macro2::TokenStream::from(rsx(input));
123    let result = quote! { ::render::Render::render(#el) };
124    TokenStream::from(result)
125}
126
127/// Generate a renderable component tree, before rendering it
128#[proc_macro]
129pub fn rsx(input: TokenStream) -> TokenStream {
130    let el = parse_macro_input!(input as Element);
131    let result = quote! { #el };
132    TokenStream::from(result)
133}
134
135/// A syntactic sugar for implementing [`Render`](../render/trait.Render.html) conveniently
136/// using functions.
137///
138/// This attribute should be above a stand-alone function definition that returns a
139/// [`String`](std::string::String):
140///
141/// ```rust
142/// # #![feature(proc_macro_hygiene)]
143/// # use render_macros::{component, rsx};
144/// #
145/// #[component]
146/// fn UserFn(name: String) {
147///     rsx! { <div>{format!("Hello, {}", name)}</div> }
148/// }
149/// ```
150///
151/// Practically, this is exactly the same as using the [Render](../render/trait.Render.html) trait:
152///
153/// ```rust
154/// # #![feature(proc_macro_hygiene)]
155/// # use render_macros::{component, rsx, html};
156/// # use render::Render;
157/// # use pretty_assertions::assert_eq;
158/// #
159/// #[derive(Debug)]
160/// struct User { name: String }
161///
162/// impl render::Render for User {
163///     fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
164///         Render::render_into(rsx! { <div>{format!("Hello, {}", self.name)}</div> }, writer)
165///     }
166/// }
167///
168/// # #[component]
169/// # fn UserFn(name: String) {
170/// #     rsx! { <div>{format!("Hello, {}", name)}</div> }
171/// # }
172/// #
173/// # let from_fn = html! {
174/// #     <UserFn name={String::from("Schniz")} />
175/// # };
176/// #
177/// # let from_struct = html! {
178/// #     <User name={String::from("Schniz")} />
179/// # };
180/// #
181/// # assert_eq!(from_fn, from_struct);
182/// ```
183#[proc_macro_attribute]
184pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
185    let f = parse_macro_input!(item as syn::ItemFn);
186    function_component::create_function_component(f)
187}