tide-jsx v0.4.0
git clone https://git.pyrossh.dev/tide-jsx
Tide + JSX
cc58dd1
— Gal Schlezinger
2019-09-21T08:30:36+03:00
initial implementation
- .gitignore +2 -0
- Cargo.lock +61 -0
- Cargo.toml +7 -0
- render/Cargo.toml +10 -0
- render/src/fragment.rs +28 -0
- render/src/html.rs +28 -0
- render/src/lib.rs +10 -0
- render/src/renderable.rs +41 -0
- render/src/simple_element.rs +41 -0
- render/src/text_element.rs +13 -0
- render_macros/Cargo.toml +15 -0
- render_macros/src/element_attribute.rs +52 -0
- render_macros/src/lib.rs +220 -0
- render_tests/Cargo.toml +11 -0
- render_tests/src/lib.rs +46 -0
.gitignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
target
|
|
2
|
+
**/*.rs.bk
|
Cargo.lock
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
[[package]]
|
|
4
|
+
name = "proc-macro2"
|
|
5
|
+
version = "1.0.3"
|
|
6
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "quote"
|
|
13
|
+
version = "1.0.2"
|
|
14
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[[package]]
|
|
20
|
+
name = "render"
|
|
21
|
+
version = "0.1.0"
|
|
22
|
+
dependencies = [
|
|
23
|
+
"render_macros 0.1.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[[package]]
|
|
27
|
+
name = "render_macros"
|
|
28
|
+
version = "0.1.0"
|
|
29
|
+
dependencies = [
|
|
30
|
+
"proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
31
|
+
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
32
|
+
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[[package]]
|
|
36
|
+
name = "render_tests"
|
|
37
|
+
version = "0.1.0"
|
|
38
|
+
dependencies = [
|
|
39
|
+
"render 0.1.0",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[[package]]
|
|
43
|
+
name = "syn"
|
|
44
|
+
version = "1.0.5"
|
|
45
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
46
|
+
dependencies = [
|
|
47
|
+
"proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
48
|
+
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
49
|
+
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[[package]]
|
|
53
|
+
name = "unicode-xid"
|
|
54
|
+
version = "0.2.0"
|
|
55
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
56
|
+
|
|
57
|
+
[metadata]
|
|
58
|
+
"checksum proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e98a83a9f9b331f54b924e68a66acb1bb35cb01fb0a23645139967abefb697e8"
|
|
59
|
+
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
|
60
|
+
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
|
|
61
|
+
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
Cargo.toml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
|
|
3
|
+
members = [
|
|
4
|
+
"render",
|
|
5
|
+
"render_macros",
|
|
6
|
+
"render_tests",
|
|
7
|
+
]
|
render/Cargo.toml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "render"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
authors = ["Gal Schlezinger <[email protected]>"]
|
|
5
|
+
edition = "2018"
|
|
6
|
+
|
|
7
|
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
8
|
+
|
|
9
|
+
[dependencies]
|
|
10
|
+
render_macros = { path = "../render_macros" }
|
render/src/fragment.rs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
use crate::Renderable;
|
|
2
|
+
|
|
3
|
+
/// A top-level root component to combine a same-level components
|
|
4
|
+
/// in a RSX fashion
|
|
5
|
+
///
|
|
6
|
+
/// ```rust
|
|
7
|
+
/// # #![feature(proc_macro_hygiene)]
|
|
8
|
+
/// # use render::html::HTML5Doctype;
|
|
9
|
+
/// # use render_macros::html;
|
|
10
|
+
/// # use render::fragment::Fragment;
|
|
11
|
+
/// let result = html! {
|
|
12
|
+
/// <Fragment>
|
|
13
|
+
/// <a />
|
|
14
|
+
/// <b />
|
|
15
|
+
/// </Fragment>
|
|
16
|
+
/// };
|
|
17
|
+
/// assert_eq!(result, "<a /><b />");
|
|
18
|
+
/// ```
|
|
19
|
+
#[derive(Debug)]
|
|
20
|
+
pub struct Fragment<T: Renderable> {
|
|
21
|
+
pub children: T,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl<T: Renderable> Renderable for Fragment<T> {
|
|
25
|
+
fn render(self) -> String {
|
|
26
|
+
self.children.render()
|
|
27
|
+
}
|
|
28
|
+
}
|
render/src/html.rs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
use crate::Renderable;
|
|
2
|
+
|
|
3
|
+
/// HTML 5 doctype declaration
|
|
4
|
+
///
|
|
5
|
+
/// ```rust
|
|
6
|
+
/// # #![feature(proc_macro_hygiene)]
|
|
7
|
+
/// # use render::html::HTML5Doctype;
|
|
8
|
+
/// # use render::html;
|
|
9
|
+
/// # use render::fragment::Fragment;
|
|
10
|
+
/// # let result =
|
|
11
|
+
/// html! {
|
|
12
|
+
/// <Fragment>
|
|
13
|
+
/// <HTML5Doctype />
|
|
14
|
+
/// <html>
|
|
15
|
+
/// <body />
|
|
16
|
+
/// </html>
|
|
17
|
+
/// </Fragment>
|
|
18
|
+
/// };
|
|
19
|
+
/// # assert_eq!(result, "<!DOCTYPE html><html><body /></html>");
|
|
20
|
+
/// ```
|
|
21
|
+
#[derive(Debug)]
|
|
22
|
+
pub struct HTML5Doctype;
|
|
23
|
+
|
|
24
|
+
impl Renderable for HTML5Doctype {
|
|
25
|
+
fn render(self) -> String {
|
|
26
|
+
"<!DOCTYPE html>".to_string()
|
|
27
|
+
}
|
|
28
|
+
}
|
render/src/lib.rs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
pub mod fragment;
|
|
2
|
+
pub mod html;
|
|
3
|
+
mod renderable;
|
|
4
|
+
mod simple_element;
|
|
5
|
+
mod text_element;
|
|
6
|
+
|
|
7
|
+
pub use fragment::Fragment;
|
|
8
|
+
pub use renderable::Renderable;
|
|
9
|
+
pub use render_macros::{html, rsx};
|
|
10
|
+
pub use simple_element::SimpleElement;
|
render/src/renderable.rs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/// A renderable component
|
|
2
|
+
pub trait Renderable: core::fmt::Debug + Sized {
|
|
3
|
+
/// Render the component to the HTML representation.
|
|
4
|
+
///
|
|
5
|
+
/// Mostly done using the `html!` macro to generate strings
|
|
6
|
+
/// by composing tags.
|
|
7
|
+
fn render(self) -> String;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
impl Renderable for () {
|
|
11
|
+
fn render(self) -> String {
|
|
12
|
+
"".to_string()
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
impl<A: Renderable, B: Renderable> Renderable for (A, B) {
|
|
17
|
+
fn render(self) -> String {
|
|
18
|
+
format!("{}{}", self.0.render(), self.1.render())
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl<A: Renderable, B: Renderable, C: Renderable> Renderable for (A, B, C) {
|
|
23
|
+
fn render(self) -> String {
|
|
24
|
+
((self.0, self.1), self.2).render()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
impl<A: Renderable, B: Renderable, C: Renderable, D: Renderable> Renderable for (A, B, C, D) {
|
|
29
|
+
fn render(self) -> String {
|
|
30
|
+
((self.0, self.1), (self.2, self.3)).render()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl<T: Renderable> Renderable for Option<T> {
|
|
35
|
+
fn render(self) -> String {
|
|
36
|
+
match self {
|
|
37
|
+
None => "".to_string(),
|
|
38
|
+
Some(x) => x.render(),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
render/src/simple_element.rs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
use crate::Renderable;
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
|
|
4
|
+
/// Simple HTML element tag
|
|
5
|
+
#[derive(Debug)]
|
|
6
|
+
pub struct SimpleElement<'a, T: Renderable> {
|
|
7
|
+
/// the HTML tag name, like `html`, `head`, `body`, `link`...
|
|
8
|
+
pub tag_name: &'a str,
|
|
9
|
+
pub attributes: Option<HashMap<&'a str, &'a str>>,
|
|
10
|
+
pub contents: Option<T>,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fn attributes_to_string<Key: std::fmt::Display + std::hash::Hash, Value: std::fmt::Debug>(
|
|
14
|
+
opt: &Option<HashMap<Key, Value>>,
|
|
15
|
+
) -> String {
|
|
16
|
+
match opt {
|
|
17
|
+
None => "".to_string(),
|
|
18
|
+
Some(map) => {
|
|
19
|
+
let s: String = map
|
|
20
|
+
.iter()
|
|
21
|
+
.map(|(key, value)| format!(" {}={:?}", key, value))
|
|
22
|
+
.collect();
|
|
23
|
+
s
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
impl<'a, T: Renderable> Renderable for SimpleElement<'a, T> {
|
|
29
|
+
fn render(self) -> String {
|
|
30
|
+
let attrs = attributes_to_string(&self.attributes);
|
|
31
|
+
match self.contents {
|
|
32
|
+
None => format!("<{}{} />", self.tag_name, attrs),
|
|
33
|
+
Some(renderable) => format!(
|
|
34
|
+
"<{tag_name}{attrs}>{contents}</{tag_name}>",
|
|
35
|
+
tag_name = self.tag_name,
|
|
36
|
+
attrs = attrs,
|
|
37
|
+
contents = renderable.render()
|
|
38
|
+
),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
render/src/text_element.rs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
use crate::Renderable;
|
|
2
|
+
|
|
3
|
+
impl Renderable for String {
|
|
4
|
+
fn render(self) -> String {
|
|
5
|
+
self
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
impl Renderable for &str {
|
|
10
|
+
fn render(self) -> String {
|
|
11
|
+
self.to_string()
|
|
12
|
+
}
|
|
13
|
+
}
|
render_macros/Cargo.toml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "render_macros"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
authors = ["Gal Schlezinger <[email protected]>"]
|
|
5
|
+
edition = "2018"
|
|
6
|
+
|
|
7
|
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
8
|
+
|
|
9
|
+
[lib]
|
|
10
|
+
proc-macro = true
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
syn = { version = "1.0", features = ["full"] }
|
|
14
|
+
quote = "1.0"
|
|
15
|
+
proc-macro2 = "1.0"
|
render_macros/src/element_attribute.rs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
use quote::quote;
|
|
2
|
+
use syn::parse::{Parse, ParseStream, Result};
|
|
3
|
+
|
|
4
|
+
pub enum ElementAttribute {
|
|
5
|
+
Punned(syn::Ident),
|
|
6
|
+
WithValue(syn::Ident, syn::Block),
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
impl ElementAttribute {
|
|
10
|
+
pub fn ident(&self) -> &syn::Ident {
|
|
11
|
+
match self {
|
|
12
|
+
Self::Punned(ident) | Self::WithValue(ident, _) => ident,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub fn value_tokens(&self) -> proc_macro2::TokenStream {
|
|
17
|
+
match self {
|
|
18
|
+
Self::WithValue(_, value) => quote!(#value),
|
|
19
|
+
Self::Punned(ident) => quote!(#ident),
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl PartialEq for ElementAttribute {
|
|
25
|
+
fn eq(&self, other: &Self) -> bool {
|
|
26
|
+
self.ident() == other.ident()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
impl Eq for ElementAttribute {}
|
|
31
|
+
|
|
32
|
+
impl std::hash::Hash for ElementAttribute {
|
|
33
|
+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
34
|
+
std::hash::Hash::hash(self.ident(), state)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl Parse for ElementAttribute {
|
|
39
|
+
fn parse(input: ParseStream) -> Result<Self> {
|
|
40
|
+
let name = input.parse::<syn::Ident>()?;
|
|
41
|
+
let not_punned = input.peek(syn::Token![=]);
|
|
42
|
+
|
|
43
|
+
if !not_punned {
|
|
44
|
+
return Ok(Self::Punned(name));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
input.parse::<syn::Token![=]>()?;
|
|
48
|
+
let value = input.parse::<syn::Block>()?;
|
|
49
|
+
|
|
50
|
+
Ok(Self::WithValue(name, value))
|
|
51
|
+
}
|
|
52
|
+
}
|
render_macros/src/lib.rs
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#![feature(proc_macro_diagnostic, proc_macro_hygiene)]
|
|
2
|
+
|
|
3
|
+
// TODO: Extract a `Children` struct that can implement `Parse` and `ToTokens`:
|
|
4
|
+
// - `Parse` will do the nasty things inside `Element`
|
|
5
|
+
// - `ToTokens` will do the trick with the tuples
|
|
6
|
+
|
|
7
|
+
extern crate proc_macro;
|
|
8
|
+
|
|
9
|
+
mod element_attribute;
|
|
10
|
+
|
|
11
|
+
use element_attribute::ElementAttribute;
|
|
12
|
+
use proc_macro::TokenStream;
|
|
13
|
+
use quote::{quote, ToTokens};
|
|
14
|
+
use std::collections::HashSet;
|
|
15
|
+
use syn::parse::{Parse, ParseStream, Result};
|
|
16
|
+
use syn::parse_macro_input;
|
|
17
|
+
|
|
18
|
+
struct Element {
|
|
19
|
+
name: syn::Ident,
|
|
20
|
+
attributes: HashSet<ElementAttribute>,
|
|
21
|
+
children: Vec<RenderableItem>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
enum RenderableItem {
|
|
25
|
+
Element(Element),
|
|
26
|
+
RawBlock(syn::Block),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl ToTokens for RenderableItem {
|
|
30
|
+
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
|
31
|
+
match self {
|
|
32
|
+
Self::Element(element) => element.to_tokens(tokens),
|
|
33
|
+
Self::RawBlock(block) => {
|
|
34
|
+
let ts = quote! { #block };
|
|
35
|
+
ts.to_tokens(tokens);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl Parse for RenderableItem {
|
|
42
|
+
fn parse(input: ParseStream) -> Result<Self> {
|
|
43
|
+
match input.parse::<Element>() {
|
|
44
|
+
Ok(element) => Ok(Self::Element(element)),
|
|
45
|
+
Err(_) => {
|
|
46
|
+
let block = input.parse::<syn::Block>()?;
|
|
47
|
+
Ok(Self::RawBlock(block))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
impl Parse for Element {
|
|
54
|
+
fn parse(input: ParseStream) -> Result<Self> {
|
|
55
|
+
let mut attributes: HashSet<ElementAttribute> = HashSet::new();
|
|
56
|
+
let _starts_a_tag = input.parse::<syn::Token![<]>().is_ok();
|
|
57
|
+
|
|
58
|
+
let name = input.parse()?;
|
|
59
|
+
|
|
60
|
+
while input.peek(syn::Ident) {
|
|
61
|
+
if let Ok(attribute) = input.parse::<ElementAttribute>() {
|
|
62
|
+
if attributes.contains(&attribute) {
|
|
63
|
+
let error_message = format!(
|
|
64
|
+
"There is a previous definition of the {} attribute",
|
|
65
|
+
attribute.ident()
|
|
66
|
+
);
|
|
67
|
+
attribute
|
|
68
|
+
.ident()
|
|
69
|
+
.span()
|
|
70
|
+
.unwrap()
|
|
71
|
+
.warning(error_message)
|
|
72
|
+
.emit();
|
|
73
|
+
}
|
|
74
|
+
attributes.insert(attribute);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let can_have_contents = input.parse::<syn::Token![/]>().is_err();
|
|
79
|
+
input.parse::<syn::Token![>]>()?;
|
|
80
|
+
|
|
81
|
+
let mut children = vec![];
|
|
82
|
+
|
|
83
|
+
if can_have_contents {
|
|
84
|
+
while !input.peek(syn::Token![<]) || !input.peek2(syn::Token![/]) {
|
|
85
|
+
if let Ok(child) = input.parse::<RenderableItem>() {
|
|
86
|
+
children.push(child);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// parse closing
|
|
91
|
+
input.parse::<syn::Token![<]>()?;
|
|
92
|
+
input.parse::<syn::Token![/]>()?;
|
|
93
|
+
let closing_name: syn::Ident = input.parse()?;
|
|
94
|
+
if closing_name != name {
|
|
95
|
+
let error_message = format!("Expected closing tag for: <{}>", &name);
|
|
96
|
+
closing_name.span().unwrap().error(error_message).emit();
|
|
97
|
+
}
|
|
98
|
+
input.parse::<syn::Token![>]>()?;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Ok(Element {
|
|
102
|
+
name,
|
|
103
|
+
attributes,
|
|
104
|
+
children,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
impl Element {
|
|
110
|
+
pub fn is_custom_element(&self) -> bool {
|
|
111
|
+
let name = self.name.to_string();
|
|
112
|
+
let first_letter = name.get(0..1).unwrap();
|
|
113
|
+
first_letter.to_uppercase() == first_letter
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
impl ToTokens for Element {
|
|
118
|
+
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
|
119
|
+
let Element { name, children, .. } = self;
|
|
120
|
+
|
|
121
|
+
let children_quotes: Vec<_> = children
|
|
122
|
+
.iter()
|
|
123
|
+
.map(|child| {
|
|
124
|
+
quote! { #child }
|
|
125
|
+
})
|
|
126
|
+
.collect();
|
|
127
|
+
let children_tuple = match children_quotes.len() {
|
|
128
|
+
0 => quote! { Option::<()>::None },
|
|
129
|
+
1 => quote! { Some(#(#children_quotes)*) },
|
|
130
|
+
_ => {
|
|
131
|
+
let mut iter = children_quotes.iter();
|
|
132
|
+
let first = iter.next().unwrap();
|
|
133
|
+
let second = iter.next().unwrap();
|
|
134
|
+
let tuple_of_tuples = iter.fold(
|
|
135
|
+
quote!((#first, #second)),
|
|
136
|
+
|renderable, current| quote!((#current, #renderable)),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
quote! { Some(#tuple_of_tuples) }
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
let declaration = if self.is_custom_element() {
|
|
144
|
+
let mut attrs: Vec<_> = self
|
|
145
|
+
.attributes
|
|
146
|
+
.iter()
|
|
147
|
+
.map(|attribute| {
|
|
148
|
+
let ident = attribute.ident();
|
|
149
|
+
let value = attribute.value_tokens();
|
|
150
|
+
|
|
151
|
+
quote! {
|
|
152
|
+
#ident: #value
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
.collect();
|
|
156
|
+
|
|
157
|
+
if children_quotes.len() > 0 {
|
|
158
|
+
attrs.push(quote! {
|
|
159
|
+
children: #children_tuple
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if attrs.len() == 0 {
|
|
164
|
+
quote! { #name }
|
|
165
|
+
} else {
|
|
166
|
+
quote! {
|
|
167
|
+
#name {
|
|
168
|
+
#(#attrs),*
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
let attrs: Vec<_> = self
|
|
174
|
+
.attributes
|
|
175
|
+
.iter()
|
|
176
|
+
.map(|attribute| {
|
|
177
|
+
let ident = attribute.ident();
|
|
178
|
+
let value = attribute.value_tokens();
|
|
179
|
+
|
|
180
|
+
quote! {
|
|
181
|
+
hm.insert(stringify!(#ident), #value);
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
.collect();
|
|
185
|
+
let attributes_value = if self.attributes.len() == 0 {
|
|
186
|
+
quote!(None)
|
|
187
|
+
} else {
|
|
188
|
+
quote! {{
|
|
189
|
+
let mut hm = std::collections::HashMap::<&str, &str>::new();
|
|
190
|
+
#(#attrs)*
|
|
191
|
+
Some(hm)
|
|
192
|
+
}}
|
|
193
|
+
};
|
|
194
|
+
quote! {
|
|
195
|
+
::render::SimpleElement {
|
|
196
|
+
tag_name: stringify!(#name),
|
|
197
|
+
attributes: #attributes_value,
|
|
198
|
+
contents: #children_tuple,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
declaration.to_tokens(tokens);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// Render a component tree to an HTML string
|
|
207
|
+
#[proc_macro]
|
|
208
|
+
pub fn html(input: TokenStream) -> TokenStream {
|
|
209
|
+
let el = proc_macro2::TokenStream::from(rsx(input));
|
|
210
|
+
let result = quote! { ::render::Renderable::render(#el) };
|
|
211
|
+
TokenStream::from(result)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Generate a renderable component tree
|
|
215
|
+
#[proc_macro]
|
|
216
|
+
pub fn rsx(input: TokenStream) -> TokenStream {
|
|
217
|
+
let el = parse_macro_input!(input as Element);
|
|
218
|
+
let result = quote! { #el };
|
|
219
|
+
TokenStream::from(result)
|
|
220
|
+
}
|
render_tests/Cargo.toml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "render_tests"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
authors = ["Gal Schlezinger <[email protected]>"]
|
|
5
|
+
edition = "2018"
|
|
6
|
+
publish = false
|
|
7
|
+
|
|
8
|
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
9
|
+
|
|
10
|
+
[dependencies]
|
|
11
|
+
render = { path = "../render" }
|
render_tests/src/lib.rs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#![feature(proc_macro_hygiene)]
|
|
2
|
+
|
|
3
|
+
use render::html::HTML5Doctype;
|
|
4
|
+
use render::{html, rsx, Renderable, Fragment};
|
|
5
|
+
|
|
6
|
+
#[derive(Debug)]
|
|
7
|
+
struct Hello<'a, T: Renderable> {
|
|
8
|
+
world: &'a str,
|
|
9
|
+
yes: i32,
|
|
10
|
+
children: T,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
impl<'a, T: Renderable> Renderable for Hello<'a, T> {
|
|
14
|
+
fn render(self) -> String {
|
|
15
|
+
html! {
|
|
16
|
+
<b class={"some_bem_class"}>
|
|
17
|
+
{format!("{}", self.world)}
|
|
18
|
+
<br />
|
|
19
|
+
{format!("A number: {}", self.yes)}
|
|
20
|
+
{self.children}
|
|
21
|
+
</b>
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub fn it_works() -> String {
|
|
27
|
+
let world = "hello";
|
|
28
|
+
let other_value = rsx! {
|
|
29
|
+
<em>{format!("hello world?")}</em>
|
|
30
|
+
};
|
|
31
|
+
let value = html! {
|
|
32
|
+
<Fragment>
|
|
33
|
+
<HTML5Doctype />
|
|
34
|
+
<Hello world yes={1 + 1}>
|
|
35
|
+
<div>{format!("HEY!")}</div>
|
|
36
|
+
{other_value}
|
|
37
|
+
</Hello>
|
|
38
|
+
</Fragment>
|
|
39
|
+
};
|
|
40
|
+
value
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[test]
|
|
44
|
+
pub fn verify_works() {
|
|
45
|
+
println!("{}", it_works());
|
|
46
|
+
}
|