papercraft 0.12 → 0.16
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 +20 -1
- data/README.md +68 -33
- data/lib/papercraft/component.rb +35 -26
- data/lib/papercraft/json.rb +73 -0
- data/lib/papercraft/renderer.rb +23 -17
- data/lib/papercraft/version.rb +1 -1
- data/lib/papercraft.rb +29 -16
- metadata +19 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 996d95088c85415ca883c27b6b1c98e65af5c046e3121ea08f092ac7bcdcc588
|
|
4
|
+
data.tar.gz: 8fe294225c04dcfc82289fd2b9047760260b814712e83e9bf5ea7d361dad6950
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 54fdbe164a4a2541153e57b90cdb35ba71b6887fbe2d199505da3389730f1bd937fe0254f8c019b39d81957058c5f51ad71bc900f324e3178b979f368642ee57
|
|
7
|
+
data.tar.gz: fa05ea9ae79a57a3eace2e708b01fe5f44412591875776cf1a2fe95cc6d9d1586357756e702e8a17e5458b3fa4c3e3224290bc234b71fdcd426db0dedff31dc0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## 0.16 2022-01-23
|
|
2
|
+
|
|
3
|
+
- Implement JSON templating (#7)
|
|
4
|
+
- Add support for MIME types (#6)
|
|
5
|
+
- Change entrypoint from `Kernel#H`, `Kernel#X` to `Papercraft.html`, `.xml` (#5)
|
|
6
|
+
|
|
7
|
+
## 0.15 2022-01-20
|
|
8
|
+
|
|
9
|
+
- Fix tag method line reference
|
|
10
|
+
- Don't clobber ArgumentError exception
|
|
11
|
+
|
|
12
|
+
## 0.14 2022-01-19
|
|
13
|
+
|
|
14
|
+
- Add support for #emit_yield in applied component (#4)
|
|
15
|
+
|
|
16
|
+
## 0.13 2022-01-19
|
|
17
|
+
|
|
18
|
+
- Add support for partial parameter application (#3)
|
|
19
|
+
|
|
1
20
|
## 0.12 2022-01-06
|
|
2
21
|
|
|
3
22
|
- Improve documentation
|
|
@@ -29,7 +48,7 @@
|
|
|
29
48
|
## 0.8 2021-12-22
|
|
30
49
|
|
|
31
50
|
- Cleanup and refactor code
|
|
32
|
-
- Add
|
|
51
|
+
- Add Papercraft.xml global method for XML templates
|
|
33
52
|
- Make `Component` a descendant of `Proc`
|
|
34
53
|
- Introduce new component API
|
|
35
54
|
- Rename Rubyoshka to Papercraft
|
data/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<h1 align="center">
|
|
2
|
+
<img src="papercraft.png">
|
|
3
|
+
<br>
|
|
2
4
|
Papercraft
|
|
3
5
|
</h1>
|
|
4
6
|
|
|
5
|
-
<h4 align="center">Composable
|
|
7
|
+
<h4 align="center">Composable templating for Ruby</h4>
|
|
6
8
|
|
|
7
9
|
<p align="center">
|
|
8
10
|
<a href="http://rubygems.org/gems/papercraft">
|
|
@@ -25,26 +27,28 @@
|
|
|
25
27
|
```ruby
|
|
26
28
|
require 'papercraft'
|
|
27
29
|
|
|
28
|
-
page =
|
|
30
|
+
page = Papercraft.html { |*args|
|
|
29
31
|
html {
|
|
30
32
|
head { }
|
|
31
33
|
body { emit_yield *args }
|
|
32
34
|
}
|
|
33
35
|
}
|
|
36
|
+
page.render { p 'foo' }
|
|
37
|
+
#=> "<html><head/><body><p>foo</p></body></html>"
|
|
34
38
|
|
|
35
|
-
hello =
|
|
39
|
+
hello = page.apply { |name| h1 "Hello, #{name}!" }
|
|
36
40
|
hello.render('world')
|
|
37
41
|
#=> "<html><head/><body><h1>Hello, world!</h1></body></html>"
|
|
38
42
|
```
|
|
39
43
|
|
|
40
|
-
Papercraft is
|
|
41
|
-
features:
|
|
44
|
+
Papercraft is a templating engine for Ruby that offers the following features:
|
|
42
45
|
|
|
43
|
-
- HTML and
|
|
46
|
+
- HTML, XML and JSON templating using plain Ruby syntax
|
|
44
47
|
- Minimal boilerplate
|
|
45
48
|
- Mix logic and tags freely
|
|
46
49
|
- Automatic HTML and XML escaping
|
|
47
50
|
- Composable components
|
|
51
|
+
- Standard or custom MIME types
|
|
48
52
|
- Explicit parameter passing to nested components
|
|
49
53
|
- Higher order components
|
|
50
54
|
- Built-in support for rendering [Markdown](#emitting-markdown)
|
|
@@ -71,7 +75,7 @@ To create a template use the global method `Kernel#H`:
|
|
|
71
75
|
```ruby
|
|
72
76
|
require 'papercraft'
|
|
73
77
|
|
|
74
|
-
html =
|
|
78
|
+
html = Papercraft.html {
|
|
75
79
|
div(id: 'greeter') { p 'Hello!' }
|
|
76
80
|
}
|
|
77
81
|
```
|
|
@@ -87,7 +91,7 @@ html.render #=> "<div id="greeter"><p>Hello!</p></div>"
|
|
|
87
91
|
Tags are added using unqualified method calls, and can be nested using blocks:
|
|
88
92
|
|
|
89
93
|
```ruby
|
|
90
|
-
|
|
94
|
+
Papercraft.html {
|
|
91
95
|
html {
|
|
92
96
|
head {
|
|
93
97
|
title 'page title'
|
|
@@ -104,19 +108,19 @@ H {
|
|
|
104
108
|
Tag methods accept a string argument, a block, or no argument at all:
|
|
105
109
|
|
|
106
110
|
```ruby
|
|
107
|
-
|
|
111
|
+
Papercraft.html { p 'hello' }.render #=> "<p>hello</p>"
|
|
108
112
|
|
|
109
|
-
|
|
113
|
+
Papercraft.html { p { span '1'; span '2' } }.render #=> "<p><span>1</span><span>2</span></p>"
|
|
110
114
|
|
|
111
|
-
|
|
115
|
+
Papercraft.html { hr() }.render #=> "<hr/>"
|
|
112
116
|
```
|
|
113
117
|
|
|
114
118
|
Tag methods also accept tag attributes, given as a hash:
|
|
115
119
|
|
|
116
120
|
```ruby
|
|
117
|
-
|
|
121
|
+
Papercraft.html { img src: '/my.gif' }.render #=> "<img src="/my.gif"/>
|
|
118
122
|
|
|
119
|
-
|
|
123
|
+
Papercraft.html { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
|
|
120
124
|
```
|
|
121
125
|
|
|
122
126
|
## Template parameters
|
|
@@ -126,14 +130,14 @@ parameters are specified as block parameters, and are passed to the template on
|
|
|
126
130
|
rendering:
|
|
127
131
|
|
|
128
132
|
```ruby
|
|
129
|
-
greeting =
|
|
133
|
+
greeting = Papercraft.html { |name| h1 "Hello, #{name}!" }
|
|
130
134
|
greeting.render('world') #=> "<h1>Hello, world!</h1>"
|
|
131
135
|
```
|
|
132
136
|
|
|
133
137
|
Templates can also accept named parameters:
|
|
134
138
|
|
|
135
139
|
```ruby
|
|
136
|
-
greeting =
|
|
140
|
+
greeting = Papercraft.html { |name:| h1 "Hello, #{name}!" }
|
|
137
141
|
greeting.render(name: 'world') #=> "<h1>Hello, world!</h1>"
|
|
138
142
|
```
|
|
139
143
|
|
|
@@ -143,7 +147,7 @@ Since Papercraft templates are just a bunch of Ruby, you can easily write your
|
|
|
143
147
|
view logic right in the template:
|
|
144
148
|
|
|
145
149
|
```ruby
|
|
146
|
-
|
|
150
|
+
Papercraft.html { |user = nil|
|
|
147
151
|
if user
|
|
148
152
|
span "Hello, #{user.name}!"
|
|
149
153
|
else
|
|
@@ -157,7 +161,7 @@ H { |user = nil|
|
|
|
157
161
|
Templates can also accept and render blocks by using `emit_yield`:
|
|
158
162
|
|
|
159
163
|
```ruby
|
|
160
|
-
page =
|
|
164
|
+
page = Papercraft.html {
|
|
161
165
|
html {
|
|
162
166
|
body { emit_yield }
|
|
163
167
|
}
|
|
@@ -174,14 +178,14 @@ it by passing it as a block to `H`:
|
|
|
174
178
|
|
|
175
179
|
```ruby
|
|
176
180
|
greeting = proc { |name| h1 "Hello, #{name}!" }
|
|
177
|
-
|
|
181
|
+
Papercraft.html(&greeting).render('world')
|
|
178
182
|
```
|
|
179
183
|
|
|
180
184
|
Components can also be expressed using lambda notation:
|
|
181
185
|
|
|
182
186
|
```ruby
|
|
183
187
|
greeting = ->(name) { h1 "Hello, #{name}!" }
|
|
184
|
-
|
|
188
|
+
Papercraft.html(&greeting).render('world')
|
|
185
189
|
```
|
|
186
190
|
|
|
187
191
|
## Component composition
|
|
@@ -208,7 +212,7 @@ ItemList = ->(items) {
|
|
|
208
212
|
}
|
|
209
213
|
}
|
|
210
214
|
|
|
211
|
-
page =
|
|
215
|
+
page = Papercraft.html { |title, items|
|
|
212
216
|
html5 {
|
|
213
217
|
head { Title(title) }
|
|
214
218
|
body { ItemList(items) }
|
|
@@ -227,7 +231,7 @@ non-constant components by invoking the `#emit` method:
|
|
|
227
231
|
```ruby
|
|
228
232
|
greeting = -> { span "Hello, world" }
|
|
229
233
|
|
|
230
|
-
|
|
234
|
+
Papercraft.html {
|
|
231
235
|
div {
|
|
232
236
|
emit greeting
|
|
233
237
|
}
|
|
@@ -245,12 +249,12 @@ or block to the original component:
|
|
|
245
249
|
|
|
246
250
|
```ruby
|
|
247
251
|
# parameter application
|
|
248
|
-
hello =
|
|
252
|
+
hello = Papercraft.html { |name| h1 "Hello, #{name}!" }
|
|
249
253
|
hello_world = hello.apply('world')
|
|
250
254
|
hello_world.render #=> "<h1>Hello, world!</h1>"
|
|
251
255
|
|
|
252
256
|
# block application
|
|
253
|
-
div_wrap =
|
|
257
|
+
div_wrap = Papercraft.html { div { emit_yield } }
|
|
254
258
|
wrapped_h1 = div_wrap.apply { h1 'hi' }
|
|
255
259
|
wrapped_h1.render #=> "<div><h1>hi</h1></div>"
|
|
256
260
|
|
|
@@ -269,8 +273,8 @@ markup, enhancing components or injecting component parameters.
|
|
|
269
273
|
Here is a HOC that takes a component as parameter:
|
|
270
274
|
|
|
271
275
|
```ruby
|
|
272
|
-
div_wrap =
|
|
273
|
-
greeter =
|
|
276
|
+
div_wrap = Papercraft.html { |inner| div { emit inner } }
|
|
277
|
+
greeter = Papercraft.html { h1 'hi' }
|
|
274
278
|
wrapped_greeter = div_wrap.apply(greeter)
|
|
275
279
|
wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
|
276
280
|
```
|
|
@@ -278,7 +282,7 @@ wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
|
|
278
282
|
The inner component can also be passed as a block, as shown above:
|
|
279
283
|
|
|
280
284
|
```ruby
|
|
281
|
-
div_wrap =
|
|
285
|
+
div_wrap = Papercraft.html { div { emit_yield } }
|
|
282
286
|
wrapped_greeter = div_wrap.apply { h1 'hi' }
|
|
283
287
|
wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
|
284
288
|
```
|
|
@@ -292,7 +296,7 @@ this by creating a `default` page template that takes a block, then use `#apply`
|
|
|
292
296
|
to create the other templates:
|
|
293
297
|
|
|
294
298
|
```ruby
|
|
295
|
-
default_layout =
|
|
299
|
+
default_layout = Papercraft.html { |**params|
|
|
296
300
|
html5 {
|
|
297
301
|
head {
|
|
298
302
|
title: params[:title]
|
|
@@ -321,7 +325,7 @@ article_layout.render(
|
|
|
321
325
|
Raw HTML can be emitted using `#emit`:
|
|
322
326
|
|
|
323
327
|
```ruby
|
|
324
|
-
wrapped =
|
|
328
|
+
wrapped = Papercraft.html { |html| div { emit html } }
|
|
325
329
|
wrapped.render("<h1>hi</h1>") #=> "<div><h1>hi</h1></div>"
|
|
326
330
|
```
|
|
327
331
|
|
|
@@ -331,7 +335,7 @@ To emit a string with proper HTML encoding, without wrapping it in an HTML
|
|
|
331
335
|
element, use `#text`:
|
|
332
336
|
|
|
333
337
|
```ruby
|
|
334
|
-
|
|
338
|
+
Papercraft.html { str 'hi&lo' }.render #=> "hi&lo"
|
|
335
339
|
```
|
|
336
340
|
|
|
337
341
|
## Emitting Markdown
|
|
@@ -341,7 +345,7 @@ Markdown is rendered using the
|
|
|
341
345
|
`#emit_markdown`:
|
|
342
346
|
|
|
343
347
|
```ruby
|
|
344
|
-
template =
|
|
348
|
+
template = Papercraft.html { |md| div { emit_markdown md } }
|
|
345
349
|
template.render("Here's some *Markdown*") #=> "<div><p>Here's some <em>Markdown</em><p>\n</div>"
|
|
346
350
|
```
|
|
347
351
|
|
|
@@ -350,7 +354,7 @@ options](https://kramdown.gettalong.org/options.html#available-options) can be
|
|
|
350
354
|
specified by adding them to the `#emit_markdown` call:
|
|
351
355
|
|
|
352
356
|
```ruby
|
|
353
|
-
template =
|
|
357
|
+
template = Papercraft.html { |md| div { emit_markdown md, auto_ids: false } }
|
|
354
358
|
template.render("# title") #=> "<div><h1>title</h1></div>"
|
|
355
359
|
```
|
|
356
360
|
|
|
@@ -393,7 +397,7 @@ class that can collect JS and CSS dependencies from the different components
|
|
|
393
397
|
integrated into the page, and adds them to the page's `<head>` element:
|
|
394
398
|
|
|
395
399
|
```ruby
|
|
396
|
-
default_layout =
|
|
400
|
+
default_layout = Papercraft.html { |**args|
|
|
397
401
|
@dependencies = DependencyMananger.new
|
|
398
402
|
head {
|
|
399
403
|
defer { emit @dependencies.head_markup }
|
|
@@ -479,7 +483,7 @@ The call to `Papercraft::extension` lets us access the different methods of
|
|
|
479
483
|
we'll be able to express the above markup as follows:
|
|
480
484
|
|
|
481
485
|
```ruby
|
|
482
|
-
|
|
486
|
+
Papercraft.html {
|
|
483
487
|
bootstrap.card(style: 'width: 18rem') {
|
|
484
488
|
bootstrap.card_title 'Card title'
|
|
485
489
|
bootstrap.card_subtitle 'Card subtitle'
|
|
@@ -490,6 +494,37 @@ H {
|
|
|
490
494
|
}
|
|
491
495
|
```
|
|
492
496
|
|
|
497
|
+
## JSON templating
|
|
498
|
+
|
|
499
|
+
You can create a JSON template using the same API used for HTML and XML
|
|
500
|
+
templating. The only difference is that for adding array items you'll need to
|
|
501
|
+
use the `#item` method:
|
|
502
|
+
|
|
503
|
+
```ruby
|
|
504
|
+
Papercraft.json {
|
|
505
|
+
item 1
|
|
506
|
+
item 2
|
|
507
|
+
item 3
|
|
508
|
+
}.render #=> "[1,2,3]"
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Otherwise, you can create arbitrarily complex JSON structures by mixing hashes
|
|
512
|
+
and arrays:
|
|
513
|
+
|
|
514
|
+
```Ruby
|
|
515
|
+
Papercraft.json {
|
|
516
|
+
foo {
|
|
517
|
+
bar {
|
|
518
|
+
item nil
|
|
519
|
+
item true
|
|
520
|
+
item 123.456
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}.render #=> "{\"foo\":{\"bar\":[null,true,123.456]}}"
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
Papercraft uses the [JSON gem](https://rubyapi.org/3.1/o/json) under the hood.
|
|
527
|
+
|
|
493
528
|
## API Reference
|
|
494
529
|
|
|
495
530
|
The API reference for this library can be found
|
data/lib/papercraft/component.rb
CHANGED
|
@@ -11,21 +11,26 @@ module Papercraft
|
|
|
11
11
|
# class is simply a special kind of Proc, which has some enhanced
|
|
12
12
|
# capabilities, allowing it to be easily composed in a variety of ways.
|
|
13
13
|
#
|
|
14
|
-
# Components are usually created using the
|
|
15
|
-
#
|
|
14
|
+
# Components are usually created using the class methods `html`, `xml` or
|
|
15
|
+
# `json`, for HTML, XML or JSON templates, respectively:
|
|
16
16
|
#
|
|
17
|
-
# greeter =
|
|
17
|
+
# greeter = Papercraft.html { |name| h1 "Hello, #{name}!" }
|
|
18
18
|
# greeter.render('world') #=> "<h1>Hello, world!</h1>"
|
|
19
19
|
#
|
|
20
20
|
# Components can also be created using the normal constructor:
|
|
21
21
|
#
|
|
22
|
-
# greeter = Papercraft::Component.new { |name| h1 "Hello, #{name}!" }
|
|
22
|
+
# greeter = Papercraft::Component.new(mode: :html) { |name| h1 "Hello, #{name}!" }
|
|
23
23
|
# greeter.render('world') #=> "<h1>Hello, world!</h1>"
|
|
24
24
|
#
|
|
25
|
+
# The different methods for creating components can also take a custom MIME
|
|
26
|
+
# type, by passing a `mime_type` named argument:
|
|
27
|
+
#
|
|
28
|
+
# json = Papercraft.json(mime_type: 'application/feed+json') { ... }
|
|
29
|
+
#
|
|
25
30
|
# In the component block, HTML elements are created by simply calling
|
|
26
31
|
# unqualified methods:
|
|
27
32
|
#
|
|
28
|
-
# page_layout =
|
|
33
|
+
# page_layout = Papercraft.html {
|
|
29
34
|
# html5 {
|
|
30
35
|
# head {
|
|
31
36
|
# title 'foo'
|
|
@@ -41,7 +46,7 @@ module Papercraft
|
|
|
41
46
|
# `greeter` template shown above takes a single `name` parameter. Here's how a
|
|
42
47
|
# anchor component could be implemented with named parameters:
|
|
43
48
|
#
|
|
44
|
-
# anchor =
|
|
49
|
+
# anchor = Papercraft.html { |uri: , text: | a(text, href: uri) }
|
|
45
50
|
#
|
|
46
51
|
# The above component could later be rendered by passing the needed arguments:
|
|
47
52
|
#
|
|
@@ -51,7 +56,7 @@ module Papercraft
|
|
|
51
56
|
#
|
|
52
57
|
# A component can be included in another component using the `emit` method:
|
|
53
58
|
#
|
|
54
|
-
# links =
|
|
59
|
+
# links = Papercraft.html {
|
|
55
60
|
# emit anchor, uri: '/posts', text: 'Posts'
|
|
56
61
|
# emit anchor, uri: '/archive', text: 'Archive'
|
|
57
62
|
# emit anchor, uri: '/about', text: 'About'
|
|
@@ -60,7 +65,7 @@ module Papercraft
|
|
|
60
65
|
# Another way of composing components is to pass the components themselves as
|
|
61
66
|
# parameters:
|
|
62
67
|
#
|
|
63
|
-
# links =
|
|
68
|
+
# links = Papercraft.html { |anchors|
|
|
64
69
|
# anchors.each { |a| emit a }
|
|
65
70
|
# }
|
|
66
71
|
# links.render([
|
|
@@ -84,14 +89,22 @@ module Papercraft
|
|
|
84
89
|
# Determines the rendering mode: `:html` or `:xml`.
|
|
85
90
|
attr_accessor :mode
|
|
86
91
|
|
|
92
|
+
STOCK_MIME_TYPE = {
|
|
93
|
+
html: 'text/html',
|
|
94
|
+
xml: 'application/xml',
|
|
95
|
+
json: 'application/json'
|
|
96
|
+
}.freeze
|
|
97
|
+
|
|
87
98
|
# Initializes a component with the given block. The rendering mode (HTML or
|
|
88
99
|
# XML) can be passed in the `mode:` parameter. If `mode:` is not specified,
|
|
89
100
|
# the component defaults to HTML.
|
|
90
101
|
#
|
|
91
102
|
# @param mode [:html, :xml] rendering mode
|
|
103
|
+
# @param mime_type [String, nil] the component's mime type (nil for default)
|
|
92
104
|
# @param block [Proc] nested HTML block
|
|
93
|
-
def initialize(mode: :html, &block)
|
|
105
|
+
def initialize(mode: :html, mime_type: nil, &block)
|
|
94
106
|
@mode = mode
|
|
107
|
+
@mime_type = mime_type || STOCK_MIME_TYPE[mode]
|
|
95
108
|
super(&block)
|
|
96
109
|
end
|
|
97
110
|
|
|
@@ -106,21 +119,16 @@ module Papercraft
|
|
|
106
119
|
template = self
|
|
107
120
|
Renderer.verify_proc_parameters(template, a, b)
|
|
108
121
|
renderer_class.new do
|
|
109
|
-
if block
|
|
110
|
-
|
|
111
|
-
else
|
|
112
|
-
instance_exec(*a, **b, &template)
|
|
113
|
-
end
|
|
122
|
+
push_emit_yield_block(block) if block
|
|
123
|
+
instance_exec(*a, **b, &template)
|
|
114
124
|
end.to_s
|
|
115
|
-
rescue ArgumentError => e
|
|
116
|
-
raise Papercraft::Error, e.message
|
|
117
125
|
end
|
|
118
126
|
|
|
119
127
|
# Creates a new component, applying the given parameters and or block to the
|
|
120
128
|
# current one. Application is one of the principal methods of composing
|
|
121
129
|
# components, particularly when passing inner components as blocks:
|
|
122
130
|
#
|
|
123
|
-
# article_wrapper =
|
|
131
|
+
# article_wrapper = Papercraft.html {
|
|
124
132
|
# article {
|
|
125
133
|
# emit_yield
|
|
126
134
|
# }
|
|
@@ -136,15 +144,10 @@ module Papercraft
|
|
|
136
144
|
# @return [Papercraft::Component] applied component
|
|
137
145
|
def apply(*a, **b, &block)
|
|
138
146
|
template = self
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
else
|
|
144
|
-
Component.new(&proc do |*x, **y|
|
|
145
|
-
instance_exec(*a, **b, &template)
|
|
146
|
-
end)
|
|
147
|
-
end
|
|
147
|
+
Component.new(mode: @mode, mime_type: @mime_type, &proc do |*x, **y|
|
|
148
|
+
push_emit_yield_block(block) if block
|
|
149
|
+
instance_exec(*a, *x, **b, **y, &template)
|
|
150
|
+
end)
|
|
148
151
|
end
|
|
149
152
|
|
|
150
153
|
# Returns the Renderer class used for rendering the templates, according to
|
|
@@ -157,10 +160,16 @@ module Papercraft
|
|
|
157
160
|
HTMLRenderer
|
|
158
161
|
when :xml
|
|
159
162
|
XMLRenderer
|
|
163
|
+
when :json
|
|
164
|
+
JSONRenderer
|
|
160
165
|
else
|
|
161
166
|
raise "Invalid mode #{@mode.inspect}"
|
|
162
167
|
end
|
|
163
168
|
end
|
|
169
|
+
|
|
170
|
+
def mime_type
|
|
171
|
+
@mime_type
|
|
172
|
+
end
|
|
164
173
|
|
|
165
174
|
# def compile
|
|
166
175
|
# Papercraft::Compiler.new.compile(self)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Papercraft
|
|
6
|
+
# JSON renderer extensions
|
|
7
|
+
module JSON
|
|
8
|
+
def object_stack
|
|
9
|
+
@object_stack ||= [nil]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def with_object(&block)
|
|
13
|
+
object_stack << nil
|
|
14
|
+
instance_eval(&block)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def verify_array_target
|
|
18
|
+
case object_stack[-1]
|
|
19
|
+
when nil
|
|
20
|
+
object_stack[-1] = []
|
|
21
|
+
when Hash
|
|
22
|
+
raise "Mixing array and hash values"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def verify_hash_target
|
|
27
|
+
case object_stack[-1]
|
|
28
|
+
when nil
|
|
29
|
+
object_stack[-1] = {}
|
|
30
|
+
when Array
|
|
31
|
+
raise "Mixing array and hash values"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def push_array_item(value)
|
|
36
|
+
object_stack[-1] << value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def push_kv_item(key, value)
|
|
40
|
+
object_stack[-1][key] = value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def enter_object(&block)
|
|
44
|
+
object_stack << nil
|
|
45
|
+
instance_eval(&block)
|
|
46
|
+
object_stack.pop
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def item(value = nil, &block)
|
|
50
|
+
verify_array_target
|
|
51
|
+
if block
|
|
52
|
+
value = enter_object(&block)
|
|
53
|
+
end
|
|
54
|
+
push_array_item(value)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def kv(key, value, &block)
|
|
58
|
+
verify_hash_target
|
|
59
|
+
if block
|
|
60
|
+
value = enter_object(&block)
|
|
61
|
+
end
|
|
62
|
+
push_kv_item(key, value)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def method_missing(sym, value = nil, &block)
|
|
66
|
+
kv(sym, value, &block)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_s
|
|
70
|
+
object_stack[0].to_json
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/papercraft/renderer.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative './html'
|
|
4
|
+
require_relative './json'
|
|
4
5
|
require_relative './extension_proxy'
|
|
5
6
|
|
|
6
7
|
module Papercraft
|
|
@@ -55,7 +56,7 @@ module Papercraft
|
|
|
55
56
|
# end
|
|
56
57
|
#
|
|
57
58
|
# Papercraft.extension(components: ComponentLibrary)
|
|
58
|
-
#
|
|
59
|
+
# Papercraft.html { components.card('Foo', '**Bar**') }
|
|
59
60
|
#
|
|
60
61
|
# @param map [Hash] hash mapping methods to extension modules
|
|
61
62
|
# @return [void]
|
|
@@ -112,7 +113,7 @@ module Papercraft
|
|
|
112
113
|
|
|
113
114
|
# The tag method template below is optimized for performance. Do not touch!
|
|
114
115
|
|
|
115
|
-
S_TAG_METHOD_LINE = __LINE__ +
|
|
116
|
+
S_TAG_METHOD_LINE = __LINE__ + 2
|
|
116
117
|
S_TAG_METHOD = <<~EOF
|
|
117
118
|
S_TAG_%<TAG>s_PRE = %<tag_pre>s
|
|
118
119
|
S_TAG_%<TAG>s_CLOSE = %<tag_close>s
|
|
@@ -210,11 +211,11 @@ module Papercraft
|
|
|
210
211
|
# `#to_s` which is then added to the rendering buffer, without any escaping.
|
|
211
212
|
#
|
|
212
213
|
# greeter = proc { |name| h1 "Hello, #{name}!" }
|
|
213
|
-
#
|
|
214
|
+
# Papercraft.html { emit(greeter, 'world') }.render #=> "<h1>Hello, world!</h1>"
|
|
214
215
|
#
|
|
215
|
-
#
|
|
216
|
+
# Papercraft.html { emit 'hi&<bye>' }.render #=> "hi&<bye>"
|
|
216
217
|
#
|
|
217
|
-
#
|
|
218
|
+
# Papercraft.html { emit nil }.render #=> ""
|
|
218
219
|
#
|
|
219
220
|
# @param o [Proc, Papercraft::Component, String] emitted object
|
|
220
221
|
# @param *a [Array<any>] arguments to pass to a proc
|
|
@@ -234,7 +235,7 @@ module Papercraft
|
|
|
234
235
|
|
|
235
236
|
# Emits a block supplied using `Component#apply` or `Component#render`.
|
|
236
237
|
#
|
|
237
|
-
# div_wrap =
|
|
238
|
+
# div_wrap = Papercraft.html { |*args| div { emit_yield(*args) } }
|
|
238
239
|
# greeter = div_wrap.apply { |name| h1 "Hello, #{name}!" }
|
|
239
240
|
# greeter.render('world') #=> "<div><h1>Hello, world!</h1></div>"
|
|
240
241
|
#
|
|
@@ -242,9 +243,10 @@ module Papercraft
|
|
|
242
243
|
# @param **b [Hash] named arguments to pass to a proc
|
|
243
244
|
# @return [void]
|
|
244
245
|
def emit_yield(*a, **b)
|
|
245
|
-
|
|
246
|
+
block = @emit_yield_stack&.pop
|
|
247
|
+
raise Papercraft::Error, "No block given" unless block
|
|
246
248
|
|
|
247
|
-
instance_exec(*a, **b,
|
|
249
|
+
instance_exec(*a, **b, &block)
|
|
248
250
|
end
|
|
249
251
|
|
|
250
252
|
# Defers the given block to be evaluated later. Deferred evaluation allows
|
|
@@ -255,7 +257,7 @@ module Papercraft
|
|
|
255
257
|
# adding elements to the `<head>` section. Here's how a title can be
|
|
256
258
|
# controlled from a nested component:
|
|
257
259
|
#
|
|
258
|
-
# layout =
|
|
260
|
+
# layout = Papercraft.html {
|
|
259
261
|
# html {
|
|
260
262
|
# head {
|
|
261
263
|
# defer { title @title }
|
|
@@ -304,19 +306,19 @@ module Papercraft
|
|
|
304
306
|
private
|
|
305
307
|
|
|
306
308
|
# Escapes text. This method must be overriden in descendant classes.
|
|
309
|
+
#
|
|
310
|
+
# @param text [String] text to be escaped
|
|
307
311
|
def escape_text(text)
|
|
308
312
|
raise NotImplementedError
|
|
309
313
|
end
|
|
310
314
|
|
|
311
|
-
#
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
ensure
|
|
317
|
-
@inner_block = old_block
|
|
315
|
+
# Pushes the given block onto the emit_yield stack.
|
|
316
|
+
#
|
|
317
|
+
# @param block [Proc] block
|
|
318
|
+
def push_emit_yield_block(block)
|
|
319
|
+
(@emit_yield_stack ||= []) << block
|
|
318
320
|
end
|
|
319
|
-
|
|
321
|
+
|
|
320
322
|
# Emits tag attributes into the rendering buffer
|
|
321
323
|
# @param props [Hash] tag attributes
|
|
322
324
|
# @return [void]
|
|
@@ -379,4 +381,8 @@ module Papercraft
|
|
|
379
381
|
EscapeUtils.escape_xml(text.to_s)
|
|
380
382
|
end
|
|
381
383
|
end
|
|
384
|
+
|
|
385
|
+
class JSONRenderer < Renderer
|
|
386
|
+
include JSON
|
|
387
|
+
end
|
|
382
388
|
end
|
data/lib/papercraft/version.rb
CHANGED
data/lib/papercraft.rb
CHANGED
|
@@ -25,36 +25,49 @@ module Papercraft
|
|
|
25
25
|
def self.extension(map)
|
|
26
26
|
Renderer.extension(map)
|
|
27
27
|
end
|
|
28
|
-
end
|
|
29
28
|
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
#
|
|
34
|
-
#
|
|
29
|
+
# Creates a new papercraft component. `Papercraft.html` can take either a proc
|
|
30
|
+
# argument or a block. In both cases, the proc is converted to a
|
|
31
|
+
# `Papercraft::Component`.
|
|
32
|
+
#
|
|
33
|
+
# Papercraft.html(proc { h1 'hi' }).render #=> "<h1>hi</h1>"
|
|
34
|
+
# Papercraft.html { h1 'hi' }.render #=> "<h1>hi</h1>"
|
|
35
|
+
#
|
|
36
|
+
# @param template [Proc] template block
|
|
37
|
+
# @return [Papercraft::Component] Papercraft component
|
|
38
|
+
def self.html(o = nil, mime_type: nil, &template)
|
|
39
|
+
return o if o.is_a?(Papercraft::Component)
|
|
40
|
+
template ||= o
|
|
41
|
+
Papercraft::Component.new(mode: :html, mime_type: mime_type, &template)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Creates a new papercraft component in XML mode. `Papercraft.xml` can take
|
|
45
|
+
# either a proc argument or a block. In both cases, the proc is converted to a
|
|
46
|
+
# `Papercraft::Component`.
|
|
35
47
|
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
48
|
+
# Papercraft.xml(proc { item 'foo' }).render #=> "<item>foo</item>"
|
|
49
|
+
# Papercraft.xml { item 'foo' }.render #=> "<item>foo</item>"
|
|
38
50
|
#
|
|
39
51
|
# @param template [Proc] template block
|
|
40
52
|
# @return [Papercraft::Component] Papercraft component
|
|
41
|
-
def
|
|
53
|
+
def self.xml(o = nil, mime_type: nil, &template)
|
|
42
54
|
return o if o.is_a?(Papercraft::Component)
|
|
43
55
|
template ||= o
|
|
44
|
-
Papercraft::Component.new(mode: :
|
|
56
|
+
Papercraft::Component.new(mode: :xml, mime_type: mime_type, &template)
|
|
45
57
|
end
|
|
46
58
|
|
|
47
|
-
# Creates a new papercraft component in
|
|
48
|
-
# a block. In both cases, the proc is converted to a
|
|
59
|
+
# Creates a new papercraft component in JSON mode. `Papercraft.json` can take
|
|
60
|
+
# either a proc argument or a block. In both cases, the proc is converted to a
|
|
61
|
+
# `Papercraft::Component`.
|
|
49
62
|
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
63
|
+
# Papercraft.json(proc { item 42 }).render #=> "[42]"
|
|
64
|
+
# Papercraft.json { foo 'bar' }.render #=> "{\"foo\": \"bar\"}"
|
|
52
65
|
#
|
|
53
66
|
# @param template [Proc] template block
|
|
54
67
|
# @return [Papercraft::Component] Papercraft component
|
|
55
|
-
def
|
|
68
|
+
def self.json(o = nil, mime_type: nil, &template)
|
|
56
69
|
return o if o.is_a?(Papercraft::Component)
|
|
57
70
|
template ||= o
|
|
58
|
-
Papercraft::Component.new(mode: :
|
|
71
|
+
Papercraft::Component.new(mode: :json, mime_type: mime_type, &template)
|
|
59
72
|
end
|
|
60
73
|
end
|
metadata
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: papercraft
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: '0.
|
|
4
|
+
version: '0.16'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sharon Rosner
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-01-
|
|
11
|
+
date: 2022-01-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: escape_utils
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- -
|
|
17
|
+
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: 1.2.1
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- -
|
|
24
|
+
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: 1.2.1
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
@@ -30,28 +30,28 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 2.3.
|
|
33
|
+
version: 2.3.1
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 2.3.
|
|
40
|
+
version: 2.3.1
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: rouge
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: 3.
|
|
47
|
+
version: 3.27.0
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: 3.
|
|
54
|
+
version: 3.27.0
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: kramdown-parser-gfm
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -70,56 +70,56 @@ dependencies:
|
|
|
70
70
|
name: minitest
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
|
-
- -
|
|
73
|
+
- - "~>"
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: 5.
|
|
75
|
+
version: '5.15'
|
|
76
76
|
type: :development
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
|
-
- -
|
|
80
|
+
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: 5.
|
|
82
|
+
version: '5.15'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
84
|
name: benchmark-ips
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
|
-
- -
|
|
87
|
+
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
89
|
version: 2.7.2
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
|
-
- -
|
|
94
|
+
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
96
|
version: 2.7.2
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
98
|
name: erubis
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
100
100
|
requirements:
|
|
101
|
-
- -
|
|
101
|
+
- - "~>"
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
103
|
version: 2.7.0
|
|
104
104
|
type: :development
|
|
105
105
|
prerelease: false
|
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
107
|
requirements:
|
|
108
|
-
- -
|
|
108
|
+
- - "~>"
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: 2.7.0
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
112
|
name: tilt
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
114
114
|
requirements:
|
|
115
|
-
- -
|
|
115
|
+
- - "~>"
|
|
116
116
|
- !ruby/object:Gem::Version
|
|
117
117
|
version: 2.0.9
|
|
118
118
|
type: :development
|
|
119
119
|
prerelease: false
|
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
121
|
requirements:
|
|
122
|
-
- -
|
|
122
|
+
- - "~>"
|
|
123
123
|
- !ruby/object:Gem::Version
|
|
124
124
|
version: 2.0.9
|
|
125
125
|
description:
|
|
@@ -137,6 +137,7 @@ files:
|
|
|
137
137
|
- lib/papercraft/encoding.rb
|
|
138
138
|
- lib/papercraft/extension_proxy.rb
|
|
139
139
|
- lib/papercraft/html.rb
|
|
140
|
+
- lib/papercraft/json.rb
|
|
140
141
|
- lib/papercraft/renderer.rb
|
|
141
142
|
- lib/papercraft/version.rb
|
|
142
143
|
homepage: http://github.com/digital-fabric/papercraft
|