papercraft 2.23 → 2.24
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +15 -589
- data/lib/papercraft/compiler.rb +11 -11
- data/lib/papercraft/proc_ext.rb +14 -57
- data/lib/papercraft/template.rb +2 -2
- data/lib/papercraft/version.rb +1 -1
- data/lib/papercraft.rb +37 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea6b8d53d5110e44678302644fef523e5e9735b7f862f67b20a9f1bb4883c8b8
|
4
|
+
data.tar.gz: 3aacb8f0fb578c691230aefc5a1c0c16b1ddae30666004e977094f1f4a7793da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3014b7b8a22b5128f5aecb7fd7e7c181d6af5fd1f0727f71dafaa1994f16fda643c6f80aad04fcffc332042e392c11803d9f75d4b18ec8704edc94f374a927ab
|
7
|
+
data.tar.gz: e8d2017554d39c57d18ccd4ddf3a39f5fc4592d1c16584c47f5c31028a131c28379b43adad55557b70191df34d32f28d4e78989ed8d59700fc0f78bfd69dfcde
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -18,603 +18,29 @@
|
|
18
18
|
</a>
|
19
19
|
</p>
|
20
20
|
|
21
|
-
<p align="center">
|
22
|
-
<a href="https://www.rubydoc.info/gems/papercraft">API reference</a>
|
23
|
-
</p>
|
24
|
-
|
25
|
-
## What is Papercraft?
|
26
|
-
|
27
21
|
```ruby
|
28
22
|
require 'papercraft'
|
29
23
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
}
|
35
|
-
}
|
36
|
-
page.render {
|
37
|
-
p 'foo'
|
38
|
-
}
|
39
|
-
#=> "<html><head><title>Title</title></head><body><p>foo</p></body></html>"
|
24
|
+
-> {
|
25
|
+
h1 "Hello from Papercraft!"
|
26
|
+
}.render
|
27
|
+
#=> "<h1>Hello from Papercraft</h1>"
|
40
28
|
```
|
41
29
|
|
42
|
-
Papercraft is a templating engine for dynamically producing HTML in Ruby apps.
|
43
|
-
templates are expressed as Ruby procs, leading to easier debugging,
|
44
|
-
protection against HTML injection attacks, and better code reuse.
|
30
|
+
Papercraft is a templating engine for dynamically producing HTML in Ruby apps.
|
31
|
+
Papercraft templates are expressed as Ruby procs, leading to easier debugging,
|
32
|
+
better protection against HTML injection attacks, and better code reuse.
|
45
33
|
|
46
|
-
Papercraft templates can be composed in a variety of ways, facilitating the
|
47
|
-
layout templates, and enabling a component-oriented approach to
|
48
|
-
interfaces of arbitrary complexity.
|
34
|
+
Papercraft templates can be composed in a variety of ways, facilitating the
|
35
|
+
usage of layout templates, and enabling a component-oriented approach to
|
36
|
+
building web interfaces of arbitrary complexity.
|
49
37
|
|
50
38
|
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
|
52
|
-
developers create derivative templates using full or partial parameter
|
39
|
+
arguments, making the data flow easy to follow and understand. Papercraft also
|
40
|
+
lets developers create derivative templates using full or partial parameter
|
53
41
|
application.
|
54
42
|
|
55
|
-
|
56
|
-
require 'papercraft'
|
57
|
-
|
58
|
-
page = ->(**props) {
|
59
|
-
html {
|
60
|
-
head { title 'My Title' }
|
61
|
-
body { render_children **props }
|
62
|
-
}
|
63
|
-
}
|
64
|
-
page.render {
|
65
|
-
p(class: 'big') 'foo'
|
66
|
-
}
|
67
|
-
#=> "<html><head><title>Title</title></head><body><p class="big">foo</p></body></html>"
|
68
|
-
|
69
|
-
hello_page = page.apply ->(name:, **) {
|
70
|
-
h1 "Hello, #{name}!"
|
71
|
-
}
|
72
|
-
hello.render(name: 'world')
|
73
|
-
#=> "<html><head><title>Title</title></head><body><h1>Hello, world!</h1></body></html>"
|
74
|
-
```
|
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
|
-
|
90
|
-
## Table of Content
|
91
|
-
|
92
|
-
- [Getting Started](#getting-started)
|
93
|
-
- [Basic Markup](#basic-markup)
|
94
|
-
- [Builtin Methods](#builtin-methods)
|
95
|
-
- [Template Parameters](#template-parameters)
|
96
|
-
- [Template Logic](#template-logic)
|
97
|
-
- [Template Blocks](#template-blocks)
|
98
|
-
- [Template Composition](#template-composition)
|
99
|
-
- [Parameter and Block Application](#parameter-and-block-application)
|
100
|
-
- [Higher-Order Templates](#higher-order-templates)
|
101
|
-
- [Layout Template Composition](#layout-template-composition)
|
102
|
-
- [Rendering Markdown](#rendering-markdown)
|
103
|
-
- [Deferred Evaluation](#deferred-evaluation)
|
104
|
-
- [Cached Rendering](#cached-rendering)
|
105
|
-
|
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
|
108
|
-
|
109
|
-
## Getting Started
|
110
|
-
|
111
|
-
In Papercraft, an HTML template is expressed as a proc:
|
112
|
-
|
113
|
-
```ruby
|
114
|
-
html = -> {
|
115
|
-
div(id: 'greeter') { p 'Hello!' }
|
116
|
-
}
|
117
|
-
```
|
118
|
-
|
119
|
-
Rendering a template is done using `Proc#render`:
|
120
|
-
|
121
|
-
```ruby
|
122
|
-
require 'papercraft'
|
123
|
-
|
124
|
-
html.render #=> "<div id="greeter"><p>Hello!</p></div>"
|
125
|
-
```
|
126
|
-
|
127
|
-
## Basic Markup
|
128
|
-
|
129
|
-
Tags are added using unqualified method calls, and can be nested using blocks:
|
130
|
-
|
131
|
-
```ruby
|
132
|
-
-> {
|
133
|
-
html {
|
134
|
-
head {
|
135
|
-
title 'page title'
|
136
|
-
}
|
137
|
-
body {
|
138
|
-
article {
|
139
|
-
h1 'article title'
|
140
|
-
}
|
141
|
-
}
|
142
|
-
}
|
143
|
-
}
|
144
|
-
```
|
145
|
-
|
146
|
-
Tag methods accept a string argument, a block, or no argument at all:
|
147
|
-
|
148
|
-
```ruby
|
149
|
-
-> { p 'hello' }.render #=> "<p>hello</p>"
|
150
|
-
|
151
|
-
-> { p { span '1'; span '2' } }.render #=> "<p><span>1</span><span>2</span></p>"
|
152
|
-
|
153
|
-
-> { hr() }.render #=> "<hr/>"
|
154
|
-
```
|
155
|
-
|
156
|
-
Tag methods also accept tag attributes, given as a hash:
|
157
|
-
|
158
|
-
```ruby
|
159
|
-
-> { img src: '/my.gif' }.render #=> "<img src=\"/my.gif\"/>"
|
160
|
-
|
161
|
-
-> { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
|
162
|
-
```
|
163
|
-
|
164
|
-
A `true` attribute value will emit a valueless attribute. A `nil` or `false`
|
165
|
-
attribute value will emit nothing:
|
166
|
-
|
167
|
-
```ruby
|
168
|
-
-> { button disabled: nil }.render #=> "<button></button>"
|
169
|
-
-> { button disabled: true }.render #=> "<button disabled></button>"
|
170
|
-
```
|
171
|
-
|
172
|
-
An attribute value given as an array will be joined by space characters:
|
173
|
-
|
174
|
-
```ruby
|
175
|
-
-> { div class: [:foo, :bar] }.render #=> "<div class=\"foo bar\"></div>"
|
176
|
-
```
|
177
|
-
|
178
|
-
### Tag and Attribute Formatting
|
179
|
-
|
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:
|
185
|
-
|
186
|
-
- HTML: underscores are converted to dashes:
|
187
|
-
|
188
|
-
```ruby
|
189
|
-
-> {
|
190
|
-
foo_bar { p 'Hello', data_name: 'world' }
|
191
|
-
}.render #=> '<foo-bar><p data-name="world">Hello</p></foo-bar>'
|
192
|
-
```
|
193
|
-
|
194
|
-
If you need more precise control over tag names, you can use the `#tag` method,
|
195
|
-
which takes the tag name as its first parameter, then the rest of the parameters
|
196
|
-
normally used for tags:
|
197
|
-
|
198
|
-
```ruby
|
199
|
-
-> {
|
200
|
-
tag 'cra_zy__:!tag', 'foo'
|
201
|
-
}.render #=> '<cra_zy__:!tag>foo</cra_zy__:!tag>'
|
202
|
-
```
|
203
|
-
|
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
|
211
|
-
|
212
|
-
In addition to normal tags, Papercraft provides the following method calls for templates:
|
213
|
-
|
214
|
-
### `#text` - emit escaped text
|
215
|
-
|
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:
|
218
|
-
|
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
|
-
```
|
241
|
-
|
242
|
-
### `#render_children` - render the given block
|
243
|
-
|
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
|
270
|
-
}
|
271
|
-
}
|
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>
|
281
|
-
```
|
282
|
-
|
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:
|
287
|
-
|
288
|
-
```ruby
|
289
|
-
partial = -> { p 'foo' }
|
290
|
-
-> {
|
291
|
-
div {
|
292
|
-
render partial
|
293
|
-
}
|
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>
|
306
|
-
```
|
307
|
-
|
308
|
-
### `#html`/`#html5` - emit an HTML5 document type declaration and html tag
|
309
|
-
|
310
|
-
```ruby
|
311
|
-
-> {
|
312
|
-
html5 {
|
313
|
-
p 'hi'
|
314
|
-
}
|
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*'
|
327
|
-
}
|
328
|
-
}.render #=> <p>This is <em>markdown</em></p>
|
329
|
-
```
|
330
|
-
|
331
|
-
## Template Parameters
|
332
|
-
|
333
|
-
In Papercraft, parameters are always passed explicitly. This means that template
|
334
|
-
parameters are specified as block parameters, and are passed to the template on
|
335
|
-
rendering:
|
336
|
-
|
337
|
-
```ruby
|
338
|
-
greeting = ->(name) { h1 "Hello, #{name}!" }
|
339
|
-
greeting.render('world') #=> "<h1>Hello, world!</h1>"
|
340
|
-
```
|
341
|
-
|
342
|
-
Templates can also accept named parameters:
|
343
|
-
|
344
|
-
```ruby
|
345
|
-
greeting = ->(name:) { h1 "Hello, #{name}!" }
|
346
|
-
greeting.render(name: 'world') #=> "<h1>Hello, world!</h1>"
|
347
|
-
```
|
348
|
-
|
349
|
-
## Template Logic
|
350
|
-
|
351
|
-
Since Papercraft templates are just a bunch of Ruby, you can easily embed your view
|
352
|
-
logic right in the template:
|
353
|
-
|
354
|
-
```ruby
|
355
|
-
->(user = nil) {
|
356
|
-
if user
|
357
|
-
span "Hello, #{user.name}!"
|
358
|
-
else
|
359
|
-
span "Hello, guest!"
|
360
|
-
end
|
361
|
-
}
|
362
|
-
```
|
363
|
-
|
364
|
-
## Template Blocks
|
365
|
-
|
366
|
-
Templates can also accept and render blocks by using `render_children`:
|
367
|
-
|
368
|
-
```ruby
|
369
|
-
page = -> {
|
370
|
-
html {
|
371
|
-
body { render_children }
|
372
|
-
}
|
373
|
-
}
|
374
|
-
|
375
|
-
# we pass the inner HTML
|
376
|
-
page.render { h1 'hi' }
|
377
|
-
```
|
378
|
-
|
379
|
-
## Template Composition
|
380
|
-
|
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.
|
383
|
-
|
384
|
-
```ruby
|
385
|
-
Title = ->(title) { h1 title }
|
386
|
-
|
387
|
-
Item = ->(id:, text:, checked:) {
|
388
|
-
li {
|
389
|
-
input name: id, type: 'checkbox', checked: checked
|
390
|
-
label text, for: id
|
391
|
-
}
|
392
|
-
}
|
393
|
-
|
394
|
-
ItemList = ->(items) {
|
395
|
-
ul {
|
396
|
-
items.each { |i|
|
397
|
-
Item(**i)
|
398
|
-
}
|
399
|
-
}
|
400
|
-
}
|
401
|
-
|
402
|
-
page = ->(title, items) {
|
403
|
-
html5 {
|
404
|
-
head { Title(title) }
|
405
|
-
body { ItemList(items) }
|
406
|
-
}
|
407
|
-
}
|
408
|
-
|
409
|
-
page.render('Hello from composed templates', [
|
410
|
-
{ id: 1, text: 'foo', checked: false },
|
411
|
-
{ id: 2, text: 'bar', checked: true }
|
412
|
-
])
|
413
|
-
```
|
414
|
-
|
415
|
-
In addition to using templates defined as constants, you can also use
|
416
|
-
non-constant templates by invoking the `#render` method:
|
417
|
-
|
418
|
-
```ruby
|
419
|
-
greeting = -> { span "Hello, world" }
|
420
|
-
|
421
|
-
-> {
|
422
|
-
div {
|
423
|
-
render greeting
|
424
|
-
}
|
425
|
-
}
|
426
|
-
```
|
427
|
-
|
428
|
-
## Parameter and Block Application
|
429
|
-
|
430
|
-
Parameters and blocks can be applied to a template without it being rendered, by
|
431
|
-
using `#apply`. This mechanism is what allows template composition and the
|
432
|
-
creation of higher-order templates.
|
433
|
-
|
434
|
-
The `#apply` method returns a new template which applies the given parameters
|
435
|
-
and or block to the original template:
|
436
|
-
|
437
|
-
```ruby
|
438
|
-
# parameter application
|
439
|
-
hello = -> { |name| h1 "Hello, #{name}!" }
|
440
|
-
hello_world = hello.apply('world')
|
441
|
-
hello_world.render #=> "<h1>Hello, world!</h1>"
|
442
|
-
|
443
|
-
# block application
|
444
|
-
div_wrap = -> { div { render_children } }
|
445
|
-
wrapped_h1 = div_wrap.apply { h1 'hi' }
|
446
|
-
wrapped_h1.render #=> "<div><h1>hi</h1></div>"
|
447
|
-
|
448
|
-
# wrap a template
|
449
|
-
wrapped_hello_world = div_wrap.apply(&hello_world)
|
450
|
-
wrapped_hello_world.render #=> "<div><h1>Hello, world!</h1></div>"
|
451
|
-
```
|
452
|
-
|
453
|
-
## Higher-Order Templates
|
454
|
-
|
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.
|
459
|
-
|
460
|
-
Here is a higher-order template that takes a template as parameter:
|
461
|
-
|
462
|
-
```ruby
|
463
|
-
div_wrap = -> { |inner| div { render inner } }
|
464
|
-
greeter = -> { h1 'hi' }
|
465
|
-
wrapped_greeter = div_wrap.apply(greeter)
|
466
|
-
wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
467
|
-
```
|
468
|
-
|
469
|
-
The inner template can also be passed as a block, as shown above:
|
470
|
-
|
471
|
-
```ruby
|
472
|
-
div_wrap = -> { div { render_children } }
|
473
|
-
wrapped_greeter = div_wrap.apply { h1 'hi' }
|
474
|
-
wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
475
|
-
```
|
476
|
-
|
477
|
-
## Layout Template Composition
|
478
|
-
|
479
|
-
One of the principal uses of higher-order templates is the creation of nested
|
480
|
-
layouts. Suppose we have a website with a number of different layouts, and we'd
|
481
|
-
like to avoid having to repeat the same code in the different layouts. We can do
|
482
|
-
this by creating a `default` page template that takes a block, then use `#apply`
|
483
|
-
to create the other templates:
|
484
|
-
|
485
|
-
```ruby
|
486
|
-
default_layout = -> { |**params|
|
487
|
-
html5 {
|
488
|
-
head {
|
489
|
-
title: params[:title]
|
490
|
-
}
|
491
|
-
body {
|
492
|
-
render_children(**params)
|
493
|
-
}
|
494
|
-
}
|
495
|
-
}
|
496
|
-
|
497
|
-
article_layout = default_layout.apply { |title:, body:|
|
498
|
-
article {
|
499
|
-
h1 title
|
500
|
-
markdown body
|
501
|
-
}
|
502
|
-
}
|
503
|
-
|
504
|
-
article_layout.render(
|
505
|
-
title: 'This is a title',
|
506
|
-
body: 'Hello from *markdown body*'
|
507
|
-
)
|
508
|
-
```
|
509
|
-
|
510
|
-
## Rendering Markdown
|
511
|
-
|
512
|
-
Markdown is rendered using the
|
513
|
-
[Kramdown](https://kramdown.gettalong.org/index.html) gem. To emit Markdown, use
|
514
|
-
`#markdown`:
|
515
|
-
|
516
|
-
```ruby
|
517
|
-
template = -> { |md| div { markdown md } }
|
518
|
-
template.render("Here's some *Markdown*") #=> "<div><p>Here's some <em>Markdown</em><p>\n</div>"
|
519
|
-
```
|
520
|
-
|
521
|
-
[Kramdown
|
522
|
-
options](https://kramdown.gettalong.org/options.html#available-options) can be
|
523
|
-
specified by adding them to the `#markdown` call:
|
524
|
-
|
525
|
-
```ruby
|
526
|
-
template = -> { |md| div { markdown md, auto_ids: false } }
|
527
|
-
template.render("# title") #=> "<div><h1>title</h1></div>"
|
528
|
-
```
|
529
|
-
|
530
|
-
You can also use `Papercraft.markdown` directly:
|
531
|
-
|
532
|
-
```ruby
|
533
|
-
Papercraft.markdown('# title') #=> "<h1>title</h1>"
|
534
|
-
```
|
535
|
-
|
536
|
-
The default Kramdown options are:
|
43
|
+
## Documentation
|
537
44
|
|
538
|
-
|
539
|
-
|
540
|
-
entity_output: :numeric,
|
541
|
-
syntax_highlighter: :rouge,
|
542
|
-
input: 'GFM',
|
543
|
-
hard_wrap: false
|
544
|
-
}
|
545
|
-
```
|
546
|
-
|
547
|
-
The deafult options can be configured by accessing
|
548
|
-
`Papercraft.default_kramdown_options`, e.g.:
|
549
|
-
|
550
|
-
```ruby
|
551
|
-
Papercraft.default_kramdown_options[:auto_ids] = false
|
552
|
-
```
|
553
|
-
|
554
|
-
## Deferred Evaluation
|
555
|
-
|
556
|
-
Deferred evaluation allows deferring the rendering of parts of a template until
|
557
|
-
the last moment, thus allowing an inner template to manipulate the state of the
|
558
|
-
outer template. To in order to defer a part of a template, use `#defer`, and
|
559
|
-
include any markup in the provided block. This technique, in in conjunction with
|
560
|
-
holding state in instance variables, is an alternative to passing parameters,
|
561
|
-
which can be limiting in some situations.
|
562
|
-
|
563
|
-
A few use cases for deferred evaulation come to mind:
|
564
|
-
|
565
|
-
- Setting the page title.
|
566
|
-
- Adding a flash message to a page.
|
567
|
-
- Using templates that dynamically add static dependencies (JS and CSS) to the
|
568
|
-
page.
|
569
|
-
|
570
|
-
The last use case is particularly interesting. Imagine a `DependencyMananger`
|
571
|
-
class that can collect JS and CSS dependencies from the different templates
|
572
|
-
integrated into the page, and adds them to the page's `<head>` element:
|
573
|
-
|
574
|
-
```ruby
|
575
|
-
deps = DependencyMananger.new
|
576
|
-
|
577
|
-
default_layout = -> { |**args|
|
578
|
-
head {
|
579
|
-
defer { render deps.head_markup }
|
580
|
-
}
|
581
|
-
body { render_children **args }
|
582
|
-
}
|
583
|
-
|
584
|
-
button = proc { |text, onclick|
|
585
|
-
deps.js '/static/js/button.js'
|
586
|
-
deps.css '/static/css/button.css'
|
587
|
-
|
588
|
-
button text, onclick: onclick
|
589
|
-
}
|
590
|
-
|
591
|
-
heading = proc { |text|
|
592
|
-
deps.js '/static/js/heading.js'
|
593
|
-
deps.css '/static/css/heading.css'
|
594
|
-
|
595
|
-
h1 text
|
596
|
-
}
|
597
|
-
|
598
|
-
page = default_layout.apply {
|
599
|
-
render heading, "What's your favorite cheese?"
|
600
|
-
|
601
|
-
render button, 'Beaufort', 'eat_beaufort()'
|
602
|
-
render button, 'Mont d''or', 'eat_montdor()'
|
603
|
-
render button, 'Époisses', 'eat_epoisses()'
|
604
|
-
}
|
605
|
-
```
|
606
|
-
|
607
|
-
## Cached Rendering
|
608
|
-
|
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`:
|
613
|
-
|
614
|
-
```ruby
|
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)
|
620
|
-
```
|
45
|
+
For more information, please consult the [Papercraft
|
46
|
+
website](https://papercraft.noteflakes.com/).
|
data/lib/papercraft/compiler.rb
CHANGED
@@ -178,7 +178,7 @@ module Papercraft
|
|
178
178
|
when Prism::BlockArgumentNode
|
179
179
|
flush_html_parts!
|
180
180
|
adjust_whitespace(node.block)
|
181
|
-
emit("; #{format_code(node.block.expression)}.
|
181
|
+
emit("; #{format_code(node.block.expression)}.__compiled_proc__.(__buffer__)")
|
182
182
|
end
|
183
183
|
|
184
184
|
if node.inner_text
|
@@ -213,7 +213,7 @@ module Papercraft
|
|
213
213
|
emit(format_code(node.call_node.receiver))
|
214
214
|
emit('::')
|
215
215
|
end
|
216
|
-
emit("#{node.call_node.name}.
|
216
|
+
emit("#{node.call_node.name}.__compiled_proc__.(__buffer__")
|
217
217
|
if node.call_node.arguments
|
218
218
|
emit(', ')
|
219
219
|
visit(node.call_node.arguments)
|
@@ -229,17 +229,17 @@ module Papercraft
|
|
229
229
|
args = node.call_node.arguments.arguments
|
230
230
|
first_arg = args.first
|
231
231
|
|
232
|
-
block_embed = node.block && "&(->(__buffer__) #{format_code(node.block)}.
|
232
|
+
block_embed = node.block && "&(->(__buffer__) #{format_code(node.block)}.__compiled__!)"
|
233
233
|
block_embed = ", #{block_embed}" if block_embed && node.call_node.arguments
|
234
234
|
|
235
235
|
flush_html_parts!
|
236
236
|
adjust_whitespace(node.location)
|
237
237
|
|
238
238
|
if args.length == 1
|
239
|
-
emit("; #{format_code(first_arg)}.
|
239
|
+
emit("; #{format_code(first_arg)}.__compiled_proc__.(__buffer__#{block_embed})")
|
240
240
|
else
|
241
241
|
args_code = format_code_comma_separated_nodes(args[1..])
|
242
|
-
emit("; #{format_code(first_arg)}.
|
242
|
+
emit("; #{format_code(first_arg)}.__compiled_proc__.(__buffer__, #{args_code}#{block_embed})")
|
243
243
|
end
|
244
244
|
end
|
245
245
|
|
@@ -336,7 +336,7 @@ module Papercraft
|
|
336
336
|
def visit_extension_tag_node(node)
|
337
337
|
flush_html_parts!
|
338
338
|
adjust_whitespace(node.location)
|
339
|
-
emit("; Papercraft::Extensions[#{node.tag.inspect}].
|
339
|
+
emit("; Papercraft::Extensions[#{node.tag.inspect}].__compiled_proc__.(__buffer__")
|
340
340
|
if node.call_node.arguments
|
341
341
|
emit(', ')
|
342
342
|
visit(node.call_node.arguments)
|
@@ -367,7 +367,7 @@ module Papercraft
|
|
367
367
|
end
|
368
368
|
block_params = block_params.empty? ? '' : ", #{block_params.join(', ')}"
|
369
369
|
|
370
|
-
emit(", &(proc { |__buffer__#{block_params}| #{block_body} }).
|
370
|
+
emit(", &(proc { |__buffer__#{block_params}| #{block_body} }).__compiled__!")
|
371
371
|
end
|
372
372
|
emit(")")
|
373
373
|
end
|
@@ -382,7 +382,7 @@ module Papercraft
|
|
382
382
|
guard = @render_yield_used ?
|
383
383
|
'' : "; raise(LocalJumpError, 'no block given (render_yield)') if !__block__"
|
384
384
|
@render_yield_used = true
|
385
|
-
emit("#{guard}; __block__.
|
385
|
+
emit("#{guard}; __block__.__compiled_proc__.(__buffer__")
|
386
386
|
if node.call_node.arguments
|
387
387
|
emit(', ')
|
388
388
|
visit(node.call_node.arguments)
|
@@ -398,7 +398,7 @@ module Papercraft
|
|
398
398
|
flush_html_parts!
|
399
399
|
adjust_whitespace(node.location)
|
400
400
|
@render_children_used = true
|
401
|
-
emit("; __block__&.
|
401
|
+
emit("; __block__&.__compiled_proc__&.(__buffer__")
|
402
402
|
if node.call_node.arguments
|
403
403
|
emit(', ')
|
404
404
|
visit(node.call_node.arguments)
|
@@ -410,7 +410,7 @@ module Papercraft
|
|
410
410
|
flush_html_parts!
|
411
411
|
adjust_whitespace(node.location)
|
412
412
|
|
413
|
-
emit("; #{node.call_node.receiver.name}.
|
413
|
+
emit("; #{node.call_node.receiver.name}.__compiled_proc__.(__buffer__")
|
414
414
|
if node.call_node.arguments
|
415
415
|
emit(', ')
|
416
416
|
visit(node.call_node.arguments)
|
@@ -418,7 +418,7 @@ module Papercraft
|
|
418
418
|
if node.call_node.block
|
419
419
|
emit(", &(->")
|
420
420
|
visit(node.call_node.block)
|
421
|
-
emit(").
|
421
|
+
emit(").__compiled_proc__")
|
422
422
|
end
|
423
423
|
emit(")")
|
424
424
|
end
|
data/lib/papercraft/proc_ext.rb
CHANGED
@@ -5,42 +5,19 @@ require_relative './compiler'
|
|
5
5
|
module Papercraft
|
6
6
|
# Extensions to the Proc class.
|
7
7
|
module ProcExtensions
|
8
|
-
# Returns the compiled form code for the proc.
|
9
|
-
#
|
10
|
-
# @return [String] compiled proc code
|
11
|
-
def compiled_code
|
12
|
-
Papercraft::Compiler.compile_to_code(self).last
|
13
|
-
end
|
14
|
-
|
15
|
-
# Returns the source map for the compiled proc.
|
16
|
-
#
|
17
|
-
# @return [Array<String>] source map
|
18
|
-
def source_map
|
19
|
-
loc = source_location
|
20
|
-
fn = compiled? ? loc.first : Papercraft::Compiler.source_location_to_fn(loc)
|
21
|
-
Papercraft::Compiler.source_map_store[fn]
|
22
|
-
end
|
23
|
-
|
24
|
-
# Returns the AST for the proc.
|
25
|
-
#
|
26
|
-
# @return [Prism::Node] AST root
|
27
|
-
def ast
|
28
|
-
Sirop.to_ast(self)
|
29
|
-
end
|
30
|
-
|
31
8
|
# Returns true if proc is marked as compiled.
|
32
9
|
#
|
33
10
|
# @return [bool] is the proc marked as compiled
|
34
|
-
def
|
35
|
-
@
|
11
|
+
def __compiled__?
|
12
|
+
@__compiled__
|
36
13
|
end
|
37
14
|
|
38
15
|
# Marks the proc as compiled, i.e. can render directly and takes a string
|
39
16
|
# buffer as first argument.
|
40
17
|
#
|
41
18
|
# @return [self]
|
42
|
-
def
|
43
|
-
@
|
19
|
+
def __compiled__!
|
20
|
+
@__compiled__ = true
|
44
21
|
self
|
45
22
|
end
|
46
23
|
|
@@ -49,25 +26,15 @@ module Papercraft
|
|
49
26
|
#
|
50
27
|
# @param mode [Symbol] compilation mode (:html, :xml)
|
51
28
|
# @return [Proc] compiled proc or self
|
52
|
-
def
|
53
|
-
@
|
54
|
-
end
|
55
|
-
|
56
|
-
# Compiles the proc into the compiled form.
|
57
|
-
#
|
58
|
-
# @param mode [Symbol] compilation mode (:html, :xml)
|
59
|
-
# @return [Proc] compiled proc
|
60
|
-
def compile(mode: :html)
|
61
|
-
Papercraft::Compiler.compile(self, mode:).compiled!
|
62
|
-
rescue Sirop::Error
|
63
|
-
raise Papercraft::Error, "Dynamically defined procs cannot be compiled"
|
29
|
+
def __compiled_proc__(mode: :html)
|
30
|
+
@__compiled_proc__ ||= @__compiled__ ? self : Papercraft.compile(self, mode:)
|
64
31
|
end
|
65
32
|
|
66
33
|
# Renders the proc to HTML with the given arguments.
|
67
34
|
#
|
68
35
|
# @return [String] HTML string
|
69
36
|
def render(*a, **b, &c)
|
70
|
-
|
37
|
+
__compiled_proc__.(+'', *a, **b, &c)
|
71
38
|
rescue Exception => e
|
72
39
|
e.is_a?(Papercraft::Error) ? raise : raise(Papercraft.translate_backtrace(e))
|
73
40
|
end
|
@@ -76,21 +43,11 @@ module Papercraft
|
|
76
43
|
#
|
77
44
|
# @return [String] XML string
|
78
45
|
def render_xml(*a, **b, &c)
|
79
|
-
|
46
|
+
__compiled_proc__(mode: :xml).(+'', *a, **b, &c)
|
80
47
|
rescue Exception => e
|
81
48
|
e.is_a?(Papercraft::Error) ? raise : raise(Papercraft.translate_backtrace(e))
|
82
49
|
end
|
83
50
|
|
84
|
-
# Renders the proc to HTML with the given arguments into the given buffer.
|
85
|
-
#
|
86
|
-
# @param buf [String] buffer
|
87
|
-
# @return [String] HTML string
|
88
|
-
def render_to_buffer(buf, *a, **b, &c)
|
89
|
-
compiled_proc.(buf, *a, **b, &c)
|
90
|
-
rescue Exception => e
|
91
|
-
raise Papercraft.translate_backtrace(e)
|
92
|
-
end
|
93
|
-
|
94
51
|
# Returns a proc that applies the given arguments to the original proc. The
|
95
52
|
# returned proc calls the *compiled* form of the proc, merging the
|
96
53
|
# positional and keywords parameters passed to `#apply` with parameters
|
@@ -101,25 +58,25 @@ module Papercraft
|
|
101
58
|
# @param **kw1 [Hash<any, any] applied keyword parameters
|
102
59
|
# @return [Proc] applied proc
|
103
60
|
def apply(*pos1, **kw1, &block)
|
104
|
-
compiled =
|
105
|
-
c_compiled = block&.
|
61
|
+
compiled = __compiled_proc__
|
62
|
+
c_compiled = block&.__compiled_proc__
|
106
63
|
|
107
64
|
->(__buffer__, *pos2, **kw2, &block2) {
|
108
65
|
c_proc = c_compiled && ->(__buffer__, *pos3, **kw3) {
|
109
66
|
c_compiled.(__buffer__, *pos3, **kw3, &block2)
|
110
|
-
}.
|
67
|
+
}.__compiled__!
|
111
68
|
|
112
69
|
compiled.(__buffer__, *pos1, *pos2, **kw1, **kw2, &c_proc)
|
113
|
-
}.
|
70
|
+
}.__compiled__!
|
114
71
|
end
|
115
72
|
|
116
73
|
# Caches and returns the rendered HTML for the template with the given
|
117
74
|
# arguments.
|
118
75
|
#
|
76
|
+
# @param key [any] Cache key
|
119
77
|
# @return [String] HTML string
|
120
|
-
def
|
78
|
+
def render_cache(key, *args, **kargs, &block)
|
121
79
|
@render_cache ||= {}
|
122
|
-
key = args.empty? && kargs.empty? && !block ? nil : [args, kargs, block&.source_location]
|
123
80
|
@render_cache[key] ||= render(*args, **kargs, &block)
|
124
81
|
end
|
125
82
|
end
|
data/lib/papercraft/template.rb
CHANGED
data/lib/papercraft/version.rb
CHANGED
data/lib/papercraft.rb
CHANGED
@@ -153,4 +153,41 @@ module Papercraft
|
|
153
153
|
def default_kramdown_options=(opts)
|
154
154
|
@default_kramdown_options = opts
|
155
155
|
end
|
156
|
+
|
157
|
+
# Returns the compiled form code for the given proc.
|
158
|
+
#
|
159
|
+
# @param proc [Proc] template proc
|
160
|
+
# @return [String] compiled proc code
|
161
|
+
def compiled_code(proc)
|
162
|
+
Papercraft::Compiler.compile_to_code(proc).last
|
163
|
+
end
|
164
|
+
|
165
|
+
# Returns the source map for the given proc.
|
166
|
+
#
|
167
|
+
# @param proc [Proc] template proc
|
168
|
+
# @return [Array<String>] source map
|
169
|
+
def source_map(proc)
|
170
|
+
loc = proc.source_location
|
171
|
+
fn = proc.__compiled__? ? loc.first : Papercraft::Compiler.source_location_to_fn(loc)
|
172
|
+
Papercraft::Compiler.source_map_store[fn]
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns the AST for the given proc.
|
176
|
+
#
|
177
|
+
# @param proc [Proc] template proc
|
178
|
+
# @return [Prism::Node] AST root
|
179
|
+
def ast(proc)
|
180
|
+
Sirop.to_ast(proc)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Compiles the given template.
|
184
|
+
#
|
185
|
+
# @param proc [Proc] template proc
|
186
|
+
# @param mode [Symbol] compilation mode (:html, :xml)
|
187
|
+
# @return [Proc] compiled proc
|
188
|
+
def compile(proc, mode: :html)
|
189
|
+
Papercraft::Compiler.compile(proc, mode:).__compiled__!
|
190
|
+
rescue Sirop::Error
|
191
|
+
raise Papercraft::Error, "Can't compile eval'd template"
|
192
|
+
end
|
156
193
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: papercraft
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '2.
|
4
|
+
version: '2.24'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
@@ -142,8 +142,8 @@ homepage: http://github.com/digital-fabric/papercraft
|
|
142
142
|
licenses:
|
143
143
|
- MIT
|
144
144
|
metadata:
|
145
|
-
homepage_uri: https://
|
146
|
-
|
145
|
+
homepage_uri: https://papercraft.noteflakes.com/
|
146
|
+
source_code_uri: https://github.com/digital-fabric/papercraft
|
147
147
|
changelog_uri: https://github.com/digital-fabric/papercraft/blob/master/CHANGELOG.md
|
148
148
|
rdoc_options:
|
149
149
|
- "--title"
|