b03ce49b
—
Gal Schlezinger 6 years ago
Use `Write` instead of owned strings (#8)
- Cargo.lock +0 -7
- README.md +107 -7
- render/Cargo.toml +0 -1
- render/src/fragment.rs +6 -6
- render/src/html.rs +5 -4
- render/src/html_escaping.rs +26 -0
- render/src/lib.rs +115 -9
- render/src/render.rs +61 -0
- render/src/renderable.rs +0 -96
- render/src/simple_element.rs +31 -24
- render/src/text_element.rs +14 -13
- render_macros/src/children.rs +2 -3
- render_macros/src/element_attributes.rs +9 -10
- render_macros/src/function_component.rs +8 -5
- render_macros/src/lib.rs +25 -25
- render_tests/src/lib.rs +52 -67
Cargo.lock
CHANGED
|
@@ -22,11 +22,6 @@ name = "difference"
|
|
|
22
22
|
version = "2.0.0"
|
|
23
23
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
24
24
|
|
|
25
|
-
[[package]]
|
|
26
|
-
name = "htmlescape"
|
|
27
|
-
version = "0.3.1"
|
|
28
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
29
|
-
|
|
30
25
|
[[package]]
|
|
31
26
|
name = "output_vt100"
|
|
32
27
|
version = "0.1.2"
|
|
@@ -66,7 +61,6 @@ dependencies = [
|
|
|
66
61
|
name = "render"
|
|
67
62
|
version = "0.2.0"
|
|
68
63
|
dependencies = [
|
|
69
|
-
"htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
70
64
|
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
71
65
|
"render_macros 0.2.0",
|
|
72
66
|
]
|
|
@@ -128,7 +122,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
128
122
|
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
|
129
123
|
"checksum ctor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3e061727ebef83bbccac7c27b9a5ff9fd83094d34cb20f4005440a9562a27de7"
|
|
130
124
|
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
|
131
|
-
"checksum htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
|
|
132
125
|
"checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
|
|
133
126
|
"checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
|
|
134
127
|
"checksum proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e98a83a9f9b331f54b924e68a66acb1bb35cb01fb0a23645139967abefb697e8"
|
README.md
CHANGED
|
@@ -8,13 +8,25 @@ XML rendering, but can work with other usages as well, like ReasonML's [`Pastel`
|
|
|
8
8
|
|
|
9
9
|
## How?
|
|
10
10
|
|
|
11
|
-
A renderable component is a struct that implements the `
|
|
11
|
+
A renderable component is a struct that implements the `Render` trait. There
|
|
12
12
|
are multiple macros that provide a better experience implementing Renderable:
|
|
13
13
|
|
|
14
|
-
* `html!` for the JSX ergonomics
|
|
15
|
-
* `#[component]` for
|
|
14
|
+
* `#[component]` for defining components using a function
|
|
15
|
+
* `rsx!` for composing elements with JSX ergonomics
|
|
16
|
+
* `html!` for composing elements and render them to a string
|
|
16
17
|
|
|
17
|
-
## Why
|
|
18
|
+
## Why is this different from...
|
|
19
|
+
|
|
20
|
+
### `handlebars`?
|
|
21
|
+
|
|
22
|
+
Handlebars is an awesome spec that lets us devs define templates and work
|
|
23
|
+
seemlessly between languages and frameworks. Unfortunately, it does not guarantee any of Rust's
|
|
24
|
+
type-safety, due to its spec. This forces you to write tests for validating types for your views, like you would in a dynamically typed language. These tests weren't necessary in a type-safe language like Rust — but Handlebars is JSON-oriented, which doesn't comply Rust's type system.
|
|
25
|
+
|
|
26
|
+
`render` provides the same level of type-safety Rust provides, with no compromises of
|
|
27
|
+
ergonomics or speed.
|
|
28
|
+
|
|
29
|
+
### `typed-html`?
|
|
18
30
|
|
|
19
31
|
`typed-html` is a wonderful library. Unfortunately, it focused its power in strictness of the HTML spec itself, and doesn't allow arbitrary compositions of custom elements.
|
|
20
32
|
|
|
@@ -22,6 +34,92 @@ are multiple macros that provide a better experience implementing Renderable:
|
|
|
22
34
|
|
|
23
35
|
## Usage
|
|
24
36
|
|
|
37
|
+
> Note: `render` needs the `nightly` Rust compiler, for now, so it will have hygienic macros.
|
|
38
|
+
|
|
39
|
+
This means you will need to add the following feature flag in the root of your `lib.rs`/`main.rs`:
|
|
40
|
+
|
|
41
|
+
```rust
|
|
42
|
+
#![feature(proc_macro_hygiene)]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Simple HTML rendering
|
|
46
|
+
|
|
47
|
+
In order to render a simple HTML fragment into a `String`, use the `rsx!` macro to generate a
|
|
48
|
+
component tree, and call `render` on it:
|
|
49
|
+
|
|
50
|
+
```rust
|
|
51
|
+
#![feature(proc_macro_hygiene)]
|
|
52
|
+
|
|
53
|
+
use render::{rsx, Render};
|
|
54
|
+
|
|
55
|
+
let tree = rsx! {
|
|
56
|
+
<div>
|
|
57
|
+
<h1>{"Hello!"}</h1>
|
|
58
|
+
<p>{"Hello world!"}</p>
|
|
59
|
+
</div>
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
assert_eq!(tree.render(), "<div><h1>Hello!</h1><p>Hello world!</p></div>");
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Because this is so common, there's another macro called `html!` that calls `rsx!` to generate
|
|
66
|
+
a component tree, and then calls `render` on it. Most of the time, you'll find yourself using
|
|
67
|
+
the `rsx!` macro to compose arbitrary components, and only calling `html!` when you need a
|
|
68
|
+
String output, when sending a response or generating a Markdown file.
|
|
69
|
+
|
|
70
|
+
In Render, attributes and plain strings are escaped using the `render::html_escaping` module. In order to
|
|
71
|
+
use un-escaped values so you can dangerously insert raw HTML, use the `raw!` macro around your
|
|
72
|
+
string:
|
|
73
|
+
|
|
74
|
+
```rust
|
|
75
|
+
#![feature(proc_macro_hygiene)]
|
|
76
|
+
|
|
77
|
+
use render::{html, raw};
|
|
78
|
+
|
|
79
|
+
let tree = html! {
|
|
80
|
+
<div>
|
|
81
|
+
<p>{"<Hello />"}</p>
|
|
82
|
+
<p>{raw!("<Hello />")}</p>
|
|
83
|
+
</div>
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
assert_eq!(tree, "<div><p><Hello /></p><p><Hello /></p></div>");
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Custom components
|
|
90
|
+
|
|
91
|
+
Render's greatest ability is to provide type-safety along with custom renderable components.
|
|
92
|
+
Introducing new components is as easy as defining a function that returns a `Render` value.
|
|
93
|
+
|
|
94
|
+
In order to build up components from other components or HTML nodes, you can use the `rsx!`
|
|
95
|
+
macro, which generates a `Render` component tree:
|
|
96
|
+
|
|
97
|
+
```rust
|
|
98
|
+
#![feature(proc_macro_hygiene)]
|
|
99
|
+
|
|
100
|
+
use render::{component, rsx, html};
|
|
101
|
+
|
|
102
|
+
#[component]
|
|
103
|
+
fn Heading<'title>(title: &'title str) {
|
|
104
|
+
rsx! { <h1 class={"title"}>{title}</h1> }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let rendered_html = html! {
|
|
108
|
+
<Heading title={"Hello world!"} />
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
assert_eq!(rendered_html, r#"<h1 class="title">Hello world!</h1>"#);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If you pay close attention, you see that the function `Heading` is:
|
|
115
|
+
|
|
116
|
+
* declared with an uppercase. Underneath, it generates a struct with the same name, and
|
|
117
|
+
implements the `Render` trait on it.
|
|
118
|
+
* does not have a return type. This is because everything is written to a writer, for
|
|
119
|
+
performance reasons.
|
|
120
|
+
|
|
121
|
+
#### Full example
|
|
122
|
+
|
|
25
123
|
```rust
|
|
26
124
|
#![feature(proc_macro_hygiene)]
|
|
27
125
|
|
|
@@ -31,15 +129,17 @@ use render::{
|
|
|
31
129
|
// A macro to create components
|
|
32
130
|
component,
|
|
33
131
|
// A macro to compose components in JSX fashion
|
|
132
|
+
rsx,
|
|
133
|
+
// A macro to render components in JSX fashion
|
|
34
134
|
html,
|
|
35
135
|
// A trait for custom components
|
|
36
|
-
|
|
136
|
+
Render,
|
|
37
137
|
};
|
|
38
138
|
|
|
39
139
|
// This can be any layout we want
|
|
40
140
|
#[component]
|
|
41
|
-
fn Page<'a, Children:
|
|
141
|
+
fn Page<'a, Children: Render>(title: &'a str, children: Children) {
|
|
42
|
-
|
|
142
|
+
rsx! {
|
|
43
143
|
<>
|
|
44
144
|
<HTML5Doctype />
|
|
45
145
|
<html>
|
render/Cargo.toml
CHANGED
|
@@ -14,7 +14,6 @@ license = "MIT"
|
|
|
14
14
|
|
|
15
15
|
[dependencies]
|
|
16
16
|
render_macros = { path = "../render_macros", version = "0.2" }
|
|
17
|
-
htmlescape = "0.3"
|
|
18
17
|
|
|
19
18
|
[dev-dependencies]
|
|
20
19
|
pretty_assertions = "0.6"
|
render/src/fragment.rs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//! The fragment component
|
|
2
2
|
|
|
3
|
-
use crate::
|
|
3
|
+
use crate::Render;
|
|
4
|
+
use std::fmt::{Result, Write};
|
|
4
5
|
|
|
5
6
|
/// A top-level root component to combine a same-level components
|
|
6
7
|
/// in a RSX fashion
|
|
@@ -8,7 +9,6 @@ use crate::Renderable;
|
|
|
8
9
|
/// ```rust
|
|
9
10
|
/// # #![feature(proc_macro_hygiene)]
|
|
10
11
|
/// # use pretty_assertions::assert_eq;
|
|
11
|
-
/// # use render::html::HTML5Doctype;
|
|
12
12
|
/// # use render_macros::html;
|
|
13
13
|
/// let result = html! {
|
|
14
14
|
/// <>
|
|
@@ -19,12 +19,12 @@ use crate::Renderable;
|
|
|
19
19
|
/// assert_eq!(result, "<a /><b />");
|
|
20
20
|
/// ```
|
|
21
21
|
#[derive(Debug)]
|
|
22
|
-
pub struct Fragment<T:
|
|
22
|
+
pub struct Fragment<T: Render> {
|
|
23
23
|
pub children: T,
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
impl<T:
|
|
26
|
+
impl<T: Render> Render for Fragment<T> {
|
|
27
|
-
fn
|
|
27
|
+
fn render_into<W: Write>(self, writer: &mut W) -> Result {
|
|
28
|
-
self.children.
|
|
28
|
+
self.children.render_into(writer)
|
|
29
29
|
}
|
|
30
30
|
}
|
render/src/html.rs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//! HTML utilities
|
|
2
2
|
|
|
3
|
-
use crate::
|
|
3
|
+
use crate::Render;
|
|
4
|
+
use std::fmt::{Result, Write};
|
|
4
5
|
|
|
5
6
|
/// HTML 5 doctype declaration
|
|
6
7
|
///
|
|
@@ -23,8 +24,8 @@ use crate::Renderable;
|
|
|
23
24
|
#[derive(Debug)]
|
|
24
25
|
pub struct HTML5Doctype;
|
|
25
26
|
|
|
26
|
-
impl
|
|
27
|
+
impl Render for HTML5Doctype {
|
|
27
|
-
fn
|
|
28
|
+
fn render_into<W: Write>(self, writer: &mut W) -> Result {
|
|
28
|
-
"<!DOCTYPE html>"
|
|
29
|
+
write!(writer, "<!DOCTYPE html>")
|
|
29
30
|
}
|
|
30
31
|
}
|
render/src/html_escaping.rs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
use std::fmt::{Result, Write};
|
|
2
|
+
|
|
3
|
+
/// Simple HTML escaping, so strings can be safely rendered.
|
|
4
|
+
///
|
|
5
|
+
/// ```rust
|
|
6
|
+
/// # use pretty_assertions::assert_eq;
|
|
7
|
+
/// # use render::html_escaping;
|
|
8
|
+
///
|
|
9
|
+
/// let mut buf = String::new();
|
|
10
|
+
/// html_escaping::escape_html(r#"<hello world="attribute" />"#, &mut buf).unwrap();
|
|
11
|
+
/// assert_eq!(buf, "<hello world="attribute" />");
|
|
12
|
+
/// ```
|
|
13
|
+
pub fn escape_html<W: Write>(html: &str, writer: &mut W) -> Result {
|
|
14
|
+
for c in html.chars() {
|
|
15
|
+
match c {
|
|
16
|
+
'>' => write!(writer, ">")?,
|
|
17
|
+
'<' => write!(writer, "<")?,
|
|
18
|
+
'"' => write!(writer, """)?,
|
|
19
|
+
'&' => write!(writer, "&")?,
|
|
20
|
+
'\'' => write!(writer, "'")?,
|
|
21
|
+
c => writer.write_char(c)?,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Ok(())
|
|
26
|
+
}
|
render/src/lib.rs
CHANGED
|
@@ -6,13 +6,25 @@
|
|
|
6
6
|
//!
|
|
7
7
|
//! # How?
|
|
8
8
|
//!
|
|
9
|
-
//! A renderable component is a struct that implements the `
|
|
9
|
+
//! A renderable component is a struct that implements the `Render` trait. There
|
|
10
10
|
//! are multiple macros that provide a better experience implementing Renderable:
|
|
11
11
|
//!
|
|
12
|
-
//! * `html!` for the JSX ergonomics
|
|
13
|
-
//! * `#[component]` for
|
|
12
|
+
//! * `#[component]` for defining components using a function
|
|
13
|
+
//! * `rsx!` for composing elements with JSX ergonomics
|
|
14
|
+
//! * `html!` for composing elements and render them to a string
|
|
14
15
|
//!
|
|
15
|
-
//! # Why
|
|
16
|
+
//! # Why is this different from...
|
|
17
|
+
//!
|
|
18
|
+
//! ## `handlebars`?
|
|
19
|
+
//!
|
|
20
|
+
//! Handlebars is an awesome spec that lets us devs define templates and work
|
|
21
|
+
//! seemlessly between languages and frameworks. Unfortunately, it does not guarantee any of Rust's
|
|
22
|
+
//! type-safety, due to its spec. This forces you to write tests for validating types for your views, like you would in a dynamically typed language. These tests weren't necessary in a type-safe language like Rust — but Handlebars is JSON-oriented, which doesn't comply Rust's type system.
|
|
23
|
+
//!
|
|
24
|
+
//! `render` provides the same level of type-safety Rust provides, with no compromises of
|
|
25
|
+
//! ergonomics or speed.
|
|
26
|
+
//!
|
|
27
|
+
//! ## `typed-html`?
|
|
16
28
|
//!
|
|
17
29
|
//! `typed-html` is a wonderful library. Unfortunately, it focused its power in strictness of the HTML spec itself, and doesn't allow arbitrary compositions of custom elements.
|
|
18
30
|
//!
|
|
@@ -20,6 +32,95 @@
|
|
|
20
32
|
//!
|
|
21
33
|
//! # Usage
|
|
22
34
|
//!
|
|
35
|
+
//! > Note: `render` needs the `nightly` Rust compiler, for now, so it will have hygienic macros.
|
|
36
|
+
//!
|
|
37
|
+
//! This means you will need to add the following feature flag in the root of your `lib.rs`/`main.rs`:
|
|
38
|
+
//!
|
|
39
|
+
//! ```rust
|
|
40
|
+
//! #![feature(proc_macro_hygiene)]
|
|
41
|
+
//! ```
|
|
42
|
+
//!
|
|
43
|
+
//! ## Simple HTML rendering
|
|
44
|
+
//!
|
|
45
|
+
//! In order to render a simple HTML fragment into a `String`, use the `rsx!` macro to generate a
|
|
46
|
+
//! component tree, and call `render` on it:
|
|
47
|
+
//!
|
|
48
|
+
//! ```rust
|
|
49
|
+
//! #![feature(proc_macro_hygiene)]
|
|
50
|
+
//! # use pretty_assertions::assert_eq;
|
|
51
|
+
//!
|
|
52
|
+
//! use render::{rsx, Render};
|
|
53
|
+
//!
|
|
54
|
+
//! let tree = rsx! {
|
|
55
|
+
//! <div>
|
|
56
|
+
//! <h1>{"Hello!"}</h1>
|
|
57
|
+
//! <p>{"Hello world!"}</p>
|
|
58
|
+
//! </div>
|
|
59
|
+
//! };
|
|
60
|
+
//!
|
|
61
|
+
//! assert_eq!(tree.render(), "<div><h1>Hello!</h1><p>Hello world!</p></div>");
|
|
62
|
+
//! ```
|
|
63
|
+
//!
|
|
64
|
+
//! Because this is so common, there's another macro called `html!` that calls `rsx!` to generate
|
|
65
|
+
//! a component tree, and then calls `render` on it. Most of the time, you'll find yourself using
|
|
66
|
+
//! the `rsx!` macro to compose arbitrary components, and only calling `html!` when you need a
|
|
67
|
+
//! String output, when sending a response or generating a Markdown file.
|
|
68
|
+
//!
|
|
69
|
+
//! In Render, attributes and plain strings are escaped using the `render::html_escaping` module. In order to
|
|
70
|
+
//! use un-escaped values so you can dangerously insert raw HTML, use the `raw!` macro around your
|
|
71
|
+
//! string:
|
|
72
|
+
//!
|
|
73
|
+
//! ```rust
|
|
74
|
+
//! #![feature(proc_macro_hygiene)]
|
|
75
|
+
//! # use pretty_assertions::assert_eq;
|
|
76
|
+
//!
|
|
77
|
+
//! use render::{html, raw};
|
|
78
|
+
//!
|
|
79
|
+
//! let tree = html! {
|
|
80
|
+
//! <div>
|
|
81
|
+
//! <p>{"<Hello />"}</p>
|
|
82
|
+
//! <p>{raw!("<Hello />")}</p>
|
|
83
|
+
//! </div>
|
|
84
|
+
//! };
|
|
85
|
+
//!
|
|
86
|
+
//! assert_eq!(tree, "<div><p><Hello /></p><p><Hello /></p></div>");
|
|
87
|
+
//! ```
|
|
88
|
+
//!
|
|
89
|
+
//! ## Custom components
|
|
90
|
+
//!
|
|
91
|
+
//! Render's greatest ability is to provide type-safety along with custom renderable components.
|
|
92
|
+
//! Introducing new components is as easy as defining a function that returns a `Render` value.
|
|
93
|
+
//!
|
|
94
|
+
//! In order to build up components from other components or HTML nodes, you can use the `rsx!`
|
|
95
|
+
//! macro, which generates a `Render` component tree:
|
|
96
|
+
//!
|
|
97
|
+
//! ```rust
|
|
98
|
+
//! #![feature(proc_macro_hygiene)]
|
|
99
|
+
//! # use pretty_assertions::assert_eq;
|
|
100
|
+
//!
|
|
101
|
+
//! use render::{component, rsx, html};
|
|
102
|
+
//!
|
|
103
|
+
//! #[component]
|
|
104
|
+
//! fn Heading<'title>(title: &'title str) {
|
|
105
|
+
//! rsx! { <h1 class={"title"}>{title}</h1> }
|
|
106
|
+
//! }
|
|
107
|
+
//!
|
|
108
|
+
//! let rendered_html = html! {
|
|
109
|
+
//! <Heading title={"Hello world!"} />
|
|
110
|
+
//! };
|
|
111
|
+
//!
|
|
112
|
+
//! assert_eq!(rendered_html, r#"<h1 class="title">Hello world!</h1>"#);
|
|
113
|
+
//! ```
|
|
114
|
+
//!
|
|
115
|
+
//! If you pay close attention, you see that the function `Heading` is:
|
|
116
|
+
//!
|
|
117
|
+
//! * declared with an uppercase. Underneath, it generates a struct with the same name, and
|
|
118
|
+
//! implements the `Render` trait on it.
|
|
119
|
+
//! * does not have a return type. This is because everything is written to a writer, for
|
|
120
|
+
//! performance reasons.
|
|
121
|
+
//!
|
|
122
|
+
//! ### Full example
|
|
123
|
+
//!
|
|
23
124
|
//! ```rust
|
|
24
125
|
//! #![feature(proc_macro_hygiene)]
|
|
25
126
|
//!
|
|
@@ -29,15 +130,17 @@
|
|
|
29
130
|
//! // A macro to create components
|
|
30
131
|
//! component,
|
|
31
132
|
//! // A macro to compose components in JSX fashion
|
|
133
|
+
//! rsx,
|
|
134
|
+
//! // A macro to render components in JSX fashion
|
|
32
135
|
//! html,
|
|
33
136
|
//! // A trait for custom components
|
|
34
|
-
//!
|
|
137
|
+
//! Render,
|
|
35
138
|
//! };
|
|
36
139
|
//!
|
|
37
140
|
//! // This can be any layout we want
|
|
38
141
|
//! #[component]
|
|
39
|
-
//! fn Page<'a, Children:
|
|
142
|
+
//! fn Page<'a, Children: Render>(title: &'a str, children: Children) {
|
|
40
|
-
//!
|
|
143
|
+
//! rsx! {
|
|
41
144
|
//! <>
|
|
42
145
|
//! <HTML5Doctype />
|
|
43
146
|
//! <html>
|
|
@@ -74,14 +177,17 @@
|
|
|
74
177
|
//! # assert_eq!(actual, expected);
|
|
75
178
|
//! ```
|
|
76
179
|
|
|
180
|
+
#![feature(proc_macro_hygiene)]
|
|
181
|
+
|
|
77
182
|
pub mod fragment;
|
|
78
183
|
pub mod html;
|
|
184
|
+
pub mod html_escaping;
|
|
79
|
-
mod
|
|
185
|
+
mod render;
|
|
80
186
|
mod simple_element;
|
|
81
187
|
mod text_element;
|
|
82
188
|
|
|
189
|
+
pub use self::render::Render;
|
|
83
190
|
pub use fragment::Fragment;
|
|
84
191
|
pub use render_macros::{component, html, rsx};
|
|
85
|
-
pub use renderable::Renderable;
|
|
86
192
|
pub use simple_element::SimpleElement;
|
|
87
193
|
pub use text_element::Raw;
|
render/src/render.rs
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
use std::fmt::{Result, Write};
|
|
2
|
+
|
|
3
|
+
/// Render a component
|
|
4
|
+
///
|
|
5
|
+
/// This is the underlying mechanism of the `#[component]` macro
|
|
6
|
+
pub trait Render: Sized {
|
|
7
|
+
/// Render the component to a writer.
|
|
8
|
+
/// Make sure you escape html correctly using the `render::html_escaping` module
|
|
9
|
+
fn render_into<W: Write>(self, writer: &mut W) -> Result;
|
|
10
|
+
|
|
11
|
+
/// Render the component to string
|
|
12
|
+
fn render(self) -> String {
|
|
13
|
+
let mut buf = String::new();
|
|
14
|
+
self.render_into(&mut buf).unwrap();
|
|
15
|
+
buf
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// Does nothing
|
|
20
|
+
impl Render for () {
|
|
21
|
+
fn render_into<W: Write>(self, _writer: &mut W) -> Result {
|
|
22
|
+
Ok(())
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Renders `A`, then `B`
|
|
27
|
+
impl<A: Render, B: Render> Render for (A, B) {
|
|
28
|
+
fn render_into<W: Write>(self, writer: &mut W) -> Result {
|
|
29
|
+
self.0.render_into(writer)?;
|
|
30
|
+
self.1.render_into(writer)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Renders `A`, then `B`, then `C`
|
|
35
|
+
impl<A: Render, B: Render, C: Render> Render for (A, B, C) {
|
|
36
|
+
fn render_into<W: Write>(self, writer: &mut W) -> Result {
|
|
37
|
+
self.0.render_into(writer)?;
|
|
38
|
+
self.1.render_into(writer)?;
|
|
39
|
+
self.2.render_into(writer)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Renders `T` or nothing
|
|
44
|
+
impl<T: Render> Render for Option<T> {
|
|
45
|
+
fn render_into<W: Write>(self, writer: &mut W) -> Result {
|
|
46
|
+
match self {
|
|
47
|
+
None => Ok(()),
|
|
48
|
+
Some(x) => x.render_into(writer),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Renders `O` or `E`
|
|
54
|
+
impl<O: Render, E: Render> Render for std::result::Result<O, E> {
|
|
55
|
+
fn render_into<W: Write>(self, writer: &mut W) -> Result {
|
|
56
|
+
match self {
|
|
57
|
+
Ok(o) => o.render_into(writer),
|
|
58
|
+
Err(e) => e.render_into(writer),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
render/src/renderable.rs
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
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
|
-
///
|
|
8
|
-
/// A simple implementation:
|
|
9
|
-
///
|
|
10
|
-
/// ```rust
|
|
11
|
-
/// # #![feature(proc_macro_hygiene)]
|
|
12
|
-
/// # use render_macros::html;
|
|
13
|
-
/// # use render::Renderable;
|
|
14
|
-
/// # use pretty_assertions::assert_eq;
|
|
15
|
-
/// #[derive(Debug)]
|
|
16
|
-
/// struct Header<'t> { title: &'t str }
|
|
17
|
-
///
|
|
18
|
-
/// impl<'t> Renderable for Header<'t> {
|
|
19
|
-
/// fn render(self) -> String {
|
|
20
|
-
/// html! {
|
|
21
|
-
/// <h1>{self.title}</h1>
|
|
22
|
-
/// }
|
|
23
|
-
/// }
|
|
24
|
-
/// }
|
|
25
|
-
///
|
|
26
|
-
/// // Then you can use it with
|
|
27
|
-
///
|
|
28
|
-
/// let rendered_html = html! {
|
|
29
|
-
/// <Header title={"Hello world!"} />
|
|
30
|
-
/// };
|
|
31
|
-
///
|
|
32
|
-
/// # assert_eq!(rendered_html, "<h1>Hello world!</h1>");
|
|
33
|
-
/// ```
|
|
34
|
-
///
|
|
35
|
-
/// ## Children
|
|
36
|
-
///
|
|
37
|
-
/// `children` is a special field that will be populated with other `Renderable` if any children was provided, by both the [`html!`] and the [`rsx!`] macros.
|
|
38
|
-
///
|
|
39
|
-
/// [`html!`]: ../render_macros/macro.html.html
|
|
40
|
-
/// [`rsx!`]: ../render_macros/macro.rsx.html
|
|
41
|
-
fn render(self) -> String;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/// Renders an empty string
|
|
45
|
-
impl Renderable for () {
|
|
46
|
-
fn render(self) -> String {
|
|
47
|
-
"".to_string()
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/// Renders `A` and then `B`
|
|
52
|
-
impl<A: Renderable, B: Renderable> Renderable for (A, B) {
|
|
53
|
-
fn render(self) -> String {
|
|
54
|
-
format!("{}{}", self.0.render(), self.1.render())
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/// Renders `A`, `B`, and then `C`
|
|
59
|
-
impl<A: Renderable, B: Renderable, C: Renderable> Renderable for (A, B, C) {
|
|
60
|
-
fn render(self) -> String {
|
|
61
|
-
((self.0, self.1), self.2).render()
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/// Renders `A`, `B`, `C` and then `D`
|
|
66
|
-
impl<A: Renderable, B: Renderable, C: Renderable, D: Renderable> Renderable for (A, B, C, D) {
|
|
67
|
-
fn render(self) -> String {
|
|
68
|
-
((self.0, self.1), (self.2, self.3)).render()
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/// Renders the `T` or an empty string
|
|
73
|
-
impl<T: Renderable> Renderable for Option<T> {
|
|
74
|
-
fn render(self) -> String {
|
|
75
|
-
match self {
|
|
76
|
-
None => "".to_string(),
|
|
77
|
-
Some(x) => x.render(),
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/// Renders `O` or `E`
|
|
83
|
-
impl<O: Renderable, E: Renderable> Renderable for Result<O, E> {
|
|
84
|
-
fn render(self) -> String {
|
|
85
|
-
match self {
|
|
86
|
-
Err(e) => e.render(),
|
|
87
|
-
Ok(o) => o.render(),
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
impl Renderable for usize {
|
|
93
|
-
fn render(self) -> String {
|
|
94
|
-
self.to_string()
|
|
95
|
-
}
|
|
96
|
-
}
|
render/src/simple_element.rs
CHANGED
|
@@ -1,41 +1,48 @@
|
|
|
1
|
-
use crate::
|
|
1
|
+
use crate::Render;
|
|
2
|
+
use crate::html_escaping::escape_html;
|
|
2
3
|
use std::collections::HashMap;
|
|
4
|
+
use std::fmt::{Result, Write};
|
|
5
|
+
|
|
6
|
+
type Attributes<'a> = Option<HashMap<&'a str, &'a str>>;
|
|
3
7
|
|
|
4
8
|
/// Simple HTML element tag
|
|
5
9
|
#[derive(Debug)]
|
|
6
|
-
pub struct SimpleElement<'a, T:
|
|
10
|
+
pub struct SimpleElement<'a, T: Render> {
|
|
7
11
|
/// the HTML tag name, like `html`, `head`, `body`, `link`...
|
|
8
12
|
pub tag_name: &'a str,
|
|
9
|
-
pub attributes:
|
|
13
|
+
pub attributes: Attributes<'a>,
|
|
10
14
|
pub contents: Option<T>,
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
fn
|
|
17
|
+
fn write_attributes<'a, W: Write>(maybe_attributes: Attributes<'a>, writer: &mut W) -> Result {
|
|
14
|
-
|
|
18
|
+
match maybe_attributes {
|
|
15
|
-
|
|
19
|
+
None => Ok(()),
|
|
16
|
-
match opt {
|
|
17
|
-
None => "".to_string(),
|
|
18
|
-
Some(
|
|
20
|
+
Some(mut attributes) => {
|
|
19
|
-
|
|
21
|
+
for (key, value) in attributes.drain() {
|
|
20
|
-
.iter()
|
|
21
|
-
|
|
22
|
+
write!(writer, " {}=\"", key)?;
|
|
22
|
-
|
|
23
|
+
escape_html(value, writer)?;
|
|
24
|
+
write!(writer, "\"")?;
|
|
23
|
-
|
|
25
|
+
}
|
|
26
|
+
Ok(())
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
impl<
|
|
31
|
+
impl<T: Render> Render for SimpleElement<'_, T> {
|
|
29
|
-
fn render(self) -> String {
|
|
30
|
-
|
|
32
|
+
fn render_into<W: Write>(self, writer: &mut W) -> Result {
|
|
31
33
|
match self.contents {
|
|
34
|
+
None => {
|
|
32
|
-
|
|
35
|
+
write!(writer, "<{}", self.tag_name)?;
|
|
36
|
+
write_attributes(self.attributes, writer)?;
|
|
37
|
+
write!(writer, " />")
|
|
38
|
+
}
|
|
33
|
-
Some(renderable) =>
|
|
39
|
+
Some(renderable) => {
|
|
34
|
-
"<{
|
|
40
|
+
write!(writer, "<{}", self.tag_name)?;
|
|
35
|
-
|
|
41
|
+
write_attributes(self.attributes, writer)?;
|
|
36
|
-
|
|
42
|
+
write!(writer, ">")?;
|
|
37
|
-
|
|
43
|
+
renderable.render_into(writer)?;
|
|
44
|
+
write!(writer, "</{}>", self.tag_name)
|
|
38
|
-
|
|
45
|
+
}
|
|
39
46
|
}
|
|
40
47
|
}
|
|
41
48
|
}
|
render/src/text_element.rs
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
use crate::
|
|
1
|
+
use crate::Render;
|
|
2
|
+
use std::fmt::{Result, Write};
|
|
2
|
-
use
|
|
3
|
+
use crate::html_escaping::escape_html;
|
|
3
4
|
|
|
4
|
-
/// Renders an escaped-html string
|
|
5
|
-
impl
|
|
5
|
+
impl Render for String {
|
|
6
|
-
fn
|
|
6
|
+
fn render_into<W: Write>(self, writer: &mut W) -> Result {
|
|
7
|
-
|
|
7
|
+
escape_html(&self, writer)
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
/// Renders an escaped-html string
|
|
12
|
-
impl
|
|
11
|
+
impl Render for &str {
|
|
13
|
-
fn
|
|
12
|
+
fn render_into<W: Write>(self, writer: &mut W) -> Result {
|
|
14
|
-
|
|
13
|
+
escape_html(self, writer)
|
|
15
14
|
}
|
|
16
15
|
}
|
|
17
16
|
|
|
@@ -26,9 +25,9 @@ impl<'s> From<&'s str> for Raw<'s> {
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
/// A raw (unencoded) html string
|
|
29
|
-
impl<'s>
|
|
28
|
+
impl<'s> Render for Raw<'s> {
|
|
30
|
-
fn
|
|
29
|
+
fn render_into<W: Write>(self, writer: &mut W) -> Result {
|
|
31
|
-
self.0
|
|
30
|
+
write!(writer, "{}", self.0)
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
|
|
@@ -38,12 +37,14 @@ mod tests {
|
|
|
38
37
|
|
|
39
38
|
#[test]
|
|
40
39
|
fn decodes_html() {
|
|
40
|
+
use pretty_assertions::assert_eq;
|
|
41
41
|
let rendered = "<Hello />".render();
|
|
42
42
|
assert_eq!(rendered, "<Hello />");
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
#[test]
|
|
46
46
|
fn allows_raw_text() {
|
|
47
|
+
use pretty_assertions::assert_eq;
|
|
47
48
|
let rendered = Raw::from("<Hello />").render();
|
|
48
49
|
assert_eq!(rendered, "<Hello />");
|
|
49
50
|
}
|
render_macros/src/children.rs
CHANGED
|
@@ -47,9 +47,8 @@ impl Parse for Children {
|
|
|
47
47
|
let mut nodes = vec![];
|
|
48
48
|
|
|
49
49
|
while !input.peek(syn::Token![<]) || !input.peek2(syn::Token![/]) {
|
|
50
|
-
|
|
50
|
+
let child = input.parse::<Child>()?;
|
|
51
|
-
|
|
51
|
+
nodes.push(child);
|
|
52
|
-
}
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
Ok(Self::new(nodes))
|
render_macros/src/element_attributes.rs
CHANGED
|
@@ -56,17 +56,16 @@ impl Parse for ElementAttributes {
|
|
|
56
56
|
fn parse(input: ParseStream) -> Result<Self> {
|
|
57
57
|
let mut attributes: HashSet<ElementAttribute> = HashSet::new();
|
|
58
58
|
while input.peek(syn::Ident) {
|
|
59
|
-
|
|
59
|
+
let attribute = input.parse::<ElementAttribute>()?;
|
|
60
|
-
|
|
60
|
+
let ident = attribute.ident();
|
|
61
|
-
|
|
61
|
+
if attributes.contains(&attribute) {
|
|
62
|
-
|
|
62
|
+
let error_message = format!(
|
|
63
|
-
|
|
63
|
+
"There is a previous definition of the {} attribute",
|
|
64
|
-
|
|
64
|
+
quote!(#ident)
|
|
65
|
-
|
|
65
|
+
);
|
|
66
|
-
|
|
66
|
+
ident.span().unwrap().warning(error_message).emit();
|
|
67
|
-
}
|
|
68
|
-
attributes.insert(attribute);
|
|
69
67
|
}
|
|
68
|
+
attributes.insert(attribute);
|
|
70
69
|
}
|
|
71
70
|
Ok(ElementAttributes::new(attributes))
|
|
72
71
|
}
|
render_macros/src/function_component.rs
CHANGED
|
@@ -2,7 +2,7 @@ use proc_macro::TokenStream;
|
|
|
2
2
|
use quote::quote;
|
|
3
3
|
use syn::spanned::Spanned;
|
|
4
4
|
|
|
5
|
-
pub fn
|
|
5
|
+
pub fn create_function_component(f: syn::ItemFn) -> TokenStream {
|
|
6
6
|
let struct_name = f.sig.ident;
|
|
7
7
|
let (impl_generics, ty_generics, where_clause) = f.sig.generics.split_for_impl();
|
|
8
8
|
let inputs = f.sig.inputs;
|
|
@@ -41,10 +41,13 @@ pub fn to_component(f: syn::ItemFn) -> TokenStream {
|
|
|
41
41
|
#[derive(Debug)]
|
|
42
42
|
#vis struct #struct_name#impl_generics #inputs_block
|
|
43
43
|
|
|
44
|
-
impl#impl_generics ::render::
|
|
44
|
+
impl#impl_generics ::render::Render for #struct_name #ty_generics #where_clause {
|
|
45
|
+
fn render_into<W: std::fmt::Write>(self, w: &mut W) -> std::fmt::Result {
|
|
45
|
-
|
|
46
|
+
let result = {
|
|
46
|
-
|
|
47
|
+
#inputs_reading
|
|
47
|
-
|
|
48
|
+
#block
|
|
49
|
+
};
|
|
50
|
+
::render::Render::render_into(result, w)
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
})
|
render_macros/src/lib.rs
CHANGED
|
@@ -34,21 +34,21 @@ use syn::parse_macro_input;
|
|
|
34
34
|
/// ```rust
|
|
35
35
|
/// # #![feature(proc_macro_hygiene)]
|
|
36
36
|
/// # use pretty_assertions::assert_eq;
|
|
37
|
-
/// # use render_macros::html;
|
|
37
|
+
/// # use render_macros::{html, rsx};
|
|
38
|
-
/// use render::
|
|
38
|
+
/// use render::Render;
|
|
39
39
|
///
|
|
40
40
|
/// #[derive(Debug)]
|
|
41
41
|
/// struct Heading<'t> { title: &'t str }
|
|
42
42
|
///
|
|
43
|
-
/// impl<'t>
|
|
43
|
+
/// impl<'t> Render for Heading<'t> {
|
|
44
|
-
/// fn
|
|
44
|
+
/// fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
|
|
45
|
-
///
|
|
45
|
+
/// Render::render_into(rsx! { <h1>{self.title}</h1> }, writer)
|
|
46
46
|
/// }
|
|
47
47
|
/// }
|
|
48
48
|
///
|
|
49
|
-
/// let rendered = html! { <Heading title={"Hello
|
|
49
|
+
/// let rendered = html! { <Heading title={"Hello world!"} /> };
|
|
50
50
|
///
|
|
51
|
-
/// assert_eq!(rendered, r#"<h1>Hello
|
|
51
|
+
/// assert_eq!(rendered, r#"<h1>Hello world!</h1>"#);
|
|
52
52
|
/// ```
|
|
53
53
|
///
|
|
54
54
|
/// ### Values are always surrounded by curly braces
|
|
@@ -71,10 +71,10 @@ use syn::parse_macro_input;
|
|
|
71
71
|
/// # use render_macros::html;
|
|
72
72
|
/// # use pretty_assertions::assert_eq;
|
|
73
73
|
/// let rendered = html! {
|
|
74
|
-
/// <div data-testid={"
|
|
74
|
+
/// <div data-testid={"sometestid"} />
|
|
75
75
|
/// };
|
|
76
76
|
///
|
|
77
|
-
/// assert_eq!(rendered, r#"<div data-testid="
|
|
77
|
+
/// assert_eq!(rendered, r#"<div data-testid="sometestid" />"#);
|
|
78
78
|
/// ```
|
|
79
79
|
///
|
|
80
80
|
/// ### Custom components can't accept dashed-separated values
|
|
@@ -96,13 +96,13 @@ use syn::parse_macro_input;
|
|
|
96
96
|
/// # #![feature(proc_macro_hygiene)]
|
|
97
97
|
/// # use render_macros::html;
|
|
98
98
|
/// # use pretty_assertions::assert_eq;
|
|
99
|
-
/// let class = "
|
|
99
|
+
/// let class = "someclass";
|
|
100
100
|
///
|
|
101
101
|
/// let rendered = html! {
|
|
102
102
|
/// <div class />
|
|
103
103
|
/// };
|
|
104
104
|
///
|
|
105
|
-
/// assert_eq!(rendered, r#"<div class="
|
|
105
|
+
/// assert_eq!(rendered, r#"<div class="someclass" />"#);
|
|
106
106
|
/// ```
|
|
107
107
|
///
|
|
108
108
|
/// ### Punning is not supported for dashed-delimited attributes
|
|
@@ -120,7 +120,7 @@ use syn::parse_macro_input;
|
|
|
120
120
|
#[proc_macro]
|
|
121
121
|
pub fn html(input: TokenStream) -> TokenStream {
|
|
122
122
|
let el = proc_macro2::TokenStream::from(rsx(input));
|
|
123
|
-
let result = quote! { ::render::
|
|
123
|
+
let result = quote! { ::render::Render::render(#el) };
|
|
124
124
|
TokenStream::from(result)
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -132,7 +132,7 @@ pub fn rsx(input: TokenStream) -> TokenStream {
|
|
|
132
132
|
TokenStream::from(result)
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
/// A syntactic sugar for implementing [`
|
|
135
|
+
/// A syntactic sugar for implementing [`Render`](../render/trait.Render.html) conveniently
|
|
136
136
|
/// using functions.
|
|
137
137
|
///
|
|
138
138
|
/// This attribute should be above a stand-alone function definition that returns a
|
|
@@ -140,34 +140,34 @@ pub fn rsx(input: TokenStream) -> TokenStream {
|
|
|
140
140
|
///
|
|
141
141
|
/// ```rust
|
|
142
142
|
/// # #![feature(proc_macro_hygiene)]
|
|
143
|
-
/// # use render_macros::{component,
|
|
143
|
+
/// # use render_macros::{component, rsx};
|
|
144
144
|
/// #
|
|
145
145
|
/// #[component]
|
|
146
|
-
/// fn UserFn(name: String)
|
|
146
|
+
/// fn UserFn(name: String) {
|
|
147
|
-
///
|
|
147
|
+
/// rsx! { <div>{format!("Hello, {}", name)}</div> }
|
|
148
148
|
/// }
|
|
149
149
|
/// ```
|
|
150
150
|
///
|
|
151
|
-
/// Practically, this is exactly the same as using the [
|
|
151
|
+
/// Practically, this is exactly the same as using the [Render](../render/trait.Render.html) trait:
|
|
152
152
|
///
|
|
153
153
|
/// ```rust
|
|
154
154
|
/// # #![feature(proc_macro_hygiene)]
|
|
155
|
-
/// # use render_macros::{component, html};
|
|
155
|
+
/// # use render_macros::{component, rsx, html};
|
|
156
|
-
/// # use render::
|
|
156
|
+
/// # use render::Render;
|
|
157
157
|
/// # use pretty_assertions::assert_eq;
|
|
158
158
|
/// #
|
|
159
159
|
/// #[derive(Debug)]
|
|
160
160
|
/// struct User { name: String }
|
|
161
161
|
///
|
|
162
|
-
/// impl render::
|
|
162
|
+
/// impl render::Render for User {
|
|
163
|
-
/// fn
|
|
163
|
+
/// fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
|
|
164
|
-
///
|
|
164
|
+
/// Render::render_into(rsx! { <div>{format!("Hello, {}", self.name)}</div> }, writer)
|
|
165
165
|
/// }
|
|
166
166
|
/// }
|
|
167
167
|
///
|
|
168
168
|
/// # #[component]
|
|
169
|
-
/// # fn UserFn(name: String)
|
|
169
|
+
/// # fn UserFn(name: String) {
|
|
170
|
-
/// #
|
|
170
|
+
/// # rsx! { <div>{format!("Hello, {}", name)}</div> }
|
|
171
171
|
/// # }
|
|
172
172
|
/// #
|
|
173
173
|
/// # let from_fn = html! {
|
|
@@ -183,5 +183,5 @@ pub fn rsx(input: TokenStream) -> TokenStream {
|
|
|
183
183
|
#[proc_macro_attribute]
|
|
184
184
|
pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
185
185
|
let f = parse_macro_input!(item as syn::ItemFn);
|
|
186
|
-
function_component::
|
|
186
|
+
function_component::create_function_component(f)
|
|
187
187
|
}
|
render_tests/src/lib.rs
CHANGED
|
@@ -1,81 +1,17 @@
|
|
|
1
1
|
#![feature(proc_macro_hygiene)]
|
|
2
2
|
|
|
3
|
-
use render::html::HTML5Doctype;
|
|
4
|
-
use render::{component, html, rsx, Renderable};
|
|
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
|
-
<>
|
|
33
|
-
<HTML5Doctype />
|
|
34
|
-
<Hello world yes={1 + 1}>
|
|
35
|
-
<div data-testid={"hey"} hello={"hello"}>{format!("HEY!")}</div>
|
|
36
|
-
{other_value}
|
|
37
|
-
</Hello>
|
|
38
|
-
</>
|
|
39
|
-
};
|
|
40
|
-
value
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
#[component]
|
|
44
|
-
pub fn Layout<'a, Children: Renderable>(title: &'a str, children: Children) -> String {
|
|
45
|
-
html! {
|
|
46
|
-
<html>
|
|
47
|
-
<head><title>{title}</title></head>
|
|
48
|
-
<body>
|
|
49
|
-
{children}
|
|
50
|
-
</body>
|
|
51
|
-
</html>
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
#[component]
|
|
56
|
-
pub fn SomeComponent(name: String) -> String {
|
|
57
|
-
html! {
|
|
58
|
-
<div>{format!("Hello, {}", name)}</div>
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
#[test]
|
|
63
|
-
pub fn verify_works() {
|
|
64
|
-
println!("{}", it_works());
|
|
65
|
-
}
|
|
66
|
-
|
|
67
3
|
#[test]
|
|
68
4
|
pub fn works_with_dashes() {
|
|
69
5
|
use pretty_assertions::assert_eq;
|
|
70
6
|
|
|
71
|
-
let value = html! { <div data-id={"
|
|
7
|
+
let value = render::html! { <div data-id={"myid"} /> };
|
|
72
|
-
assert_eq!(value, r#"<div data-id="
|
|
8
|
+
assert_eq!(value, r#"<div data-id="myid" />"#);
|
|
73
9
|
}
|
|
74
10
|
|
|
75
11
|
#[test]
|
|
76
12
|
pub fn works_with_raw() {
|
|
77
13
|
use pretty_assertions::assert_eq;
|
|
78
|
-
use render::raw;
|
|
14
|
+
use render::{html, raw};
|
|
79
15
|
|
|
80
16
|
let actual = html! {
|
|
81
17
|
<div>{raw!("<Hello />")}</div>
|
|
@@ -83,3 +19,52 @@ pub fn works_with_raw() {
|
|
|
83
19
|
|
|
84
20
|
assert_eq!(actual, "<div><Hello /></div>");
|
|
85
21
|
}
|
|
22
|
+
|
|
23
|
+
mod kaki {
|
|
24
|
+
// A simple HTML 5 doctype declaration
|
|
25
|
+
use render::html::HTML5Doctype;
|
|
26
|
+
use render::{
|
|
27
|
+
// A macro to create components
|
|
28
|
+
component,
|
|
29
|
+
// A macro to compose components in JSX fashion
|
|
30
|
+
rsx,
|
|
31
|
+
// A trait for custom components
|
|
32
|
+
Render,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// This can be any layout we want
|
|
36
|
+
#[component]
|
|
37
|
+
fn Page<'a, Children: Render>(title: &'a str, children: Children) {
|
|
38
|
+
rsx! {
|
|
39
|
+
<>
|
|
40
|
+
<HTML5Doctype />
|
|
41
|
+
<html>
|
|
42
|
+
<head><title>{title}</title></head>
|
|
43
|
+
<body>
|
|
44
|
+
{children}
|
|
45
|
+
</body>
|
|
46
|
+
</html>
|
|
47
|
+
</>
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[test]
|
|
52
|
+
fn test() {
|
|
53
|
+
use pretty_assertions::assert_eq;
|
|
54
|
+
let actual = render::html! {
|
|
55
|
+
<Page title={"Home"}>
|
|
56
|
+
{format!("Welcome, {}", "Gal")}
|
|
57
|
+
</Page>
|
|
58
|
+
};
|
|
59
|
+
let expected = concat!(
|
|
60
|
+
"<!DOCTYPE html>",
|
|
61
|
+
"<html>",
|
|
62
|
+
"<head><title>Home</title></head>",
|
|
63
|
+
"<body>",
|
|
64
|
+
"Welcome, Gal",
|
|
65
|
+
"</body>",
|
|
66
|
+
"</html>"
|
|
67
|
+
);
|
|
68
|
+
assert_eq!(actual, expected);
|
|
69
|
+
}
|
|
70
|
+
}
|