papercraft 1.3 → 2.13

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_yield **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 { yield **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
102
  - [Emitting Markdown](#emitting-markdown)
88
- - [Working with MIME Types](#working-with-mime-types)
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,191 @@ 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
+ ```
227
+
228
+ ### `#raw` - emit raw HTML
229
+
230
+ `#raw` is used for emitting raw HTML, i.e. without escaping. You can use this to
231
+ emit an HTML snippet:
232
+
233
+ ```ruby
234
+ TITLE_HTML = '<h1>hi</h1>'
235
+ -> {
236
+ div {
237
+ raw TITLE_HTML
238
+ }
239
+ }.render #=> <div><h1>hi</h1></div>
240
+ ```
278
241
 
279
- ## Direct Iteration
242
+ ### `#render_yield` - emit given block
280
243
 
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:
244
+ `#render_yield` is used to emit a given block. If no block is given, a
245
+ `LocalJumpError` exception is raised:
286
246
 
287
247
  ```ruby
288
- data = %w{foo bar}
248
+ Card = ->(**props) {
249
+ card { render_yield(**props) }
250
+ }
251
+
252
+ Card.render(foo: 'bar') { |foo|
253
+ h1 foo
254
+ } #=> <card><h1>bar</h1></card>
255
+ ```
256
+
257
+ `render_yield` can be called with or without arguments, which are passed to the
258
+ given block.
259
+
260
+ ### `#render_children` - emit given block
289
261
 
290
- Papercraft.html {
291
- data.each { |item|
292
- tr {
293
- td item
262
+ `#render_children` is used to emit a given block, but does not raise an
263
+ exception if no block is given.
264
+
265
+ ### `#defer` - emit deferred HTML
266
+
267
+ `#defer` is used to emit HTML in a deferred fashion - the deferred part will be
268
+ evaluated only after processing the entire template:
269
+
270
+ ```ruby
271
+ Layout = -> {
272
+ head {
273
+ defer {
274
+ title @title
294
275
  }
295
276
  }
296
- }.render #=> '<tr><td>foo</td></tr><tr><td>bar</td></tr>'
277
+ body {
278
+ render_yield
279
+ }
280
+ }
281
+
282
+ Layout.render {
283
+ @title = 'Foobar'
284
+ h1 'hi'
285
+ } #=> <head><title>Foobar</title></head><body><h1>hi</h1></body>
297
286
  ```
298
287
 
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:
288
+ ### `#render` - render the given template inline
289
+
290
+ `#render` is used to emit the given template. This can be used to compose
291
+ templates:
301
292
 
302
293
  ```ruby
303
- Papercraft.html {
304
- tr(_for: data) { |item|
305
- td item
294
+ partial = -> { p 'foo' }
295
+ -> {
296
+ div {
297
+ render partial
306
298
  }
307
- }.render #=> '<tr><td>foo</td></tr><tr><td>bar</td></tr>'
299
+ }.render #=> <div><p>foo</p></div>
300
+ ```
301
+
302
+ Any argument following the given template is passed to the template for
303
+ rendering:
304
+
305
+ ```ruby
306
+ large_button = ->(title) { button(title, class: 'large') }
307
+
308
+ -> {
309
+ render large_button, 'foo'
310
+ }.render #=> <button class="large">foo</button>
308
311
  ```
309
312
 
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:
313
+ ### `#html5` - emit an HTML5 document type declaration and html tag
313
314
 
314
315
  ```ruby
315
- data = %{foo bar}
316
- Papercraft.html {
317
- tr(_for: data.each_with_index) { |item, idx|
318
- td idx + 1
319
- td item
316
+ -> {
317
+ html5 {
318
+ p 'hi'
320
319
  }
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
- }
320
+ } #=> <!DOCTYPE html><html><p>hi</p></html>
321
+ ```
322
+
323
+ ### `#markdown` emit markdown content
324
+
325
+ `#markdown` is used for rendering markdown content. The call converts the given
326
+ markdown to HTML and emits it into the rendered HTML:
327
+
328
+ ```ruby
329
+ -> {
330
+ div {
331
+ markdown 'This is *markdown*'
333
332
  }
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>'
333
+ }.render #=> <p>This is <em>markdown</em></p>
337
334
  ```
338
335
 
339
336
  ## Template Parameters
@@ -343,24 +340,24 @@ parameters are specified as block parameters, and are passed to the template on
343
340
  rendering:
344
341
 
345
342
  ```ruby
346
- greeting = Papercraft.html { |name| h1 "Hello, #{name}!" }
343
+ greeting = ->(name) { h1 "Hello, #{name}!" }
347
344
  greeting.render('world') #=> "<h1>Hello, world!</h1>"
348
345
  ```
349
346
 
350
347
  Templates can also accept named parameters:
351
348
 
352
349
  ```ruby
353
- greeting = Papercraft.html { |name:| h1 "Hello, #{name}!" }
350
+ greeting = ->(name:) { h1 "Hello, #{name}!" }
354
351
  greeting.render(name: 'world') #=> "<h1>Hello, world!</h1>"
355
352
  ```
356
353
 
357
354
  ## Template Logic
358
355
 
359
- Since Papercraft templates are just a bunch of Ruby, you can easily embed your
360
- view logic right in the template:
356
+ Since Papercraft templates are just a bunch of Ruby, you can easily embed your view
357
+ logic right in the template:
361
358
 
362
359
  ```ruby
363
- Papercraft.html { |user = nil|
360
+ ->(user = nil) {
364
361
  if user
365
362
  span "Hello, #{user.name}!"
366
363
  else
@@ -371,12 +368,12 @@ Papercraft.html { |user = nil|
371
368
 
372
369
  ## Template Blocks
373
370
 
374
- Templates can also accept and render blocks by using `emit_yield`:
371
+ Templates can also accept and render blocks by using `render_yield`:
375
372
 
376
373
  ```ruby
377
- page = Papercraft.html {
374
+ page = -> {
378
375
  html {
379
- body { emit_yield }
376
+ body { render_yield }
380
377
  }
381
378
  }
382
379
 
@@ -384,57 +381,10 @@ page = Papercraft.html {
384
381
  page.render { h1 'hi' }
385
382
  ```
386
383
 
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
384
  ## Template Composition
434
385
 
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.
386
+ Papercraft makes it easy to compose multiple templates into a whole HTML document. A Papercraft
387
+ template can contain other templates, as the following example shows.
438
388
 
439
389
  ```ruby
440
390
  Title = ->(title) { h1 title }
@@ -454,7 +404,7 @@ ItemList = ->(items) {
454
404
  }
455
405
  }
456
406
 
457
- page = Papercraft.html { |title, items|
407
+ page = ->(title, items) {
458
408
  html5 {
459
409
  head { Title(title) }
460
410
  body { ItemList(items) }
@@ -468,14 +418,14 @@ page.render('Hello from composed templates', [
468
418
  ```
469
419
 
470
420
  In addition to using templates defined as constants, you can also use
471
- non-constant templates by invoking the `#emit` method:
421
+ non-constant templates by invoking the `#render` method:
472
422
 
473
423
  ```ruby
474
424
  greeting = -> { span "Hello, world" }
475
425
 
476
- Papercraft.html {
426
+ -> {
477
427
  div {
478
- emit greeting
428
+ render greeting
479
429
  }
480
430
  }
481
431
  ```
@@ -486,17 +436,17 @@ Parameters and blocks can be applied to a template without it being rendered, by
486
436
  using `#apply`. This mechanism is what allows template composition and the
487
437
  creation of higher-order templates.
488
438
 
489
- The `#apply` method returns a new template which applies the given parameters and
490
- or block to the original template:
439
+ The `#apply` method returns a new template which applies the given parameters
440
+ and or block to the original template:
491
441
 
492
442
  ```ruby
493
443
  # parameter application
494
- hello = Papercraft.html { |name| h1 "Hello, #{name}!" }
444
+ hello = -> { |name| h1 "Hello, #{name}!" }
495
445
  hello_world = hello.apply('world')
496
446
  hello_world.render #=> "<h1>Hello, world!</h1>"
497
447
 
498
448
  # block application
499
- div_wrap = Papercraft.html { div { emit_yield } }
449
+ div_wrap = -> { div { render_yield } }
500
450
  wrapped_h1 = div_wrap.apply { h1 'hi' }
501
451
  wrapped_h1.render #=> "<div><h1>hi</h1></div>"
502
452
 
@@ -507,16 +457,16 @@ wrapped_hello_world.render #=> "<div><h1>Hello, world!</h1></div>"
507
457
 
508
458
  ## Higher-Order Templates
509
459
 
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.
460
+ Papercraft also lets you create higher-order templates, that is, templates that take
461
+ other templates as parameters, or as blocks. Higher-order templates are handy
462
+ for creating layouts, wrapping templates in arbitrary markup, enhancing
463
+ templates or injecting template parameters.
514
464
 
515
465
  Here is a higher-order template that takes a template as parameter:
516
466
 
517
467
  ```ruby
518
- div_wrap = Papercraft.html { |inner| div { emit inner } }
519
- greeter = Papercraft.html { h1 'hi' }
468
+ div_wrap = -> { |inner| div { render inner } }
469
+ greeter = -> { h1 'hi' }
520
470
  wrapped_greeter = div_wrap.apply(greeter)
521
471
  wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
522
472
  ```
@@ -524,7 +474,7 @@ wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
524
474
  The inner template can also be passed as a block, as shown above:
525
475
 
526
476
  ```ruby
527
- div_wrap = Papercraft.html { div { emit_yield } }
477
+ div_wrap = -> { div { render_yield } }
528
478
  wrapped_greeter = div_wrap.apply { h1 'hi' }
529
479
  wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
530
480
  ```
@@ -538,13 +488,13 @@ this by creating a `default` page template that takes a block, then use `#apply`
538
488
  to create the other templates:
539
489
 
540
490
  ```ruby
541
- default_layout = Papercraft.html { |**params|
491
+ default_layout = -> { |**params|
542
492
  html5 {
543
493
  head {
544
494
  title: params[:title]
545
495
  }
546
496
  body {
547
- emit_yield(**params)
497
+ render_yield(**params)
548
498
  }
549
499
  }
550
500
  }
@@ -552,7 +502,7 @@ default_layout = Papercraft.html { |**params|
552
502
  article_layout = default_layout.apply { |title:, body:|
553
503
  article {
554
504
  h1 title
555
- emit_markdown body
505
+ markdown body
556
506
  }
557
507
  }
558
508
 
@@ -562,47 +512,27 @@ article_layout.render(
562
512
  )
563
513
  ```
564
514
 
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
515
  ## Emitting Markdown
584
516
 
585
517
  Markdown is rendered using the
586
518
  [Kramdown](https://kramdown.gettalong.org/index.html) gem. To emit Markdown, use
587
- `#emit_markdown`:
519
+ `#markdown`:
588
520
 
589
521
  ```ruby
590
- template = Papercraft.html { |md| div { emit_markdown md } }
522
+ template = -> { |md| div { markdown md } }
591
523
  template.render("Here's some *Markdown*") #=> "<div><p>Here's some <em>Markdown</em><p>\n</div>"
592
524
  ```
593
525
 
594
526
  [Kramdown
595
527
  options](https://kramdown.gettalong.org/options.html#available-options) can be
596
- specified by adding them to the `#emit_markdown` call:
528
+ specified by adding them to the `#markdown` call:
597
529
 
598
530
  ```ruby
599
- template = Papercraft.html { |md| div { emit_markdown md, auto_ids: false } }
531
+ template = -> { |md| div { markdown md, auto_ids: false } }
600
532
  template.render("# title") #=> "<div><h1>title</h1></div>"
601
533
  ```
602
534
 
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:
535
+ You can also use `Papercraft.markdown` directly:
606
536
 
607
537
  ```ruby
608
538
  Papercraft.markdown('# title') #=> "<h1>title</h1>"
@@ -615,7 +545,7 @@ The default Kramdown options are:
615
545
  entity_output: :numeric,
616
546
  syntax_highlighter: :rouge,
617
547
  input: 'GFM',
618
- hard_wrap: false
548
+ hard_wrap: false
619
549
  }
620
550
  ```
621
551
 
@@ -626,22 +556,6 @@ The deafult options can be configured by accessing
626
556
  Papercraft.default_kramdown_options[:auto_ids] = false
627
557
  ```
628
558
 
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
559
  ## Deferred Evaluation
646
560
 
647
561
  Deferred evaluation allows deferring the rendering of parts of a template until
@@ -663,334 +577,49 @@ class that can collect JS and CSS dependencies from the different templates
663
577
  integrated into the page, and adds them to the page's `<head>` element:
664
578
 
665
579
  ```ruby
666
- default_layout = Papercraft.html { |**args|
667
- @dependencies = DependencyMananger.new
580
+ deps = DependencyMananger.new
581
+
582
+ default_layout = -> { |**args|
668
583
  head {
669
- defer { emit @dependencies.head_markup }
584
+ defer { render deps.head_markup }
670
585
  }
671
- body { emit_yield **args }
586
+ body { render_yield **args }
672
587
  }
673
588
 
674
589
  button = proc { |text, onclick|
675
- @dependencies.js '/static/js/button.js'
676
- @dependencies.css '/static/css/button.css'
590
+ deps.js '/static/js/button.js'
591
+ deps.css '/static/css/button.css'
677
592
 
678
593
  button text, onclick: onclick
679
594
  }
680
595
 
681
596
  heading = proc { |text|
682
- @dependencies.js '/static/js/heading.js'
683
- @dependencies.css '/static/css/heading.css'
597
+ deps.js '/static/js/heading.js'
598
+ deps.css '/static/css/heading.css'
684
599
 
685
600
  h1 text
686
601
  }
687
602
 
688
603
  page = default_layout.apply {
689
- emit heading, "What's your favorite cheese?"
690
-
691
- emit button, 'Beaufort', 'eat_beaufort()'
692
- emit button, 'Mont d''or', 'eat_montdor()'
693
- emit button, 'Époisses', 'eat_epoisses()'
694
- }
695
- ```
696
-
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)
751
-
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:
756
-
757
- ```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]}}"
778
- ```
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
- ```
604
+ render heading, "What's your favorite cheese?"
856
605
 
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'
606
+ render button, 'Beaufort', 'eat_beaufort()'
607
+ render button, 'Mont d''or', 'eat_montdor()'
608
+ render button, 'Époisses', 'eat_epoisses()'
873
609
  }
874
610
  ```
875
611
 
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.
612
+ ## Cached Rendering
878
613
 
879
- Local extensions can also be namespaced by passing `#extend` a hash mapping
880
- namespaces to modules:
614
+ Papercraft provides a simple API for caching the result of a rendering. The cache stores
615
+ renderings of a template respective to the given arguments. To automatically
616
+ retrieve the cached rendered HTML, or generate it for the first time, use
617
+ `Proc#render_cached`:
881
618
 
882
619
  ```ruby
883
- Papercraft.html {
884
- extend custom: CustomTags
885
-
886
- custom.label 'foo'
887
- }
620
+ template = ->(title) { div { h1 title } }
621
+ template.render_cached('foo') #=> <div><h1>foo</h1></div>
622
+ template.render_cached('foo') #=> <div><h1>foo</h1></div> (from cache)
623
+ template.render_cached('bar') #=> <div><h1>bar</h1></div>
624
+ template.render_cached('bar') #=> <div><h1>bar</h1></div> (from cache)
888
625
  ```
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).