papercraft 0.18 → 0.22
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +78 -36
- data/lib/papercraft/extension_proxy.rb +16 -3
- data/lib/papercraft/html.rb +32 -0
- data/lib/papercraft/json.rb +89 -36
- data/lib/papercraft/renderer.rb +10 -239
- data/lib/papercraft/tags.rb +260 -0
- data/lib/papercraft/{component.rb → template.rb} +29 -26
- data/lib/papercraft/version.rb +1 -1
- data/lib/papercraft/xml.rb +39 -0
- data/lib/papercraft.rb +17 -17
- data/papercraft.png +0 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 729c7b1cf7482cb761b005fba8da8ae4c1325559daee15fedd2d695de21ee08d
|
4
|
+
data.tar.gz: 0546c527a6a9ac0570c9bd6057101b31591eba1f1ee549c272d9bda13a9f7a04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdaa8d2f254744694ea3624f39871f4b411294a990f6e1bb4c57f56bf36049031b9403820cbadaa57ef6ac3e9021b6afa53da22588462450a6ca0863b6b88184
|
7
|
+
data.tar.gz: 4ea21235970af0cdef9ce09cd790742a94632e4f1f02d4a3d90a3e857741ec41952a53fe59b24016c00894dde7131dbbbc79d7616564f159f5646acf24f469f9
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
## 0.22 2022-02-14
|
2
|
+
|
3
|
+
- Fix behaviour of call to `#p` in an extension (#10)
|
4
|
+
|
5
|
+
## 0.21 2022-02-13
|
6
|
+
|
7
|
+
- Refactor and improve documentation
|
8
|
+
|
9
|
+
## 0.20 2022-02-13
|
10
|
+
|
11
|
+
- Add support for XML namespaced tags and attributes (#9)
|
12
|
+
- Move and refactor HTML/XML common code to Tags module
|
13
|
+
|
14
|
+
## 0.19 2022-02-05
|
15
|
+
|
16
|
+
- Rename `Papercraft::Component` to `Papercraft::Template`
|
17
|
+
|
1
18
|
## 0.18 2022-02-04
|
2
19
|
|
3
20
|
- Cleanup and update examples
|
data/README.md
CHANGED
@@ -64,13 +64,14 @@ hello.render('world')
|
|
64
64
|
- [Installing papercraft](#installing-papercraft)
|
65
65
|
- [Basic usage](#basic-usage)
|
66
66
|
- [Adding tags](#adding-tags)
|
67
|
+
- [Tag and attribute formatting](#tag-and-attribute-formatting)
|
67
68
|
- [Template parameters](#template-parameters)
|
68
69
|
- [Template logic](#template-logic)
|
69
70
|
- [Template blocks](#template-blocks)
|
70
71
|
- [Plain procs as templates](#plain-procs-as-templates)
|
71
72
|
- [Template composition](#template-composition)
|
72
73
|
- [Parameter and block application](#parameter-and-block-application)
|
73
|
-
- [Higher-order
|
74
|
+
- [Higher-order templates](#higher-order-templates)
|
74
75
|
- [Layout template composition](#layout-template-composition)
|
75
76
|
- [Emitting raw HTML](#emitting-raw-html)
|
76
77
|
- [Emitting a string with HTML Encoding](#emitting-a-string-with-html-encoding)
|
@@ -154,6 +155,43 @@ Papercraft.html { img src: '/my.gif' }.render #=> "<img src="/my.gif"/>
|
|
154
155
|
Papercraft.html { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
|
155
156
|
```
|
156
157
|
|
158
|
+
## Tag and attribute formatting
|
159
|
+
|
160
|
+
Papercraft does not make any presumption about what tags and attributes you can
|
161
|
+
use. You can mix upper and lower case letters, and you can include arbitrary
|
162
|
+
characters in tag and attribute names. However, in order to best adhere to the
|
163
|
+
HTML and XML specs and common practices, tag names and attributes will be
|
164
|
+
formatted according to the following rules, depending on the template type:
|
165
|
+
|
166
|
+
- HTML: underscores are converted to dashes:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
Papercraft.html {
|
170
|
+
foo_bar { p 'Hello', data_name: 'world' }
|
171
|
+
}.render #=> '<foo-bar><p data-name="world">Hello</p></foo-bar>'
|
172
|
+
```
|
173
|
+
|
174
|
+
- XML: underscores are converted to dashes, double underscores are converted to
|
175
|
+
colons:
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
Papercraft.xml {
|
179
|
+
soap__Envelope(
|
180
|
+
xmlns__soap: 'http://schemas.xmlsoap.org/soap/envelope/',
|
181
|
+
) { }
|
182
|
+
}.render #=> '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Envelope>'
|
183
|
+
```
|
184
|
+
|
185
|
+
If you need more precise control over tag names, you can use the `#tag` method,
|
186
|
+
which takes the tag name as its first parameter, then the rest of the parameters
|
187
|
+
normally used for tags:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
Papercraft.html {
|
191
|
+
tag 'cra_zy__:!tag', 'foo'
|
192
|
+
}.render #=> '<cra_zy__:!tag>foo</cra_zy__:!tag>'
|
193
|
+
```
|
194
|
+
|
157
195
|
## Template parameters
|
158
196
|
|
159
197
|
In Papercraft, parameters are always passed explicitly. This means that template
|
@@ -221,8 +259,8 @@ Papercraft.html(&greeting).render('world')
|
|
221
259
|
|
222
260
|
## Template composition
|
223
261
|
|
224
|
-
Papercraft makes it easy to compose multiple
|
225
|
-
document. A Papercraft
|
262
|
+
Papercraft makes it easy to compose multiple templates into a whole HTML
|
263
|
+
document. A Papercraft template can contain other templates, as the following
|
226
264
|
example shows.
|
227
265
|
|
228
266
|
```ruby
|
@@ -250,14 +288,14 @@ page = Papercraft.html { |title, items|
|
|
250
288
|
}
|
251
289
|
}
|
252
290
|
|
253
|
-
page.render('Hello from
|
291
|
+
page.render('Hello from composed templates', [
|
254
292
|
{ id: 1, text: 'foo', checked: false },
|
255
293
|
{ id: 2, text: 'bar', checked: true }
|
256
294
|
])
|
257
295
|
```
|
258
296
|
|
259
|
-
In addition to using
|
260
|
-
non-constant
|
297
|
+
In addition to using templates defined as constants, you can also use
|
298
|
+
non-constant templates by invoking the `#emit` method:
|
261
299
|
|
262
300
|
```ruby
|
263
301
|
greeting = -> { span "Hello, world" }
|
@@ -272,11 +310,11 @@ Papercraft.html {
|
|
272
310
|
## Parameter and block application
|
273
311
|
|
274
312
|
Parameters and blocks can be applied to a template without it being rendered, by
|
275
|
-
using `#apply`. This mechanism is what allows
|
276
|
-
creation of higher-order
|
313
|
+
using `#apply`. This mechanism is what allows template composition and the
|
314
|
+
creation of higher-order templates.
|
277
315
|
|
278
|
-
The `#apply` method returns a new
|
279
|
-
or block to the original
|
316
|
+
The `#apply` method returns a new template which applies the given parameters and
|
317
|
+
or block to the original template:
|
280
318
|
|
281
319
|
```ruby
|
282
320
|
# parameter application
|
@@ -289,19 +327,19 @@ div_wrap = Papercraft.html { div { emit_yield } }
|
|
289
327
|
wrapped_h1 = div_wrap.apply { h1 'hi' }
|
290
328
|
wrapped_h1.render #=> "<div><h1>hi</h1></div>"
|
291
329
|
|
292
|
-
# wrap a
|
330
|
+
# wrap a template
|
293
331
|
wrapped_hello_world = div_wrap.apply(&hello_world)
|
294
332
|
wrapped_hello_world.render #=> "<div><h1>Hello, world!</h1></div>"
|
295
333
|
```
|
296
334
|
|
297
|
-
## Higher-order
|
335
|
+
## Higher-order templates
|
298
336
|
|
299
|
-
Papercraft also lets you create higher-order
|
300
|
-
|
301
|
-
|
302
|
-
markup, enhancing
|
337
|
+
Papercraft also lets you create higher-order templates, that is,
|
338
|
+
templates that take other templates as parameters, or as blocks. Higher-order
|
339
|
+
templates are handy for creating layouts, wrapping templates in arbitrary
|
340
|
+
markup, enhancing templates or injecting template parameters.
|
303
341
|
|
304
|
-
Here is a
|
342
|
+
Here is a higher-order template that takes a template as parameter:
|
305
343
|
|
306
344
|
```ruby
|
307
345
|
div_wrap = Papercraft.html { |inner| div { emit inner } }
|
@@ -310,7 +348,7 @@ wrapped_greeter = div_wrap.apply(greeter)
|
|
310
348
|
wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
311
349
|
```
|
312
350
|
|
313
|
-
The inner
|
351
|
+
The inner template can also be passed as a block, as shown above:
|
314
352
|
|
315
353
|
```ruby
|
316
354
|
div_wrap = Papercraft.html { div { emit_yield } }
|
@@ -320,7 +358,7 @@ wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
|
320
358
|
|
321
359
|
## Layout template composition
|
322
360
|
|
323
|
-
One of the principal uses of higher-order
|
361
|
+
One of the principal uses of higher-order templates is the creation of nested
|
324
362
|
layouts. Suppose we have a website with a number of different layouts, and we'd
|
325
363
|
like to avoid having to repeat the same code in the different layouts. We can do
|
326
364
|
this by creating a `default` page template that takes a block, then use `#apply`
|
@@ -434,8 +472,8 @@ end
|
|
434
472
|
## Deferred evaluation
|
435
473
|
|
436
474
|
Deferred evaluation allows deferring the rendering of parts of a template until
|
437
|
-
the last moment, thus allowing an inner
|
438
|
-
outer
|
475
|
+
the last moment, thus allowing an inner template to manipulate the state of the
|
476
|
+
outer template. To in order to defer a part of a template, use `#defer`, and
|
439
477
|
include any markup in the provided block. This technique, in in conjunction with
|
440
478
|
holding state in instance variables, is an alternative to passing parameters,
|
441
479
|
which can be limiting in some situations.
|
@@ -444,11 +482,11 @@ A few use cases for deferred evaulation come to mind:
|
|
444
482
|
|
445
483
|
- Setting the page title.
|
446
484
|
- Adding a flash message to a page.
|
447
|
-
- Using
|
485
|
+
- Using templates that dynamically add static dependencies (JS and CSS) to the
|
448
486
|
page.
|
449
487
|
|
450
488
|
The last use case is particularly interesting. Imagine a `DependencyMananger`
|
451
|
-
class that can collect JS and CSS dependencies from the different
|
489
|
+
class that can collect JS and CSS dependencies from the different templates
|
452
490
|
integrated into the page, and adds them to the page's `<head>` element:
|
453
491
|
|
454
492
|
```ruby
|
@@ -513,21 +551,27 @@ and other associated methods:
|
|
513
551
|
|
514
552
|
```ruby
|
515
553
|
module BootstrapComponents
|
516
|
-
|
517
|
-
|
518
|
-
def card(**props)
|
554
|
+
def card(**props, &block)
|
519
555
|
div(class: 'card', **props) {
|
520
|
-
div(class: 'card-body')
|
521
|
-
emit_yield
|
522
|
-
}
|
556
|
+
div(class: 'card-body', &block)
|
523
557
|
}
|
524
558
|
end
|
525
|
-
|
559
|
+
|
526
560
|
def card_title(title)
|
527
|
-
|
561
|
+
h4(title, class: 'card-title')
|
562
|
+
end
|
563
|
+
|
564
|
+
def card_subtitle(subtitle)
|
565
|
+
h5(subtitle, class: 'card-subtitle')
|
528
566
|
end
|
529
567
|
|
530
|
-
|
568
|
+
def card_text(text)
|
569
|
+
p(text, class: 'card-text')
|
570
|
+
end
|
571
|
+
|
572
|
+
def card_link(text, **opts)
|
573
|
+
a(text, class: 'card-link', **opts)
|
574
|
+
end
|
531
575
|
end
|
532
576
|
|
533
577
|
Papercraft.extension(bootstrap: BootstrapComponents)
|
@@ -543,14 +587,12 @@ Papercraft.html {
|
|
543
587
|
bootstrap.card_title 'Card title'
|
544
588
|
bootstrap.card_subtitle 'Card subtitle'
|
545
589
|
bootstrap.card_text 'Some quick example text to build on the card title and make up the bulk of the card''s content.'
|
546
|
-
bootstrap.card_link '
|
547
|
-
bootstrap.card_link '
|
590
|
+
bootstrap.card_link 'Card link', href: '#foo'
|
591
|
+
bootstrap.card_link 'Another link', href: '#bar'
|
548
592
|
}
|
549
593
|
}
|
550
594
|
```
|
551
595
|
|
552
|
-
|
553
|
-
|
554
596
|
## XML templates
|
555
597
|
|
556
598
|
XML templates behave largely the same as HTML templates, with a few minor
|
@@ -10,6 +10,7 @@ module Papercraft
|
|
10
10
|
class ExtensionProxy
|
11
11
|
|
12
12
|
# Initializes a new ExtensionProxy.
|
13
|
+
#
|
13
14
|
# @param renderer [Papercraft::Renderer] renderer to proxy to
|
14
15
|
# @param mod [Module] extension module
|
15
16
|
# @return [void]
|
@@ -18,13 +19,25 @@ module Papercraft
|
|
18
19
|
extend(mod)
|
19
20
|
end
|
20
21
|
|
21
|
-
# Proxies missing methods to the renderer
|
22
|
+
# Proxies missing methods to the renderer.
|
23
|
+
#
|
22
24
|
# @param sym [Symbol] method name
|
23
25
|
# @param *args [Array] arguments
|
26
|
+
# @param *props [Array] named arguments
|
24
27
|
# @param &block [Proc] block
|
25
28
|
# @return void
|
26
|
-
def method_missing(sym, *args, &block)
|
27
|
-
@renderer.send(sym, *args, &block)
|
29
|
+
def method_missing(sym, *args, **props, &block)
|
30
|
+
@renderer.send(sym, *args, **props, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Overrides the `Kernel#p` method to emit a p tag.
|
34
|
+
#
|
35
|
+
# @param *args [Array] arguments
|
36
|
+
# @param *props [Array] named arguments
|
37
|
+
# @param &block [Proc] block
|
38
|
+
# @return void
|
39
|
+
def p(text = nil, **props, &block)
|
40
|
+
@renderer.p(text, **props, &block)
|
28
41
|
end
|
29
42
|
end
|
30
43
|
end
|
data/lib/papercraft/html.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './tags'
|
4
|
+
|
3
5
|
module Papercraft
|
4
6
|
# HTML Markup extensions
|
5
7
|
module HTML
|
8
|
+
include Tags
|
9
|
+
|
6
10
|
# Emits the p tag (overrides Object#p)
|
7
11
|
#
|
8
12
|
# @param text [String] text content of tag
|
@@ -81,5 +85,33 @@ module Papercraft
|
|
81
85
|
def emit_markdown(markdown, **opts)
|
82
86
|
emit Papercraft.markdown(markdown, **opts)
|
83
87
|
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Escapes the given text using HTML entities.
|
92
|
+
#
|
93
|
+
# @param text [String] text
|
94
|
+
# @return [String] escaped text
|
95
|
+
def escape_text(text)
|
96
|
+
EscapeUtils.escape_html(text.to_s)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Converts a tag to its string representation. Underscores will be converted
|
100
|
+
# to dashes.
|
101
|
+
#
|
102
|
+
# @param tag [Symbol, String] tag
|
103
|
+
# @return [String] tag string
|
104
|
+
def tag_repr(tag)
|
105
|
+
tag.to_s.tr('_', '-')
|
106
|
+
end
|
107
|
+
|
108
|
+
# Converts an attribute to its string representation. Underscores will be
|
109
|
+
# converted to dashes.
|
110
|
+
#
|
111
|
+
# @param att [Symbol, String] attribute
|
112
|
+
# @return [String] attribute string
|
113
|
+
def att_repr(att)
|
114
|
+
att.to_s.tr('_', '-')
|
115
|
+
end
|
84
116
|
end
|
85
117
|
end
|
data/lib/papercraft/json.rb
CHANGED
@@ -5,69 +5,122 @@ require 'json'
|
|
5
5
|
module Papercraft
|
6
6
|
# JSON renderer extensions
|
7
7
|
module JSON
|
8
|
-
|
9
|
-
|
8
|
+
# Initializes a JSON renderer, setting up an object stack.
|
9
|
+
def initialize(&template)
|
10
|
+
@object_stack = [nil]
|
11
|
+
super
|
10
12
|
end
|
11
13
|
|
14
|
+
# Adds an array item to the current object target. If a block is given, the
|
15
|
+
# block is evaulated against a new object target, then added to the current
|
16
|
+
# array.
|
17
|
+
#
|
18
|
+
# @param value [Object] item
|
19
|
+
# @param &block [Proc] template block
|
20
|
+
# @return [void]
|
21
|
+
def item(value = nil, &block)
|
22
|
+
verify_array_target
|
23
|
+
if block
|
24
|
+
value = enter_object(&block)
|
25
|
+
end
|
26
|
+
push_array_item(value)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Adds a key-value item to the current object target. If a block is given,
|
30
|
+
# the block is evaulated against a new object target, then used as the
|
31
|
+
# value.
|
32
|
+
#
|
33
|
+
# @param key [Object] key
|
34
|
+
# @param value [Object] value
|
35
|
+
# @param &block [Proc] template block
|
36
|
+
# @return [void]
|
37
|
+
def kv(key, value = nil, &block)
|
38
|
+
verify_hash_target
|
39
|
+
if block
|
40
|
+
value = enter_object(&block)
|
41
|
+
end
|
42
|
+
push_kv_item(key, value)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Intercepts method calls by adding key-value pairs to the current object
|
46
|
+
# target.
|
47
|
+
#
|
48
|
+
# @param key [Object] key
|
49
|
+
# @param value [Object] value
|
50
|
+
# @param &block [Proc] template block
|
51
|
+
# @return [void]
|
52
|
+
def method_missing(sym, value = nil, &block)
|
53
|
+
kv(sym, value, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Converts the root object target to JSON.
|
57
|
+
#
|
58
|
+
# @return [String] JSON template result
|
59
|
+
def to_s
|
60
|
+
@object_stack[0].to_json
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Adds a new entry to the object stack and evaluates the given block.
|
66
|
+
#
|
67
|
+
# @param &block [Proc] template block
|
68
|
+
# @return [void]
|
12
69
|
def with_object(&block)
|
13
|
-
object_stack << nil
|
70
|
+
@object_stack << nil
|
14
71
|
instance_eval(&block)
|
15
72
|
end
|
16
73
|
|
74
|
+
# Verifies that the current object target is not a hash.
|
75
|
+
#
|
76
|
+
# @return [bool]
|
17
77
|
def verify_array_target
|
18
|
-
case object_stack[-1]
|
78
|
+
case @object_stack[-1]
|
19
79
|
when nil
|
20
|
-
object_stack[-1] = []
|
80
|
+
@object_stack[-1] = []
|
21
81
|
when Hash
|
22
82
|
raise "Mixing array and hash values"
|
23
83
|
end
|
24
84
|
end
|
25
85
|
|
86
|
+
# Verifies that the current object target is not an array.
|
87
|
+
#
|
88
|
+
# @return [bool]
|
26
89
|
def verify_hash_target
|
27
|
-
case object_stack[-1]
|
90
|
+
case @object_stack[-1]
|
28
91
|
when nil
|
29
|
-
object_stack[-1] = {}
|
92
|
+
@object_stack[-1] = {}
|
30
93
|
when Array
|
31
94
|
raise "Mixing array and hash values"
|
32
95
|
end
|
33
96
|
end
|
34
97
|
|
98
|
+
# Pushes an array item to the current object target.
|
99
|
+
#
|
100
|
+
# @param value [Object] item
|
101
|
+
# @return [void]
|
35
102
|
def push_array_item(value)
|
36
|
-
object_stack[-1] << value
|
103
|
+
@object_stack[-1] << value
|
37
104
|
end
|
38
105
|
|
106
|
+
# Pushes a key value into the current object target.
|
107
|
+
#
|
108
|
+
# @param key [Object] key
|
109
|
+
# @param value [Object] value
|
110
|
+
# @return [void]
|
39
111
|
def push_kv_item(key, value)
|
40
|
-
object_stack[-1][key] = value
|
112
|
+
@object_stack[-1][key] = value
|
41
113
|
end
|
42
114
|
|
115
|
+
# Adds a new object to the object stack, evaluates the given template block,
|
116
|
+
# then pops the object off the stack.
|
117
|
+
#
|
118
|
+
# @param &block [Proc] template block
|
119
|
+
# @return [void]
|
43
120
|
def enter_object(&block)
|
44
|
-
object_stack << nil
|
121
|
+
@object_stack << nil
|
45
122
|
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
|
123
|
+
@object_stack.pop
|
124
|
+
end
|
72
125
|
end
|
73
126
|
end
|