papercraft 1.4 → 2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -4,14 +4,14 @@
4
4
  Papercraft
5
5
  </h1>
6
6
 
7
- <h4 align="center">Composable templating for Ruby</h4>
7
+ <h4 align="center">Functional HTML templating for Ruby</h4>
8
8
 
9
9
  <p align="center">
10
10
  <a href="http://rubygems.org/gems/papercraft">
11
11
  <img src="https://badge.fury.io/rb/papercraft.svg" alt="Ruby gem">
12
12
  </a>
13
- <a href="https://github.com/digital-fabric/papercraft/actions?query=workflow%3ATests">
14
- <img src="https://github.com/digital-fabric/papercraft/workflows/Tests/badge.svg" alt="Tests">
13
+ <a href="https://github.com/digital-fabric/papercraft/actions/workflows/test.yml">
14
+ <img src="https://github.com/digital-fabric/papercraft/actions/workflows/test.yml/badge.svg" alt="Tests">
15
15
  </a>
16
16
  <a href="https://github.com/digital-fabric/papercraft/blob/master/LICENSE">
17
17
  <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
@@ -24,160 +24,112 @@
24
24
 
25
25
  ## What is Papercraft?
26
26
 
27
- Papercraft is a templating engine for dynamically producing
28
- [HTML](#html-templates), [XML](#xml-templates) or [JSON](#json-templates).
29
- Papercraft templates are expressed in plain Ruby, leading to easier debugging,
30
- better protection against HTML/XML injection attacks, and better code reuse.
27
+ ```ruby
28
+ require 'papercraft'
31
29
 
32
- Papercraft templates can be composed in a variety of ways, facilitating the
33
- usage of layout templates, and enabling a component-oriented approach to
34
- building complex web interfaces.
30
+ page = ->(**props) {
31
+ html {
32
+ head { title 'My Title' }
33
+ body { render_children **props }
34
+ }
35
+ }
36
+ page.render {
37
+ p 'foo'
38
+ }
39
+ #=> "<html><head><title>Title</title></head><body><p>foo</p></body></html>"
40
+ ```
35
41
 
36
- In Papercraft, dynamic data is passed explicitly to the template as block
37
- arguments, making the data flow easy to follow and understand. Papercraft also
38
- lets developers create derivative templates using full or partial parameter
39
- application.
42
+ Papercraft is a templating engine for dynamically producing HTML in Ruby apps. Papercraft
43
+ templates are expressed as Ruby procs, leading to easier debugging, better
44
+ protection against HTML injection attacks, and better code reuse.
40
45
 
41
- Papercraft includes built-in support for rendering Markdown (using
42
- [Kramdown](https://github.com/gettalong/kramdown/)), as well as support for
43
- creating template extensions in order to allow the creation of component
44
- libraries.
46
+ Papercraft templates can be composed in a variety of ways, facilitating the usage of
47
+ layout templates, and enabling a component-oriented approach to building web
48
+ interfaces of arbitrary complexity.
45
49
 
46
- Papercraft automatically escapes all text emitted in templates according to the
47
- template type. For more information see the section on [escaping
48
- content](#escaping-content).
50
+ In Papercraft, dynamic data is passed explicitly to the template as block/lambda
51
+ arguments, making the data flow easy to follow and understand. Papercraft also lets
52
+ developers create derivative templates using full or partial parameter
53
+ application.
49
54
 
50
55
  ```ruby
51
56
  require 'papercraft'
52
57
 
53
- page = Papercraft.html { |*args|
58
+ page = ->(**props) {
54
59
  html {
55
- head { title 'Title' }
56
- body { emit_yield *args }
60
+ head { title 'My Title' }
61
+ body { render_children **props }
57
62
  }
58
63
  }
59
- page.render { p 'foo' }
60
- #=> "<html><head><title>Title</title></head><body><p>foo</p></body></html>"
64
+ page.render {
65
+ p(class: 'big') 'foo'
66
+ }
67
+ #=> "<html><head><title>Title</title></head><body><p class="big">foo</p></body></html>"
61
68
 
62
- hello = page.apply { |name| h1 "Hello, #{name}!" }
63
- hello.render('world')
69
+ hello_page = page.apply ->(name:, **) {
70
+ h1 "Hello, #{name}!"
71
+ }
72
+ hello.render(name: 'world')
64
73
  #=> "<html><head><title>Title</title></head><body><h1>Hello, world!</h1></body></html>"
65
74
  ```
66
75
 
76
+ Papercraft features:
77
+
78
+ - Express HTML using plain Ruby procs.
79
+ - Automatic compilation for super-fast execution (about as
80
+ [fast](https://github.com/digital-fabric/papercraft/blob/master/examples/perf.rb) as
81
+ compiled ERB/ERubi).
82
+ - Deferred rendering using `defer`.
83
+ - Simple and easy template composition (for uses such as layouts, or modular
84
+ templates).
85
+ - Markdown rendering using [Kramdown](https://github.com/gettalong/kramdown/).
86
+ - Rudimentary support for generating XML.
87
+ - Support for extensions.
88
+ - Simple caching API for caching the rendering result.
89
+
67
90
  ## Table of Content
68
91
 
69
- - [Installing Papercraft](#installing-papercraft)
70
- - [Using with Tilt](#using-with-tilt)
71
- - [Basic Usage](#basic-usage)
72
- - [Adding Tags](#adding-tags)
73
- - [Tag and Attribute Formatting](#tag-and-attribute-formatting)
74
- - [Escaping Content](#escaping-content)
75
- - [Direct Iteration](#direct-iteration)
92
+ - [Getting Started](#getting-started)
93
+ - [Basic Markup](#basic-markup)
94
+ - [Builtin Methods](#builtin-methods)
76
95
  - [Template Parameters](#template-parameters)
77
96
  - [Template Logic](#template-logic)
78
97
  - [Template Blocks](#template-blocks)
79
- - [Template Fragments](#template-fragments)
80
- - [Plain Procs as Templates](#plain-procs-as-templates)
81
98
  - [Template Composition](#template-composition)
82
99
  - [Parameter and Block Application](#parameter-and-block-application)
83
100
  - [Higher-Order Templates](#higher-order-templates)
84
101
  - [Layout Template Composition](#layout-template-composition)
85
- - [Emitting Raw HTML](#emitting-raw-html)
86
- - [Emitting a String with HTML Encoding](#emitting-a-string-with-html-encoding)
87
- - [Emitting Markdown](#emitting-markdown)
88
- - [Working with MIME Types](#working-with-mime-types)
102
+ - [Rendering Markdown](#rendering-markdown)
89
103
  - [Deferred Evaluation](#deferred-evaluation)
90
- - [HTML Templates](#html-templates)
91
- - [XML Templates](#xml-templates)
92
- - [JSON Templates](#json-templates)
93
- - [Papercraft Extensions](#papercraft-extensions)
94
- - [Extending Specific Templates](#extending-specific-templates)
95
- - [Inline Helper Methods](#inline-helper-methods)
96
- - [Bundled Extensions](#bundled-extensions)
97
- - [API Reference](#api-reference)
104
+ - [Cached Rendering](#cached-rendering)
98
105
 
99
- ## Installing Papercraft
106
+ A typical example for a dashboard-type app markup can be found here:
107
+ https://github.com/digital-fabric/papercraft/blob/master/examples/dashboard.rb
100
108
 
101
- **Note**: Papercraft requires Ruby version 3.2 or newer.
109
+ ## Getting Started
102
110
 
103
- Using bundler:
111
+ In Papercraft, an HTML template is expressed as a proc:
104
112
 
105
113
  ```ruby
106
- gem 'papercraft'
107
- ```
108
-
109
- Or manually:
110
-
111
- ```bash
112
- $ gem install papercraft
113
- ```
114
-
115
- ## Basic Usage
116
-
117
- To create an HTML template use `Papercraft.html`:
118
-
119
- ```ruby
120
- require 'papercraft'
121
-
122
- html = Papercraft.html {
114
+ html = -> {
123
115
  div(id: 'greeter') { p 'Hello!' }
124
116
  }
125
117
  ```
126
118
 
127
- (You can also use `Papercraft.xml` and `Papercraft.json` to create XML and JSON
128
- templates, respectively.)
129
-
130
- Rendering a template is done using `#render`:
119
+ Rendering a template is done using `Proc#render`:
131
120
 
132
121
  ```ruby
133
- html.render #=> "<div id="greeter"><p>Hello!</p></div>"
134
- ```
135
-
136
- ## Using with Tilt
137
-
138
- Papercraft templates can also be rendered using [Tilt](https://github.com/jeremyevans/tilt):
139
-
140
- ```ruby
141
- require 'tilt/papercraft'
142
-
143
- # loading from a file (with a .papercraft extension)
144
- template = Tilt.new('mytemplate.papercraft')
145
- template.render
146
-
147
- # defining a template inline:
148
- template = Tilt['papercraft'].new { <<~RUBY
149
- h1 'Hello'
150
- RUBY
151
- }
152
- template.render
153
- ```
154
-
155
- When rendering using Tilt, the following local variables are available to the
156
- template: `scope`, `locals` and `block`:
157
-
158
- ```ruby
159
- template = Tilt['papercraft'].new { <<~RUBY
160
- title scope.title
161
-
162
- h1 locals[:message]
163
-
164
- emit block if block
165
- RUBY
166
- }
167
-
168
- def title
169
- 'foo'
170
- end
122
+ require 'papercraft'
171
123
 
172
- template.render(self, message: 'bar') { p 'this is a paragraph' }
124
+ html.render #=> "<div id="greeter"><p>Hello!</p></div>"
173
125
  ```
174
126
 
175
- ## Adding Tags
127
+ ## Basic Markup
176
128
 
177
129
  Tags are added using unqualified method calls, and can be nested using blocks:
178
130
 
179
131
  ```ruby
180
- Papercraft.html {
132
+ -> {
181
133
  html {
182
134
  head {
183
135
  title 'page title'
@@ -194,146 +146,186 @@ Papercraft.html {
194
146
  Tag methods accept a string argument, a block, or no argument at all:
195
147
 
196
148
  ```ruby
197
- Papercraft.html { p 'hello' }.render #=> "<p>hello</p>"
149
+ -> { p 'hello' }.render #=> "<p>hello</p>"
198
150
 
199
- Papercraft.html { p { span '1'; span '2' } }.render #=> "<p><span>1</span><span>2</span></p>"
151
+ -> { p { span '1'; span '2' } }.render #=> "<p><span>1</span><span>2</span></p>"
200
152
 
201
- Papercraft.html { hr() }.render #=> "<hr/>"
153
+ -> { hr() }.render #=> "<hr/>"
202
154
  ```
203
155
 
204
156
  Tag methods also accept tag attributes, given as a hash:
205
157
 
206
158
  ```ruby
207
- Papercraft.html { img src: '/my.gif' }.render #=> "<img src=\"/my.gif\"/>"
159
+ -> { img src: '/my.gif' }.render #=> "<img src=\"/my.gif\"/>"
208
160
 
209
- Papercraft.html { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
161
+ -> { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
210
162
  ```
211
163
 
212
164
  A `true` attribute value will emit a valueless attribute. A `nil` or `false`
213
165
  attribute value will emit nothing:
214
166
 
215
167
  ```ruby
216
- Papercraft.html { button disabled: nil }.render #=> "<button></button>"
217
- Papercraft.html { button disabled: true }.render #=> "<button disabled></button>"
168
+ -> { button disabled: nil }.render #=> "<button></button>"
169
+ -> { button disabled: true }.render #=> "<button disabled></button>"
218
170
  ```
219
171
 
220
172
  An attribute value given as an array will be joined by space characters:
221
173
 
222
174
  ```ruby
223
- Papercraft.html { div class: [:foo, :bar] }.render #=> "<div class=\"foo bar\"></div>"
175
+ -> { div class: [:foo, :bar] }.render #=> "<div class=\"foo bar\"></div>"
224
176
  ```
225
177
 
226
- ## Tag and Attribute Formatting
178
+ ### Tag and Attribute Formatting
227
179
 
228
- Papercraft does not make any assumption about what tags and attributes you can
229
- use. You can mix upper and lower case letters, and you can include arbitrary
230
- characters in tag and attribute names. However, in order to best adhere to the
231
- HTML and XML specs and common practices, tag names and attributes will be
232
- formatted according to the following rules, depending on the template type:
180
+ Papercraft does not make any assumption about what tags and attributes you can use. You
181
+ can mix upper and lower case letters, and you can include arbitrary characters
182
+ in tag and attribute names. However, in order to best adhere to the HTML specs
183
+ and common practices, tag names and attributes will be formatted according to
184
+ the following rules, depending on the template type:
233
185
 
234
186
  - HTML: underscores are converted to dashes:
235
187
 
236
188
  ```ruby
237
- Papercraft.html {
189
+ -> {
238
190
  foo_bar { p 'Hello', data_name: 'world' }
239
191
  }.render #=> '<foo-bar><p data-name="world">Hello</p></foo-bar>'
240
192
  ```
241
193
 
242
- - XML: underscores are converted to dashes, double underscores are converted to
243
- colons:
244
-
245
- ```ruby
246
- Papercraft.xml {
247
- soap__Envelope(
248
- xmlns__soap: 'http://schemas.xmlsoap.org/soap/envelope/',
249
- ) { }
250
- }.render #=> '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Envelope>'
251
- ```
252
-
253
194
  If you need more precise control over tag names, you can use the `#tag` method,
254
195
  which takes the tag name as its first parameter, then the rest of the parameters
255
196
  normally used for tags:
256
197
 
257
198
  ```ruby
258
- Papercraft.html {
199
+ -> {
259
200
  tag 'cra_zy__:!tag', 'foo'
260
201
  }.render #=> '<cra_zy__:!tag>foo</cra_zy__:!tag>'
261
202
  ```
262
203
 
263
- ## Escaping Content
204
+ ### Escaping Content
205
+
206
+ Papercraft automatically escapes all text content emitted in a template. The specific
207
+ escaping algorithm depends on the template type. To emit raw HTML, use the
208
+ `#raw` method as [described below](#builtin-methods).
209
+
210
+ ## Builtin Methods
264
211
 
265
- Papercraft automatically escapes all text content emitted in a template. The
266
- specific escaping algorithm depends on the template type. For both HTML and XML
267
- templates, Papercraft uses
268
- [escape_utils](https://github.com/brianmario/escape_utils), specifically:
212
+ In addition to normal tags, Papercraft provides the following method calls for templates:
269
213
 
270
- - HTML: `escape_utils.escape_html`
271
- - XML: `escape_utils.escape_xml`
214
+ ### `#text` - emit escaped text
272
215
 
273
- In order to emit raw HTML/XML, you can use the `#emit` method as [described
274
- below](#emitting-raw-html).
216
+ `#text` is used for emitting text that will be escaped. This method can be used
217
+ to emit text not directly inside an enclosing tag:
275
218
 
276
- JSON templates are rendered using the `json` gem bundled with Ruby, which takes
277
- care of escaping text values.
219
+ ```ruby
220
+ -> {
221
+ p {
222
+ text 'The time is: '
223
+ span(Time.now, id: 'clock')
224
+ }
225
+ }.render #=> <p>The time is: <span id="clock">XX:XX:XX</span></p>
226
+ ```
278
227
 
279
- ## Direct Iteration
228
+ ### `#raw` - emit raw HTML
280
229
 
281
- Papercraft enables iterating directly over any enumerable data source. Instead
282
- of rendering each item in a given data container by wrapping it inside of an
283
- `#each` block, we can simply pass the data source *directly* to the tag using
284
- the `_for` attribute. This is particularly useful when we need to create a set
285
- of nested tags for each item. Consider the following example:
230
+ `#raw` is used for emitting raw HTML, i.e. without escaping. You can use this to
231
+ emit an HTML snippet:
286
232
 
287
233
  ```ruby
288
- data = %w{foo bar}
234
+ TITLE_HTML = '<h1>hi</h1>'
235
+ -> {
236
+ div {
237
+ raw TITLE_HTML
238
+ }
239
+ }.render #=> <div><h1>hi</h1></div>
240
+ ```
241
+
242
+ ### `#render_children` - render the given block
289
243
 
290
- Papercraft.html {
291
- data.each { |item|
292
- tr {
293
- td item
244
+ `#render_children` is used to emit a given block. If no block is given, a
245
+ `LocalJumpError` exception is raised:
246
+
247
+ ```ruby
248
+ Card = ->(**props) {
249
+ card { render_children(**props) }
250
+ }
251
+
252
+ Card.render(foo: 'bar') { |foo|
253
+ h1 foo
254
+ } #=> <card><h1>bar</h1></card>
255
+ ```
256
+
257
+ `render_children` can be called with or without arguments, which are passed to the
258
+ given block.
259
+
260
+ ### `#defer` - emit deferred HTML
261
+
262
+ `#defer` is used to emit HTML in a deferred fashion - the deferred part will be
263
+ evaluated only after processing the entire template:
264
+
265
+ ```ruby
266
+ Layout = -> {
267
+ head {
268
+ defer {
269
+ title @title
294
270
  }
295
271
  }
296
- }.render #=> '<tr><td>foo</td></tr><tr><td>bar</td></tr>'
272
+ body {
273
+ render_children
274
+ }
275
+ }
276
+
277
+ Layout.render {
278
+ @title = 'Foobar'
279
+ h1 'hi'
280
+ } #=> <head><title>Foobar</title></head><body><h1>hi</h1></body>
297
281
  ```
298
282
 
299
- Instead of using `data.each` to iterate over the list of data, we can directly
300
- pass the data source to the `tr` tag using the special `_for` attribute:
283
+ ### `#render` - render the given template inline
284
+
285
+ `#render` is used to emit the given template. This can be used to compose
286
+ templates:
301
287
 
302
288
  ```ruby
303
- Papercraft.html {
304
- tr(_for: data) { |item|
305
- td item
289
+ partial = -> { p 'foo' }
290
+ -> {
291
+ div {
292
+ render partial
306
293
  }
307
- }.render #=> '<tr><td>foo</td></tr><tr><td>bar</td></tr>'
294
+ }.render #=> <div><p>foo</p></div>
295
+ ```
296
+
297
+ Any argument following the given template is passed to the template for
298
+ rendering:
299
+
300
+ ```ruby
301
+ large_button = ->(title) { button(title, class: 'large') }
302
+
303
+ -> {
304
+ render large_button, 'foo'
305
+ }.render #=> <button class="large">foo</button>
308
306
  ```
309
307
 
310
- Note that this will work with any data source that is an `Enumerable` or an
311
- `Enumerator`. For example, you can use `#each_with_index` or iterate over a
312
- hash. Papercraft will pass all yielded values to the given block:
308
+ ### `#html5` - emit an HTML5 document type declaration and html tag
313
309
 
314
310
  ```ruby
315
- data = %{foo bar}
316
- Papercraft.html {
317
- tr(_for: data.each_with_index) { |item, idx|
318
- td idx + 1
319
- td item
311
+ -> {
312
+ html5 {
313
+ p 'hi'
320
314
  }
321
- }.render #=> '<tr><td>1</td><td>foo</td></tr><tr><td>2</td><td>bar</td></tr>'
322
-
323
- data = [
324
- { name: 'foo', age: 16 },
325
- { name: 'bar', age: 32 }
326
- ]
327
- Papercraft.html {
328
- div(_for: data, class: 'row') { |row|
329
- div(_for: row) { |k, v|
330
- span k
331
- span v
332
- }
315
+ } #=> <!DOCTYPE html><html><p>hi</p></html>
316
+ ```
317
+
318
+ ### `#markdown` emit markdown content
319
+
320
+ `#markdown` is used for rendering markdown content. The call converts the given
321
+ markdown to HTML and emits it into the rendered HTML:
322
+
323
+ ```ruby
324
+ -> {
325
+ div {
326
+ markdown 'This is *markdown*'
333
327
  }
334
- }.render
335
- #=> '<div class="row"><div><span>name</span><span>foo</span></div><div><span>age</span><span>16</span></div></div>'
336
- #=> '<div class="row"><div><span>name</span><span>bar</span></div><div><span>age</span><span>32</span></div></div>'
328
+ }.render #=> <p>This is <em>markdown</em></p>
337
329
  ```
338
330
 
339
331
  ## Template Parameters
@@ -343,24 +335,24 @@ parameters are specified as block parameters, and are passed to the template on
343
335
  rendering:
344
336
 
345
337
  ```ruby
346
- greeting = Papercraft.html { |name| h1 "Hello, #{name}!" }
338
+ greeting = ->(name) { h1 "Hello, #{name}!" }
347
339
  greeting.render('world') #=> "<h1>Hello, world!</h1>"
348
340
  ```
349
341
 
350
342
  Templates can also accept named parameters:
351
343
 
352
344
  ```ruby
353
- greeting = Papercraft.html { |name:| h1 "Hello, #{name}!" }
345
+ greeting = ->(name:) { h1 "Hello, #{name}!" }
354
346
  greeting.render(name: 'world') #=> "<h1>Hello, world!</h1>"
355
347
  ```
356
348
 
357
349
  ## Template Logic
358
350
 
359
- Since Papercraft templates are just a bunch of Ruby, you can easily embed your
360
- view logic right in the template:
351
+ Since Papercraft templates are just a bunch of Ruby, you can easily embed your view
352
+ logic right in the template:
361
353
 
362
354
  ```ruby
363
- Papercraft.html { |user = nil|
355
+ ->(user = nil) {
364
356
  if user
365
357
  span "Hello, #{user.name}!"
366
358
  else
@@ -371,12 +363,12 @@ Papercraft.html { |user = nil|
371
363
 
372
364
  ## Template Blocks
373
365
 
374
- Templates can also accept and render blocks by using `emit_yield`:
366
+ Templates can also accept and render blocks by using `render_children`:
375
367
 
376
368
  ```ruby
377
- page = Papercraft.html {
369
+ page = -> {
378
370
  html {
379
- body { emit_yield }
371
+ body { render_children }
380
372
  }
381
373
  }
382
374
 
@@ -384,57 +376,10 @@ page = Papercraft.html {
384
376
  page.render { h1 'hi' }
385
377
  ```
386
378
 
387
- ## Template Fragments
388
-
389
- Template fragments allow rendering specific parts of a template, instead of the
390
- entire template. For example, you can define a template for a web page that
391
- includes a form, but may want to render just the form. Instead of extracting the
392
- code and putting it into a separate template, you can use template fragments to
393
- render just that part of that template:
394
-
395
- ```ruby
396
- page = Papercraft.html {
397
- div {
398
- h1 'Page title'
399
- p 'Some text'
400
- }
401
- div(id: 'my-form') {
402
- fragment(:form) {
403
- form {
404
- input(name: 'email')
405
- button 'OK'
406
- }
407
- }
408
- }
409
- }
410
- page.render_fragment(:form)
411
- ```
412
-
413
- For more information on how to use template fragments, see the [HTMX
414
- article](https://htmx.org/essays/template-fragments/).
415
-
416
- ## Plain Procs as Templates
417
-
418
- With Papercraft you can write a template as a plain Ruby proc, and later render
419
- it by passing it as a block to `Papercraft.html`:
420
-
421
- ```ruby
422
- greeting = proc { |name| h1 "Hello, #{name}!" }
423
- Papercraft.html(&greeting).render('world')
424
- ```
425
-
426
- Components can also be expressed using lambda notation:
427
-
428
- ```ruby
429
- greeting = ->(name) { h1 "Hello, #{name}!" }
430
- Papercraft.html(&greeting).render('world')
431
- ```
432
-
433
379
  ## Template Composition
434
380
 
435
- Papercraft makes it easy to compose multiple templates into a whole HTML
436
- document. A Papercraft template can contain other templates, as the following
437
- example shows.
381
+ Papercraft makes it easy to compose multiple templates into a whole HTML document. A Papercraft
382
+ template can contain other templates, as the following example shows.
438
383
 
439
384
  ```ruby
440
385
  Title = ->(title) { h1 title }
@@ -454,7 +399,7 @@ ItemList = ->(items) {
454
399
  }
455
400
  }
456
401
 
457
- page = Papercraft.html { |title, items|
402
+ page = ->(title, items) {
458
403
  html5 {
459
404
  head { Title(title) }
460
405
  body { ItemList(items) }
@@ -468,14 +413,14 @@ page.render('Hello from composed templates', [
468
413
  ```
469
414
 
470
415
  In addition to using templates defined as constants, you can also use
471
- non-constant templates by invoking the `#emit` method:
416
+ non-constant templates by invoking the `#render` method:
472
417
 
473
418
  ```ruby
474
419
  greeting = -> { span "Hello, world" }
475
420
 
476
- Papercraft.html {
421
+ -> {
477
422
  div {
478
- emit greeting
423
+ render greeting
479
424
  }
480
425
  }
481
426
  ```
@@ -486,17 +431,17 @@ Parameters and blocks can be applied to a template without it being rendered, by
486
431
  using `#apply`. This mechanism is what allows template composition and the
487
432
  creation of higher-order templates.
488
433
 
489
- The `#apply` method returns a new template which applies the given parameters and
490
- or block to the original template:
434
+ The `#apply` method returns a new template which applies the given parameters
435
+ and or block to the original template:
491
436
 
492
437
  ```ruby
493
438
  # parameter application
494
- hello = Papercraft.html { |name| h1 "Hello, #{name}!" }
439
+ hello = -> { |name| h1 "Hello, #{name}!" }
495
440
  hello_world = hello.apply('world')
496
441
  hello_world.render #=> "<h1>Hello, world!</h1>"
497
442
 
498
443
  # block application
499
- div_wrap = Papercraft.html { div { emit_yield } }
444
+ div_wrap = -> { div { render_children } }
500
445
  wrapped_h1 = div_wrap.apply { h1 'hi' }
501
446
  wrapped_h1.render #=> "<div><h1>hi</h1></div>"
502
447
 
@@ -507,16 +452,16 @@ wrapped_hello_world.render #=> "<div><h1>Hello, world!</h1></div>"
507
452
 
508
453
  ## Higher-Order Templates
509
454
 
510
- Papercraft also lets you create higher-order templates, that is,
511
- templates that take other templates as parameters, or as blocks. Higher-order
512
- templates are handy for creating layouts, wrapping templates in arbitrary
513
- markup, enhancing templates or injecting template parameters.
455
+ Papercraft also lets you create higher-order templates, that is, templates that take
456
+ other templates as parameters, or as blocks. Higher-order templates are handy
457
+ for creating layouts, wrapping templates in arbitrary markup, enhancing
458
+ templates or injecting template parameters.
514
459
 
515
460
  Here is a higher-order template that takes a template as parameter:
516
461
 
517
462
  ```ruby
518
- div_wrap = Papercraft.html { |inner| div { emit inner } }
519
- greeter = Papercraft.html { h1 'hi' }
463
+ div_wrap = -> { |inner| div { render inner } }
464
+ greeter = -> { h1 'hi' }
520
465
  wrapped_greeter = div_wrap.apply(greeter)
521
466
  wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
522
467
  ```
@@ -524,7 +469,7 @@ wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
524
469
  The inner template can also be passed as a block, as shown above:
525
470
 
526
471
  ```ruby
527
- div_wrap = Papercraft.html { div { emit_yield } }
472
+ div_wrap = -> { div { render_children } }
528
473
  wrapped_greeter = div_wrap.apply { h1 'hi' }
529
474
  wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
530
475
  ```
@@ -538,13 +483,13 @@ this by creating a `default` page template that takes a block, then use `#apply`
538
483
  to create the other templates:
539
484
 
540
485
  ```ruby
541
- default_layout = Papercraft.html { |**params|
486
+ default_layout = -> { |**params|
542
487
  html5 {
543
488
  head {
544
489
  title: params[:title]
545
490
  }
546
491
  body {
547
- emit_yield(**params)
492
+ render_children(**params)
548
493
  }
549
494
  }
550
495
  }
@@ -552,7 +497,7 @@ default_layout = Papercraft.html { |**params|
552
497
  article_layout = default_layout.apply { |title:, body:|
553
498
  article {
554
499
  h1 title
555
- emit_markdown body
500
+ markdown body
556
501
  }
557
502
  }
558
503
 
@@ -562,47 +507,27 @@ article_layout.render(
562
507
  )
563
508
  ```
564
509
 
565
- ## Emitting Raw HTML
566
-
567
- Raw HTML can be emitted using `#emit`:
568
-
569
- ```ruby
570
- wrapped = Papercraft.html { |html| div { emit html } }
571
- wrapped.render("<h1>hi</h1>") #=> "<div><h1>hi</h1></div>"
572
- ```
573
-
574
- ## Emitting a String with HTML Encoding
575
-
576
- To emit a string with proper HTML encoding, without wrapping it in an HTML
577
- element, use `#text`:
578
-
579
- ```ruby
580
- Papercraft.html { text 'hi&lo' }.render #=> "hi&amp;lo"
581
- ```
582
-
583
- ## Emitting Markdown
510
+ ## Rendering Markdown
584
511
 
585
512
  Markdown is rendered using the
586
513
  [Kramdown](https://kramdown.gettalong.org/index.html) gem. To emit Markdown, use
587
- `#emit_markdown`:
514
+ `#markdown`:
588
515
 
589
516
  ```ruby
590
- template = Papercraft.html { |md| div { emit_markdown md } }
517
+ template = -> { |md| div { markdown md } }
591
518
  template.render("Here's some *Markdown*") #=> "<div><p>Here's some <em>Markdown</em><p>\n</div>"
592
519
  ```
593
520
 
594
521
  [Kramdown
595
522
  options](https://kramdown.gettalong.org/options.html#available-options) can be
596
- specified by adding them to the `#emit_markdown` call:
523
+ specified by adding them to the `#markdown` call:
597
524
 
598
525
  ```ruby
599
- template = Papercraft.html { |md| div { emit_markdown md, auto_ids: false } }
526
+ template = -> { |md| div { markdown md, auto_ids: false } }
600
527
  template.render("# title") #=> "<div><h1>title</h1></div>"
601
528
  ```
602
529
 
603
- The `#emit_markdown` method is available only to HTML templates. If you need to
604
- render markdown in XML or JSON templates (usually for implementing RSS or JSON
605
- feeds), you can use `Papercraft.markdown` directly:
530
+ You can also use `Papercraft.markdown` directly:
606
531
 
607
532
  ```ruby
608
533
  Papercraft.markdown('# title') #=> "<h1>title</h1>"
@@ -615,7 +540,7 @@ The default Kramdown options are:
615
540
  entity_output: :numeric,
616
541
  syntax_highlighter: :rouge,
617
542
  input: 'GFM',
618
- hard_wrap: false
543
+ hard_wrap: false
619
544
  }
620
545
  ```
621
546
 
@@ -626,22 +551,6 @@ The deafult options can be configured by accessing
626
551
  Papercraft.default_kramdown_options[:auto_ids] = false
627
552
  ```
628
553
 
629
- ## Working with MIME Types
630
-
631
- Papercraft lets you set and interrogate a template's MIME type, in order to be
632
- able to dynamically set the `Content-Type` HTTP response header. A template's
633
- MIME type can be set when creating the template, e.g. `Papercraft.xml(mime_type:
634
- 'application/rss+xml')`. You can interrogate the template's MIME type using
635
- `#mime_type`:
636
-
637
- ```ruby
638
- # using Qeweney (https://github.com/digital-fabric/qeweney)
639
- def serve_template(req, template)
640
- body = template.render
641
- respond(body, 'Content-Type' => template.mime_type)
642
- end
643
- ```
644
-
645
554
  ## Deferred Evaluation
646
555
 
647
556
  Deferred evaluation allows deferring the rendering of parts of a template until
@@ -663,334 +572,49 @@ class that can collect JS and CSS dependencies from the different templates
663
572
  integrated into the page, and adds them to the page's `<head>` element:
664
573
 
665
574
  ```ruby
666
- default_layout = Papercraft.html { |**args|
667
- @dependencies = DependencyMananger.new
575
+ deps = DependencyMananger.new
576
+
577
+ default_layout = -> { |**args|
668
578
  head {
669
- defer { emit @dependencies.head_markup }
579
+ defer { render deps.head_markup }
670
580
  }
671
- body { emit_yield **args }
581
+ body { render_children **args }
672
582
  }
673
583
 
674
584
  button = proc { |text, onclick|
675
- @dependencies.js '/static/js/button.js'
676
- @dependencies.css '/static/css/button.css'
585
+ deps.js '/static/js/button.js'
586
+ deps.css '/static/css/button.css'
677
587
 
678
588
  button text, onclick: onclick
679
589
  }
680
590
 
681
591
  heading = proc { |text|
682
- @dependencies.js '/static/js/heading.js'
683
- @dependencies.css '/static/css/heading.css'
592
+ deps.js '/static/js/heading.js'
593
+ deps.css '/static/css/heading.css'
684
594
 
685
595
  h1 text
686
596
  }
687
597
 
688
598
  page = default_layout.apply {
689
- emit heading, "What's your favorite cheese?"
599
+ render heading, "What's your favorite cheese?"
690
600
 
691
- emit button, 'Beaufort', 'eat_beaufort()'
692
- emit button, 'Mont d''or', 'eat_montdor()'
693
- emit button, 'Époisses', 'eat_epoisses()'
601
+ render button, 'Beaufort', 'eat_beaufort()'
602
+ render button, 'Mont d''or', 'eat_montdor()'
603
+ render button, 'Époisses', 'eat_epoisses()'
694
604
  }
695
605
  ```
696
606
 
697
- ## HTML Templates
698
-
699
- HTML templates include a few HTML-specific methods to facilitate writing modern
700
- HTML:
701
-
702
- - `html5 { ... }` - emits an HTML 5 DOCTYPE (`<!DOCTYPE html>`)
703
- - `import_map(root_path, root_url)` - emits an import map including all files
704
- matching `<root_path>/*.js`, based on the given `root_url`
705
- - `js_module(js)` - emits a `<script type="module">` element
706
- - `link_stylesheet(href, **attributes)` - emits a `<link rel="stylesheet" ...>`
707
- element
708
- - `script(js, **attributes)` - emits an inline `<script>` element
709
- - `style(css, **attributes)` - emits an inline `<style>` element
710
- - `versioned_file_href(href, root_path, root_url)` - calculates a versioned href
711
- for the given file
712
-
713
- [HTML docs](https://www.rubydoc.info/gems/papercraft/Papercraft/HTML)
714
-
715
- ## XML Templates
716
-
717
- XML templates behave largely the same as HTML templates, with a few minor
718
- differences. XML templates employ a different encoding algorithm, and lack some
719
- specific HTML functionality, such as emitting Markdown.
720
-
721
- Here's an example showing how to create an RSS feed:
722
-
723
- ```ruby
724
- rss = Papercraft.xml(mime_type: 'text/xml; charset=utf-8') { |resource:, **props|
725
- rss(version: '2.0', 'xmlns:atom' => 'http://www.w3.org/2005/Atom') {
726
- channel {
727
- title 'Noteflakes'
728
- link 'https://noteflakes.com/'
729
- description 'A website by Sharon Rosner'
730
- language 'en-us'
731
- pubDate Time.now.httpdate
732
- emit '<atom:link href="https://noteflakes.com/feeds/rss" rel="self" type="application/rss+xml" />'
733
-
734
- article_entries = resource.page_list('/articles').reverse
735
-
736
- article_entries.each { |e|
737
- item {
738
- title e[:title]
739
- link "https://noteflakes.com#{e[:url]}"
740
- guid "https://noteflakes.com#{e[:url]}"
741
- pubDate e[:date].to_time.httpdate
742
- description e[:html_content]
743
- }
744
- }
745
- }
746
- }
747
- }
748
- ```
749
-
750
- [XML docs](https://www.rubydoc.info/gems/papercraft/Papercraft/XML)
607
+ ## Cached Rendering
751
608
 
752
- ## JSON Templates
753
-
754
- JSON templates behave largely the same as HTML and XML templates. The only major
755
- difference is that for adding array items you'll need to use the `#item` method:
609
+ Papercraft provides a simple API for caching the result of a rendering. The cache stores
610
+ renderings of a template respective to the given arguments. To automatically
611
+ retrieve the cached rendered HTML, or generate it for the first time, use
612
+ `Proc#render_cached`:
756
613
 
757
614
  ```ruby
758
- Papercraft.json {
759
- item 1
760
- item 2
761
- item 3
762
- }.render #=> "[1,2,3]"
763
- ```
764
-
765
- Otherwise, you can create arbitrarily complex JSON structures by mixing hashes
766
- and arrays:
767
-
768
- ```Ruby
769
- Papercraft.json {
770
- foo {
771
- bar {
772
- item nil
773
- item true
774
- item 123.456
775
- }
776
- }
777
- }.render #=> "{\"foo\":{\"bar\":[null,true,123.456]}}"
615
+ template = ->(title) { div { h1 title } }
616
+ template.render_cached('foo') #=> <div><h1>foo</h1></div>
617
+ template.render_cached('foo') #=> <div><h1>foo</h1></div> (from cache)
618
+ template.render_cached('bar') #=> <div><h1>bar</h1></div>
619
+ template.render_cached('bar') #=> <div><h1>bar</h1></div> (from cache)
778
620
  ```
779
-
780
- Papercraft uses the [JSON gem](https://rubyapi.org/3.1/o/json) under the hood in
781
- order to generate actual JSON.
782
-
783
- [JSON docs](https://www.rubydoc.info/gems/papercraft/Papercraft/JSON)
784
-
785
- ## Papercraft Extensions
786
-
787
- Papercraft extensions are modules that contain one or more methods that can be
788
- used to render complex HTML components. Extension modules can be used by
789
- installing them as a namespaced extension using `Papercraft::extension`.
790
- Extensions are particularly useful when you work with CSS frameworks such as
791
- [Bootstrap](https://getbootstrap.com/), [Tailwind](https://tailwindui.com/) or
792
- [Primer](https://primer.style/).
793
-
794
- For example, to create a Bootstrap card component, the following HTML markup is
795
- needed (example taken from the [Bootstrap
796
- docs](https://getbootstrap.com/docs/5.1/components/card/#titles-text-and-links)):
797
-
798
- ```html
799
- <div class="card" style="width: 18rem;">
800
- <div class="card-body">
801
- <h5 class="card-title">Card title</h5>
802
- <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
803
- <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
804
- <a href="#" class="card-link">Card link</a>
805
- <a href="#" class="card-link">Another link</a>
806
- </div>
807
- </div>
808
- ```
809
-
810
- With Papercraft, we could create a `Bootstrap` extension with a `#card` method
811
- and other associated methods:
812
-
813
- ```ruby
814
- module BootstrapComponents
815
- def card(**props, &block)
816
- div(class: 'card', **props) {
817
- div(class: 'card-body', &block)
818
- }
819
- end
820
-
821
- def card_title(title)
822
- h4(title, class: 'card-title')
823
- end
824
-
825
- def card_subtitle(subtitle)
826
- h5(subtitle, class: 'card-subtitle')
827
- end
828
-
829
- def card_text(text)
830
- p(text, class: 'card-text')
831
- end
832
-
833
- def card_link(text, **opts)
834
- a(text, class: 'card-link', **opts)
835
- end
836
- end
837
-
838
- Papercraft.extension(bootstrap: BootstrapComponents)
839
- ```
840
-
841
- The call to `Papercraft::extension` lets us access the different methods of
842
- `BootstrapComponents` by calling `#bootstrap` inside a template. With this,
843
- we'll be able to express the above markup as follows:
844
-
845
- ```ruby
846
- Papercraft.html {
847
- bootstrap.card(style: 'width: 18rem') {
848
- bootstrap.card_title 'Card title'
849
- bootstrap.card_subtitle 'Card subtitle'
850
- bootstrap.card_text 'Some quick example text to build on the card title and make up the bulk of the card''s content.'
851
- bootstrap.card_link 'Card link', href: '#foo'
852
- bootstrap.card_link 'Another link', href: '#bar'
853
- }
854
- }
855
- ```
856
-
857
- ### Extending Specific Templates
858
-
859
- Sometimes you wish to extend a specific template locally, without the extension
860
- API being available to other templates. To do this you can use `#extend`:
861
-
862
- ```ruby
863
- module CustomTags
864
- def label(text)
865
- span text, class: 'label'
866
- end
867
- end
868
-
869
- Papercraft.html {
870
- extend CustomTags
871
-
872
- label 'foo'
873
- }
874
- ```
875
-
876
- The extension is in effect as long as the template is processing, so it is also
877
- accessible to any sub templates that are emitted.
878
-
879
- Local extensions can also be namespaced by passing `#extend` a hash mapping
880
- namespaces to modules:
881
-
882
- ```ruby
883
- Papercraft.html {
884
- extend custom: CustomTags
885
-
886
- custom.label 'foo'
887
- }
888
- ```
889
-
890
- ### Inline Helper Methods
891
-
892
- In addition to proper extensions defined in modules, you can also define
893
- individual extension methods inline in your Papercraft templates. You can do
894
- this using any of the following techniques:
895
-
896
- 1. Define a method in the template body:
897
-
898
- ```ruby
899
- Papercraft.html {
900
- def label(text)
901
- span text, class: 'label'
902
- end
903
-
904
- label 'foo'
905
- label 'bar'
906
- }
907
- ```
908
-
909
- 2. Use `def_tag` to define a custom tag:
910
-
911
- ```ruby
912
- Papercraft.html {
913
- def_tag(:label) { |text| span text, class: 'label' }
914
-
915
- label 'foo'
916
- label 'bar'
917
- }
918
- ```
919
-
920
- Note that using any of the above methods you can also create custom components
921
- that take a block with inner HTML:
922
-
923
- ```ruby
924
- # using def
925
- def section(title, &inner)
926
- div {
927
- h1 title
928
- emit inner
929
- }
930
- end
931
-
932
- # using def_tag
933
- def_tag(:section) do |title, &inner|
934
- div {
935
- h1 title
936
- emit inner
937
- }
938
- end
939
- ```
940
-
941
- ### Bundled Extensions
942
-
943
- Papercraft comes bundled with a few extensions that address common use cases.
944
- All bundled extensions are namespaced under `Papercraft::Extensions`, and must
945
- be specifically required in order to be available to templates.
946
-
947
- For all bundled Papercraft extensions, there's no need to call
948
- `Papercraft.extension`, requiring the extension is sufficient.
949
-
950
- #### SOAP Extension
951
-
952
- > The SOAP extension was contributed by [@aemadrid](https://github.com/aemadrid).
953
-
954
- The SOAP extension provides common tags for building SOAP payloads. To load the
955
- SOAP extensions, require `polyphony/extensions/soap`. The extension provides the
956
- following methods:
957
-
958
- - `soap.Body(...)` - emits a `soap:Body` tag.
959
- - `soap.Envelope(...)` - emits a `soap:Envelope` tag.
960
- - `soap.Fault(...)` - emits a `soap:Fault` tag.
961
- - `soap.Header(...)` - emits a `soap:Header` tag.
962
-
963
- As mentioned above, namespaced tags and attributes may be specified by using
964
- double underscores for colons. Other tags that contain special characters may be
965
- emitted using the `#tag` method:
966
-
967
- ```ruby
968
- require 'polyphony/extensions/soap'
969
-
970
- xml = Papercraft.xml {
971
- soap.Envelope(
972
- xmlns__xsd: 'http://www.w3.org/2001/XMLSchema',
973
- xmlns__xsi: 'http://www.w3.org/2001/XMLSchema-instance'
974
- ) {
975
- soap.Body {
976
- PosRequest(xmlns: 'http://Some.Site') {
977
- tag('Ver1.0') {
978
- Header {
979
- SecretAPIKey 'some_secret_key'
980
- }
981
- Transaction {
982
- SomeData {}
983
- }
984
- }
985
- }
986
- }
987
- }
988
- }
989
- ```
990
-
991
- [SOAP docs](https://www.rubydoc.info/gems/papercraft/Papercraft/Extensions/Soap)
992
-
993
- ## API Reference
994
-
995
- The API reference for this library can be found
996
- [here](https://www.rubydoc.info/gems/papercraft).