~repos /tide-jsx

#rust#proc-macro#jsx

git clone https://pyrossh.dev/repos/tide-jsx.git

Tide + JSX


b03ce49b Gal Schlezinger

6 years ago
Use `Write` instead of owned strings (#8)
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 `Renderable` trait. There
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 the syntactic-sugar of function components
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 this is different from `typed-html`?
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>&lt;Hello /&gt;</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
- Renderable,
136
+ Render,
37
137
  };
38
138
 
39
139
  // This can be any layout we want
40
140
  #[component]
41
- fn Page<'a, Children: Renderable>(title: &'a str, children: Children) -> String {
141
+ fn Page<'a, Children: Render>(title: &'a str, children: Children) {
42
- html! {
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::Renderable;
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: Renderable> {
22
+ pub struct Fragment<T: Render> {
23
23
  pub children: T,
24
24
  }
25
25
 
26
- impl<T: Renderable> Renderable for Fragment<T> {
26
+ impl<T: Render> Render for Fragment<T> {
27
- fn render(self) -> String {
27
+ fn render_into<W: Write>(self, writer: &mut W) -> Result {
28
- self.children.render()
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::Renderable;
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 Renderable for HTML5Doctype {
27
+ impl Render for HTML5Doctype {
27
- fn render(self) -> String {
28
+ fn render_into<W: Write>(self, writer: &mut W) -> Result {
28
- "<!DOCTYPE html>".to_string()
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, "&lt;hello world=&quot;attribute&quot; /&gt;");
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, "&gt;")?,
17
+ '<' => write!(writer, "&lt;")?,
18
+ '"' => write!(writer, "&quot;")?,
19
+ '&' => write!(writer, "&amp;")?,
20
+ '\'' => write!(writer, "&apos;")?,
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 `Renderable` trait. There
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 the syntactic-sugar of function components
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 this is different from `typed-html`?
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>&lt;Hello /&gt;</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
- //! Renderable,
137
+ //! Render,
35
138
  //! };
36
139
  //!
37
140
  //! // This can be any layout we want
38
141
  //! #[component]
39
- //! fn Page<'a, Children: Renderable>(title: &'a str, children: Children) -> String {
142
+ //! fn Page<'a, Children: Render>(title: &'a str, children: Children) {
40
- //! html! {
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 renderable;
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::Renderable;
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: Renderable> {
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: Option<HashMap<&'a str, &'a str>>,
13
+ pub attributes: Attributes<'a>,
10
14
  pub contents: Option<T>,
11
15
  }
12
16
 
13
- fn attributes_to_string<Key: std::fmt::Display + std::hash::Hash, Value: std::fmt::Debug>(
17
+ fn write_attributes<'a, W: Write>(maybe_attributes: Attributes<'a>, writer: &mut W) -> Result {
14
- opt: &Option<HashMap<Key, Value>>,
18
+ match maybe_attributes {
15
- ) -> String {
19
+ None => Ok(()),
16
- match opt {
17
- None => "".to_string(),
18
- Some(map) => {
20
+ Some(mut attributes) => {
19
- let s: String = map
21
+ for (key, value) in attributes.drain() {
20
- .iter()
21
- .map(|(key, value)| format!(" {}={:?}", key, value))
22
+ write!(writer, " {}=\"", key)?;
22
- .collect();
23
+ escape_html(value, writer)?;
24
+ write!(writer, "\"")?;
23
- s
25
+ }
26
+ Ok(())
24
27
  }
25
28
  }
26
29
  }
27
30
 
28
- impl<'a, T: Renderable> Renderable for SimpleElement<'a, T> {
31
+ impl<T: Render> Render for SimpleElement<'_, T> {
29
- fn render(self) -> String {
30
- let attrs = attributes_to_string(&self.attributes);
32
+ fn render_into<W: Write>(self, writer: &mut W) -> Result {
31
33
  match self.contents {
34
+ None => {
32
- None => format!("<{}{} />", self.tag_name, attrs),
35
+ write!(writer, "<{}", self.tag_name)?;
36
+ write_attributes(self.attributes, writer)?;
37
+ write!(writer, " />")
38
+ }
33
- Some(renderable) => format!(
39
+ Some(renderable) => {
34
- "<{tag_name}{attrs}>{contents}</{tag_name}>",
40
+ write!(writer, "<{}", self.tag_name)?;
35
- tag_name = self.tag_name,
41
+ write_attributes(self.attributes, writer)?;
36
- attrs = attrs,
42
+ write!(writer, ">")?;
37
- contents = renderable.render()
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::Renderable;
1
+ use crate::Render;
2
+ use std::fmt::{Result, Write};
2
- use htmlescape::encode_minimal;
3
+ use crate::html_escaping::escape_html;
3
4
 
4
- /// Renders an escaped-html string
5
- impl Renderable for String {
5
+ impl Render for String {
6
- fn render(self) -> String {
6
+ fn render_into<W: Write>(self, writer: &mut W) -> Result {
7
- encode_minimal(&self)
7
+ escape_html(&self, writer)
8
8
  }
9
9
  }
10
10
 
11
- /// Renders an escaped-html string
12
- impl Renderable for &str {
11
+ impl Render for &str {
13
- fn render(self) -> String {
12
+ fn render_into<W: Write>(self, writer: &mut W) -> Result {
14
- encode_minimal(self)
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> Renderable for Raw<'s> {
28
+ impl<'s> Render for Raw<'s> {
30
- fn render(self) -> String {
29
+ fn render_into<W: Write>(self, writer: &mut W) -> Result {
31
- self.0.to_string()
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, "&lt;Hello /&gt;");
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
- if let Ok(child) = input.parse::<Child>() {
50
+ let child = input.parse::<Child>()?;
51
- nodes.push(child);
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
- if let Ok(attribute) = input.parse::<ElementAttribute>() {
59
+ let attribute = input.parse::<ElementAttribute>()?;
60
- let ident = attribute.ident();
60
+ let ident = attribute.ident();
61
- if attributes.contains(&attribute) {
61
+ if attributes.contains(&attribute) {
62
- let error_message = format!(
62
+ let error_message = format!(
63
- "There is a previous definition of the {} attribute",
63
+ "There is a previous definition of the {} attribute",
64
- quote!(#ident)
64
+ quote!(#ident)
65
- );
65
+ );
66
- ident.span().unwrap().warning(error_message).emit();
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 to_component(f: syn::ItemFn) -> TokenStream {
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::Renderable for #struct_name #ty_generics #where_clause {
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
- fn render(self) -> String {
46
+ let result = {
46
- #inputs_reading
47
+ #inputs_reading
47
- #block
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::Renderable;
38
+ /// use render::Render;
39
39
  ///
40
40
  /// #[derive(Debug)]
41
41
  /// struct Heading<'t> { title: &'t str }
42
42
  ///
43
- /// impl<'t> Renderable for Heading<'t> {
43
+ /// impl<'t> Render for Heading<'t> {
44
- /// fn render(self) -> String {
44
+ /// fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
45
- /// html! { <h1>{self.title}</h1> }
45
+ /// Render::render_into(rsx! { <h1>{self.title}</h1> }, writer)
46
46
  /// }
47
47
  /// }
48
48
  ///
49
- /// let rendered = html! { <Heading title={"Hello world!"} /> };
49
+ /// let rendered = html! { <Heading title={"Hello world!"} /> };
50
50
  ///
51
- /// assert_eq!(rendered, r#"<h1>Hello world!</h1>"#);
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={"some test id"} />
74
+ /// <div data-testid={"sometestid"} />
75
75
  /// };
76
76
  ///
77
- /// assert_eq!(rendered, r#"<div data-testid="some test id" />"#);
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 = "some_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="some_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::Renderable::render(#el) };
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 [`Renderable`](../render/trait.Renderable.html) conveniently
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, html};
143
+ /// # use render_macros::{component, rsx};
144
144
  /// #
145
145
  /// #[component]
146
- /// fn UserFn(name: String) -> String {
146
+ /// fn UserFn(name: String) {
147
- /// html! { <div>{format!("Hello, {}", name)}</div> }
147
+ /// rsx! { <div>{format!("Hello, {}", name)}</div> }
148
148
  /// }
149
149
  /// ```
150
150
  ///
151
- /// Practically, this is exactly the same as using the [Renderable](../render/trait.Renderable.html) trait:
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::Renderable;
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::Renderable for User {
162
+ /// impl render::Render for User {
163
- /// fn render(self) -> String {
163
+ /// fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
164
- /// html! { <div>{format!("Hello, {}", self.name)}</div> }
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) -> String {
169
+ /// # fn UserFn(name: String) {
170
- /// # html! { <div>{format!("Hello, {}", name)}</div> }
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::to_component(f)
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={"some id"} /> };
7
+ let value = render::html! { <div data-id={"myid"} /> };
72
- assert_eq!(value, r#"<div data-id="some 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
+ }