papercraft 2.23 → 3.0
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 +16 -0
- data/README.md +15 -587
- data/lib/papercraft/compiler.rb +33 -13
- data/lib/papercraft/proc_ext.rb +12 -97
- data/lib/papercraft/template.rb +19 -6
- data/lib/papercraft/version.rb +1 -1
- data/lib/papercraft.rb +127 -1
- 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: ac74fb8004af29f1b15e8ca36de8039d3e47c6642105032fa1a51ec46bda100a
|
|
4
|
+
data.tar.gz: 2b5fcf6bc6d472c33033381577b62e0875594a56e3a8f9f1d769b64d4a6ec2ce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 390c81c371d17216134e931b26e1d6c9728c54e2c8db220b0d21761f9ed7d21b12f784b5642e7473e55c1f65f2bad4fe07f9afa09d78c04b7d54569ed7cc5ebf
|
|
7
|
+
data.tar.gz: 9801c61a0a9788a563396bed4064b0b0f4aeacb82e43677c52bfc83ed1d25d7b001af4915c3bdb86b6191b1f5681bd1b54460fbaaa626bfde32e471cb12a830f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
# 3.0.0 2025-10-19
|
|
2
|
+
|
|
3
|
+
- Improve implementation of `Papercraft.apply`
|
|
4
|
+
- Add support for rendering self-closing XML tags
|
|
5
|
+
- Streamline Papercraft API
|
|
6
|
+
- Add support for `Papercraft.render { ... }`
|
|
7
|
+
- Prefix internal Proc extensions with `__papercraft_`
|
|
8
|
+
- Change API to use `Papercraft.html` instead of `Proc#render`. Same for
|
|
9
|
+
`apple`, `render_xml` etc.
|
|
10
|
+
|
|
11
|
+
# 2.24 2025-10-14
|
|
12
|
+
|
|
13
|
+
- Update gem links
|
|
14
|
+
- Simplify `render_cache`, caller must provide cache key
|
|
15
|
+
- Reduce surface area of Proc extensions
|
|
16
|
+
|
|
1
17
|
# 2.23 2025-10-12
|
|
2
18
|
|
|
3
19
|
- Update ERB to version 5.1.1
|
data/README.md
CHANGED
|
@@ -18,603 +18,31 @@
|
|
|
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
|
-
body { render_children **props }
|
|
24
|
+
Papercraft.html {
|
|
25
|
+
div {
|
|
26
|
+
h1 "Hello from Papercraft!"
|
|
34
27
|
}
|
|
35
28
|
}
|
|
36
|
-
|
|
37
|
-
p 'foo'
|
|
38
|
-
}
|
|
39
|
-
#=> "<html><head><title>Title</title></head><body><p>foo</p></body></html>"
|
|
29
|
+
#=> "<div><h1>Hello from Papercraft</h1></div>"
|
|
40
30
|
```
|
|
41
31
|
|
|
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.
|
|
32
|
+
Papercraft is a templating engine for dynamically producing HTML in Ruby apps.
|
|
33
|
+
Papercraft templates are expressed as Ruby procs, leading to easier debugging,
|
|
34
|
+
better protection against HTML injection attacks, and better code reuse.
|
|
45
35
|
|
|
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.
|
|
36
|
+
Papercraft templates can be composed in a variety of ways, facilitating the
|
|
37
|
+
usage of layout templates, and enabling a component-oriented approach to
|
|
38
|
+
building web interfaces of arbitrary complexity.
|
|
49
39
|
|
|
50
40
|
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
|
|
41
|
+
arguments, making the data flow easy to follow and understand. Papercraft also
|
|
42
|
+
lets developers create derivative templates using full or partial parameter
|
|
53
43
|
application.
|
|
54
44
|
|
|
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
|
-
```
|
|
45
|
+
## Documentation
|
|
193
46
|
|
|
194
|
-
|
|
195
|
-
|
|
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:
|
|
537
|
-
|
|
538
|
-
```ruby
|
|
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
|
-
```
|
|
47
|
+
For more information, please consult the [Papercraft
|
|
48
|
+
website](https://papercraft.noteflakes.com/).
|
data/lib/papercraft/compiler.rb
CHANGED
|
@@ -164,12 +164,23 @@ module Papercraft
|
|
|
164
164
|
# adjust_whitespace(node.location)
|
|
165
165
|
is_void = is_void_element?(tag)
|
|
166
166
|
is_raw_inner_text = is_raw_inner_text_element?(tag)
|
|
167
|
+
is_empty = !node.block && !node.inner_text
|
|
167
168
|
|
|
168
|
-
if is_void &&
|
|
169
|
+
if is_void && !is_empty
|
|
169
170
|
raise Papercraft::Error, "Void element #{tag} cannot contain child nodes or inner text"
|
|
170
171
|
end
|
|
171
172
|
|
|
172
|
-
|
|
173
|
+
if @mode == :xml && is_empty
|
|
174
|
+
emit_html(
|
|
175
|
+
node.tag_location,
|
|
176
|
+
format_xml_tag_self_closing(node.tag_location, tag, node.attributes)
|
|
177
|
+
)
|
|
178
|
+
return
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
emit_html(
|
|
182
|
+
node.tag_location, format_html_tag_open(node.tag_location, tag, node.attributes)
|
|
183
|
+
)
|
|
173
184
|
return if is_void
|
|
174
185
|
|
|
175
186
|
case node.block
|
|
@@ -178,7 +189,7 @@ module Papercraft
|
|
|
178
189
|
when Prism::BlockArgumentNode
|
|
179
190
|
flush_html_parts!
|
|
180
191
|
adjust_whitespace(node.block)
|
|
181
|
-
emit("; #{format_code(node.block.expression)}.
|
|
192
|
+
emit("; #{format_code(node.block.expression)}.__papercraft_compiled_proc.(__buffer__)")
|
|
182
193
|
end
|
|
183
194
|
|
|
184
195
|
if node.inner_text
|
|
@@ -213,7 +224,7 @@ module Papercraft
|
|
|
213
224
|
emit(format_code(node.call_node.receiver))
|
|
214
225
|
emit('::')
|
|
215
226
|
end
|
|
216
|
-
emit("#{node.call_node.name}.
|
|
227
|
+
emit("#{node.call_node.name}.__papercraft_compiled_proc.(__buffer__")
|
|
217
228
|
if node.call_node.arguments
|
|
218
229
|
emit(', ')
|
|
219
230
|
visit(node.call_node.arguments)
|
|
@@ -229,17 +240,17 @@ module Papercraft
|
|
|
229
240
|
args = node.call_node.arguments.arguments
|
|
230
241
|
first_arg = args.first
|
|
231
242
|
|
|
232
|
-
block_embed = node.block && "&(->(__buffer__) #{format_code(node.block)}.
|
|
243
|
+
block_embed = node.block && "&(->(__buffer__) #{format_code(node.block)}.__papercraft_compiled!)"
|
|
233
244
|
block_embed = ", #{block_embed}" if block_embed && node.call_node.arguments
|
|
234
245
|
|
|
235
246
|
flush_html_parts!
|
|
236
247
|
adjust_whitespace(node.location)
|
|
237
248
|
|
|
238
249
|
if args.length == 1
|
|
239
|
-
emit("; #{format_code(first_arg)}.
|
|
250
|
+
emit("; #{format_code(first_arg)}.__papercraft_compiled_proc.(__buffer__#{block_embed})")
|
|
240
251
|
else
|
|
241
252
|
args_code = format_code_comma_separated_nodes(args[1..])
|
|
242
|
-
emit("; #{format_code(first_arg)}.
|
|
253
|
+
emit("; #{format_code(first_arg)}.__papercraft_compiled_proc.(__buffer__, #{args_code}#{block_embed})")
|
|
243
254
|
end
|
|
244
255
|
end
|
|
245
256
|
|
|
@@ -336,7 +347,7 @@ module Papercraft
|
|
|
336
347
|
def visit_extension_tag_node(node)
|
|
337
348
|
flush_html_parts!
|
|
338
349
|
adjust_whitespace(node.location)
|
|
339
|
-
emit("; Papercraft::Extensions[#{node.tag.inspect}].
|
|
350
|
+
emit("; Papercraft::Extensions[#{node.tag.inspect}].__papercraft_compiled_proc.(__buffer__")
|
|
340
351
|
if node.call_node.arguments
|
|
341
352
|
emit(', ')
|
|
342
353
|
visit(node.call_node.arguments)
|
|
@@ -367,7 +378,7 @@ module Papercraft
|
|
|
367
378
|
end
|
|
368
379
|
block_params = block_params.empty? ? '' : ", #{block_params.join(', ')}"
|
|
369
380
|
|
|
370
|
-
emit(", &(proc { |__buffer__#{block_params}| #{block_body} }).
|
|
381
|
+
emit(", &(proc { |__buffer__#{block_params}| #{block_body} }).__papercraft_compiled!")
|
|
371
382
|
end
|
|
372
383
|
emit(")")
|
|
373
384
|
end
|
|
@@ -382,7 +393,7 @@ module Papercraft
|
|
|
382
393
|
guard = @render_yield_used ?
|
|
383
394
|
'' : "; raise(LocalJumpError, 'no block given (render_yield)') if !__block__"
|
|
384
395
|
@render_yield_used = true
|
|
385
|
-
emit("#{guard}; __block__.
|
|
396
|
+
emit("#{guard}; __block__.__papercraft_compiled_proc.(__buffer__")
|
|
386
397
|
if node.call_node.arguments
|
|
387
398
|
emit(', ')
|
|
388
399
|
visit(node.call_node.arguments)
|
|
@@ -398,7 +409,7 @@ module Papercraft
|
|
|
398
409
|
flush_html_parts!
|
|
399
410
|
adjust_whitespace(node.location)
|
|
400
411
|
@render_children_used = true
|
|
401
|
-
emit("; __block__&.
|
|
412
|
+
emit("; __block__&.__papercraft_compiled_proc&.(__buffer__")
|
|
402
413
|
if node.call_node.arguments
|
|
403
414
|
emit(', ')
|
|
404
415
|
visit(node.call_node.arguments)
|
|
@@ -410,7 +421,7 @@ module Papercraft
|
|
|
410
421
|
flush_html_parts!
|
|
411
422
|
adjust_whitespace(node.location)
|
|
412
423
|
|
|
413
|
-
emit("; #{node.call_node.receiver.name}.
|
|
424
|
+
emit("; #{node.call_node.receiver.name}.__papercraft_compiled_proc.(__buffer__")
|
|
414
425
|
if node.call_node.arguments
|
|
415
426
|
emit(', ')
|
|
416
427
|
visit(node.call_node.arguments)
|
|
@@ -418,7 +429,7 @@ module Papercraft
|
|
|
418
429
|
if node.call_node.block
|
|
419
430
|
emit(", &(->")
|
|
420
431
|
visit(node.call_node.block)
|
|
421
|
-
emit(").
|
|
432
|
+
emit(").__papercraft_compiled_proc")
|
|
422
433
|
end
|
|
423
434
|
emit(")")
|
|
424
435
|
end
|
|
@@ -489,6 +500,15 @@ module Papercraft
|
|
|
489
500
|
RAW_INNER_TEXT_TAGS.include?(tag.to_s)
|
|
490
501
|
end
|
|
491
502
|
|
|
503
|
+
def format_xml_tag_self_closing(loc, tag, attributes)
|
|
504
|
+
tag = convert_tag(tag)
|
|
505
|
+
if attributes && attributes&.elements.size > 0 || @@html_debug_attribute_injector
|
|
506
|
+
"<#{tag} #{format_html_attributes(loc, attributes)}/>"
|
|
507
|
+
else
|
|
508
|
+
"<#{tag}/>"
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
|
|
492
512
|
# Formats an open tag with optional attributes.
|
|
493
513
|
#
|
|
494
514
|
# @param loc [Prism::Location] tag location
|
data/lib/papercraft/proc_ext.rb
CHANGED
|
@@ -5,122 +5,37 @@ 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 __papercraft_compiled?
|
|
12
|
+
@__papercraft_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 __papercraft_compiled!
|
|
20
|
+
@__papercraft_compiled = true
|
|
44
21
|
self
|
|
45
22
|
end
|
|
46
23
|
|
|
47
|
-
# Returns the compiled proc for the
|
|
24
|
+
# Returns the compiled proc for the proc. If marked as compiled, returns
|
|
48
25
|
# self.
|
|
49
26
|
#
|
|
50
27
|
# @param mode [Symbol] compilation mode (:html, :xml)
|
|
51
28
|
# @return [Proc] compiled proc or self
|
|
52
|
-
def
|
|
53
|
-
@
|
|
54
|
-
|
|
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"
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Renders the proc to HTML with the given arguments.
|
|
67
|
-
#
|
|
68
|
-
# @return [String] HTML string
|
|
69
|
-
def render(*a, **b, &c)
|
|
70
|
-
compiled_proc.(+'', *a, **b, &c)
|
|
71
|
-
rescue Exception => e
|
|
72
|
-
e.is_a?(Papercraft::Error) ? raise : raise(Papercraft.translate_backtrace(e))
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Renders the proc to XML with the given arguments.
|
|
76
|
-
#
|
|
77
|
-
# @return [String] XML string
|
|
78
|
-
def render_xml(*a, **b, &c)
|
|
79
|
-
compiled_proc(mode: :xml).(+'', *a, **b, &c)
|
|
80
|
-
rescue Exception => e
|
|
81
|
-
e.is_a?(Papercraft::Error) ? raise : raise(Papercraft.translate_backtrace(e))
|
|
82
|
-
end
|
|
83
|
-
|
|
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
|
-
# Returns a proc that applies the given arguments to the original proc. The
|
|
95
|
-
# returned proc calls the *compiled* form of the proc, merging the
|
|
96
|
-
# positional and keywords parameters passed to `#apply` with parameters
|
|
97
|
-
# passed to the applied proc. If a block is given, it is wrapped in a proc
|
|
98
|
-
# that passed merged parameters to the block.
|
|
99
|
-
#
|
|
100
|
-
# @param *pos1 [Array<any>] applied positional parameters
|
|
101
|
-
# @param **kw1 [Hash<any, any] applied keyword parameters
|
|
102
|
-
# @return [Proc] applied proc
|
|
103
|
-
def apply(*pos1, **kw1, &block)
|
|
104
|
-
compiled = compiled_proc
|
|
105
|
-
c_compiled = block&.compiled_proc
|
|
106
|
-
|
|
107
|
-
->(__buffer__, *pos2, **kw2, &block2) {
|
|
108
|
-
c_proc = c_compiled && ->(__buffer__, *pos3, **kw3) {
|
|
109
|
-
c_compiled.(__buffer__, *pos3, **kw3, &block2)
|
|
110
|
-
}.compiled!
|
|
111
|
-
|
|
112
|
-
compiled.(__buffer__, *pos1, *pos2, **kw1, **kw2, &c_proc)
|
|
113
|
-
}.compiled!
|
|
29
|
+
def __papercraft_compiled_proc(mode: :html)
|
|
30
|
+
@__papercraft_compiled_proc ||= @__papercraft_compiled ?
|
|
31
|
+
self : Papercraft.compile(self, mode:)
|
|
114
32
|
end
|
|
115
33
|
|
|
116
|
-
#
|
|
117
|
-
# arguments.
|
|
34
|
+
# Returns the render cache for the proc.
|
|
118
35
|
#
|
|
119
|
-
# @return [
|
|
120
|
-
def
|
|
121
|
-
@
|
|
122
|
-
key = args.empty? && kargs.empty? && !block ? nil : [args, kargs, block&.source_location]
|
|
123
|
-
@render_cache[key] ||= render(*args, **kargs, &block)
|
|
36
|
+
# @return [Hash] cache hash
|
|
37
|
+
def __papercraft_render_cache
|
|
38
|
+
@__papercraft_render_cache ||= {}
|
|
124
39
|
end
|
|
125
40
|
end
|
|
126
41
|
end
|
data/lib/papercraft/template.rb
CHANGED
|
@@ -8,21 +8,34 @@ module Papercraft
|
|
|
8
8
|
|
|
9
9
|
# @param proc [Proc] template proc
|
|
10
10
|
# @param mode [Symbol] mode (:html, :xml)
|
|
11
|
-
def initialize(proc, mode: :html)
|
|
12
|
-
@proc = proc
|
|
11
|
+
def initialize(proc = nil, mode: :html, &block)
|
|
12
|
+
@proc = proc || block
|
|
13
|
+
raise ArgumentError, "No template proc given" if !@proc
|
|
14
|
+
|
|
13
15
|
@mode = mode
|
|
14
16
|
end
|
|
15
17
|
|
|
18
|
+
# Renders the template.
|
|
19
|
+
#
|
|
20
|
+
# @return [String] generated HTML
|
|
16
21
|
def render(*, **, &)
|
|
17
|
-
(mode == :xml) ? @proc
|
|
22
|
+
(mode == :xml) ? Papercraft.xml(@proc, *, **, &) : Papercraft.html(@proc, *, **, &)
|
|
18
23
|
end
|
|
24
|
+
alias_method :call, :render
|
|
19
25
|
|
|
26
|
+
# Applies the given parameters and block to the template, returning an
|
|
27
|
+
# applied template.
|
|
28
|
+
#
|
|
29
|
+
# @return [Papercraft::Template] applied template
|
|
20
30
|
def apply(*, **, &)
|
|
21
|
-
Template.new(
|
|
31
|
+
Template.new(Papercraft.apply(@proc, *, **, &), mode: @mode)
|
|
22
32
|
end
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
# Returns the compiled proc for the template.
|
|
35
|
+
#
|
|
36
|
+
# @return [Proc] compiled proc
|
|
37
|
+
def __papercraft_compiled_proc
|
|
38
|
+
@proc.__papercraft_compiled_proc(mode: @mode)
|
|
26
39
|
end
|
|
27
40
|
end
|
|
28
41
|
end
|
data/lib/papercraft/version.rb
CHANGED
data/lib/papercraft.rb
CHANGED
|
@@ -114,10 +114,11 @@ module Papercraft
|
|
|
114
114
|
# @param opts [Hash] Kramdown option overrides
|
|
115
115
|
# @return [Kramdown::Document] Kramdown document
|
|
116
116
|
def markdown_doc(markdown, **opts)
|
|
117
|
-
@markdown_deps_loaded ||=
|
|
117
|
+
@markdown_deps_loaded ||= begin
|
|
118
118
|
require 'kramdown'
|
|
119
119
|
require 'rouge'
|
|
120
120
|
require 'kramdown-parser-gfm'
|
|
121
|
+
true
|
|
121
122
|
end
|
|
122
123
|
|
|
123
124
|
opts = default_kramdown_options.merge(opts)
|
|
@@ -153,4 +154,129 @@ module Papercraft
|
|
|
153
154
|
def default_kramdown_options=(opts)
|
|
154
155
|
@default_kramdown_options = opts
|
|
155
156
|
end
|
|
157
|
+
|
|
158
|
+
# Returns the compiled form code for the given proc.
|
|
159
|
+
#
|
|
160
|
+
# @param proc [Proc] template proc
|
|
161
|
+
# @return [String] compiled proc code
|
|
162
|
+
def compiled_code(proc)
|
|
163
|
+
Papercraft::Compiler.compile_to_code(proc).last
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns the source map for the given proc.
|
|
167
|
+
#
|
|
168
|
+
# @param proc [Proc] template proc
|
|
169
|
+
# @return [Array<String>] source map
|
|
170
|
+
def source_map(proc)
|
|
171
|
+
loc = proc.source_location
|
|
172
|
+
fn = proc.__papercraft_compiled? ? loc.first : Papercraft::Compiler.source_location_to_fn(loc)
|
|
173
|
+
Papercraft::Compiler.source_map_store[fn]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Returns the AST for the given proc.
|
|
177
|
+
#
|
|
178
|
+
# @param proc [Proc] template proc
|
|
179
|
+
# @return [Prism::Node] AST root
|
|
180
|
+
def ast(proc)
|
|
181
|
+
Sirop.to_ast(proc)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Compiles the given template.
|
|
185
|
+
#
|
|
186
|
+
# @param proc [Proc] template proc
|
|
187
|
+
# @param mode [Symbol] compilation mode (:html, :xml)
|
|
188
|
+
# @return [Proc] compiled proc
|
|
189
|
+
def compile(proc, mode: :html)
|
|
190
|
+
Papercraft::Compiler.compile(proc, mode:).__papercraft_compiled!
|
|
191
|
+
rescue Sirop::Error
|
|
192
|
+
raise Papercraft::Error, "Can't compile eval'd template"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Renders the given template to HTML with the given arguments. The template
|
|
196
|
+
# can be passed either as the first parameter, or as a block, if no parameter
|
|
197
|
+
# is given.
|
|
198
|
+
#
|
|
199
|
+
# @param template [Proc] template proc
|
|
200
|
+
# @return [String] HTML string
|
|
201
|
+
def html(template = nil, *pos, **kw, &block)
|
|
202
|
+
if !template
|
|
203
|
+
template = block
|
|
204
|
+
elsif template.is_a?(Template)
|
|
205
|
+
template = template.proc
|
|
206
|
+
end
|
|
207
|
+
raise ArgumentError, "No template given" if !template
|
|
208
|
+
|
|
209
|
+
template.__papercraft_compiled_proc.(+'', *pos, **kw, &block)
|
|
210
|
+
rescue Exception => e
|
|
211
|
+
e.is_a?(Papercraft::Error) ? raise : raise(Papercraft.translate_backtrace(e))
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Renders the given template to XML with the given arguments. The template can
|
|
215
|
+
# be passed either as the first parameter, or as a block, if no parameter is
|
|
216
|
+
# given.
|
|
217
|
+
#
|
|
218
|
+
# @param template [Proc] template proc
|
|
219
|
+
# @return [String] XML string
|
|
220
|
+
def xml(template = nil, *pos, **kw, &block)
|
|
221
|
+
if !template
|
|
222
|
+
template = block
|
|
223
|
+
elsif template.is_a?(Template)
|
|
224
|
+
template = template.proc
|
|
225
|
+
end
|
|
226
|
+
raise ArgumentError, "No template given" if !template
|
|
227
|
+
|
|
228
|
+
template = template.proc if template.is_a?(Template)
|
|
229
|
+
template.__papercraft_compiled_proc(mode: :xml).(+'', *pos, **kw, &block)
|
|
230
|
+
rescue Exception => e
|
|
231
|
+
e.is_a?(Papercraft::Error) ? raise : raise(Papercraft.translate_backtrace(e))
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Returns a proc that applies the given arguments to the original proc. The
|
|
235
|
+
# returned proc calls the *compiled* form of the proc, merging the
|
|
236
|
+
# positional and keywords parameters passed to `#apply` with parameters
|
|
237
|
+
# passed to the applied proc. If a block is given, it is wrapped in a proc
|
|
238
|
+
# that passed merged parameters to the block.
|
|
239
|
+
#
|
|
240
|
+
# @param template [Proc] template proc
|
|
241
|
+
# @param *pos1 [Array<any>] applied positional parameters
|
|
242
|
+
# @param **kw1 [Hash<any, any] applied keyword parameters
|
|
243
|
+
# @return [Proc] applied proc
|
|
244
|
+
def apply(template, *pos1, **kw1, &block1)
|
|
245
|
+
template = template.proc if template.is_a?(Template)
|
|
246
|
+
compiled = template.__papercraft_compiled_proc
|
|
247
|
+
block1_compiled = block1&.__papercraft_compiled_proc
|
|
248
|
+
|
|
249
|
+
->(__buffer__, *pos2, **kw2, &block2) {
|
|
250
|
+
if block2
|
|
251
|
+
block2_compiled = block1_compiled ?
|
|
252
|
+
->(__buffer__, *pos3, **kw3) {
|
|
253
|
+
block1_compiled.(__buffer__, *pos3, **kw3, &block2)
|
|
254
|
+
}.__papercraft_compiled! :
|
|
255
|
+
block2.__papercraft_compiled_proc
|
|
256
|
+
compiled.(__buffer__, *pos1, *pos2, **kw1, **kw2, &block2_compiled)
|
|
257
|
+
else
|
|
258
|
+
compiled.(__buffer__, *pos1, *pos2, **kw1, **kw2, &block1_compiled)
|
|
259
|
+
end
|
|
260
|
+
}.__papercraft_compiled!
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Caches and returns the rendered HTML for the template with the given
|
|
264
|
+
# arguments.
|
|
265
|
+
#
|
|
266
|
+
# @param template [Proc] template proc
|
|
267
|
+
# @param key [any] Cache key
|
|
268
|
+
# @return [String] HTML string
|
|
269
|
+
def cache_html(template, key, *, **, &)
|
|
270
|
+
template.__papercraft_render_cache[key] ||= html(template, *, **, &)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Caches and returns the rendered XML for the template with the given
|
|
274
|
+
# arguments.
|
|
275
|
+
#
|
|
276
|
+
# @param template [Proc] template proc
|
|
277
|
+
# @param key [any] Cache key
|
|
278
|
+
# @return [String] XML string
|
|
279
|
+
def cache_xml(template, key, *, **, &)
|
|
280
|
+
template.__papercraft_render_cache[key] ||= xml(template, *, **, &)
|
|
281
|
+
end
|
|
156
282
|
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: '
|
|
4
|
+
version: '3.0'
|
|
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"
|