papercraft 1.0 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +94 -2
- data/lib/papercraft/extension_proxy.rb +2 -4
- data/lib/papercraft/extensions/soap.rb +0 -4
- data/lib/papercraft/html.rb +9 -8
- data/lib/papercraft/json.rb +13 -11
- data/lib/papercraft/renderer.rb +36 -8
- data/lib/papercraft/tags.rb +30 -18
- data/lib/papercraft/template.rb +35 -9
- data/lib/papercraft/version.rb +1 -1
- data/lib/papercraft/xml.rb +2 -2
- data/lib/papercraft.rb +8 -8
- metadata +16 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ce5c8e506f96cd5e49625ba875bec30df1fe7849cf726e715383edf1feec4dd
|
4
|
+
data.tar.gz: fa5a04e6baf5b8e8e36d08a8e9bc962da886c48881489f699b1abab51bc06181
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9fd2b6f1ce19479f6a57a5ca75f238b7fb7dee33a3275c2d7f786a1630a5ea5ca5f2d3210946c2ee1dd55676dce65fe0d3503bab830980e5d11fef216a76b5fd
|
7
|
+
data.tar.gz: 32bd431564ba19783822b5f0596d6f13fc9bd3fee6c498b0d5a9d9722bd536f19ee90fec74a8933460324fbe47d1eab860f3700b6d07bc904c60b131933c5a7b
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -24,7 +24,8 @@
|
|
24
24
|
|
25
25
|
## What is Papercraft?
|
26
26
|
|
27
|
-
Papercraft is a templating engine for dynamically producing
|
27
|
+
Papercraft is a templating engine for dynamically producing
|
28
|
+
[HTML](#html-templates), [XML](#xml-templates) or [JSON](#json-templates).
|
28
29
|
Papercraft templates are expressed in plain Ruby, leading to easier debugging,
|
29
30
|
better protection against HTML/XML injection attacks, and better code reuse.
|
30
31
|
|
@@ -71,9 +72,11 @@ hello.render('world')
|
|
71
72
|
- [Adding Tags](#adding-tags)
|
72
73
|
- [Tag and Attribute Formatting](#tag-and-attribute-formatting)
|
73
74
|
- [Escaping Content](#escaping-content)
|
75
|
+
- [Direct Iteration](#direct-iteration)
|
74
76
|
- [Template Parameters](#template-parameters)
|
75
77
|
- [Template Logic](#template-logic)
|
76
78
|
- [Template Blocks](#template-blocks)
|
79
|
+
- [Template Fragments](#template-fragments)
|
77
80
|
- [Plain Procs as Templates](#plain-procs-as-templates)
|
78
81
|
- [Template Composition](#template-composition)
|
79
82
|
- [Parameter and Block Application](#parameter-and-block-application)
|
@@ -130,7 +133,7 @@ html.render #=> "<div id="greeter"><p>Hello!</p></div>"
|
|
130
133
|
|
131
134
|
## Using with Tilt
|
132
135
|
|
133
|
-
Papercraft templates can also be rendered using Tilt:
|
136
|
+
Papercraft templates can also be rendered using [Tilt](https://github.com/jeremyevans/tilt):
|
134
137
|
|
135
138
|
```ruby
|
136
139
|
require 'tilt/papercraft'
|
@@ -271,6 +274,66 @@ below](#emitting-raw-html).
|
|
271
274
|
JSON templates are rendered using the `json` gem bundled with Ruby, which takes
|
272
275
|
care of escaping text values.
|
273
276
|
|
277
|
+
## Direct Iteration
|
278
|
+
|
279
|
+
Papercraft enables iterating directly over any enumerable data source. Instead
|
280
|
+
of rendering each item in a given data container by wrapping it inside of an
|
281
|
+
`#each` block, we can simply pass the data source *directly* to the tag using
|
282
|
+
the `_for` attribute. This is particularly useful when we need to create a set
|
283
|
+
of nested tags for each item. Consider the following example:
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
data = %w{foo bar}
|
287
|
+
|
288
|
+
Papercraft.html {
|
289
|
+
data.each { |item|
|
290
|
+
tr {
|
291
|
+
td item
|
292
|
+
}
|
293
|
+
}
|
294
|
+
}.render #=> '<tr><td>foo</td></tr><tr><td>bar</td></tr>'
|
295
|
+
```
|
296
|
+
|
297
|
+
Instead of using `data.each` to iterate over the list of data, we can directly
|
298
|
+
pass the data source to the `tr` tag using the special `_for` attribute:
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
Papercraft.html {
|
302
|
+
tr(_for: data) { |item|
|
303
|
+
td item
|
304
|
+
}
|
305
|
+
}.render #=> '<tr><td>foo</td></tr><tr><td>bar</td></tr>'
|
306
|
+
```
|
307
|
+
|
308
|
+
Note that this will work with any data source that is an `Enumerable` or an
|
309
|
+
`Enumerator`. For example, you can use `#each_with_index` or iterate over a
|
310
|
+
hash. Papercraft will pass all yielded values to the given block:
|
311
|
+
|
312
|
+
```ruby
|
313
|
+
data = %{foo bar}
|
314
|
+
Papercraft.html {
|
315
|
+
tr(_for: data.each_with_index) { |item, idx|
|
316
|
+
td idx + 1
|
317
|
+
td item
|
318
|
+
}
|
319
|
+
}.render #=> '<tr><td>1</td><td>foo</td></tr><tr><td>2</td><td>bar</td></tr>'
|
320
|
+
|
321
|
+
data = [
|
322
|
+
{ name: 'foo', age: 16 },
|
323
|
+
{ name: 'bar', age: 32 }
|
324
|
+
]
|
325
|
+
Papercraft.html {
|
326
|
+
div(_for: data, class: 'row') { |row|
|
327
|
+
div(_for: row) { |k, v|
|
328
|
+
span k
|
329
|
+
span v
|
330
|
+
}
|
331
|
+
}
|
332
|
+
}.render
|
333
|
+
#=> '<div class="row"><div><span>name</span><span>foo</span></div><div><span>age</span><span>16</span></div></div>'
|
334
|
+
#=> '<div class="row"><div><span>name</span><span>bar</span></div><div><span>age</span><span>32</span></div></div>'
|
335
|
+
```
|
336
|
+
|
274
337
|
## Template Parameters
|
275
338
|
|
276
339
|
In Papercraft, parameters are always passed explicitly. This means that template
|
@@ -319,6 +382,35 @@ page = Papercraft.html {
|
|
319
382
|
page.render { h1 'hi' }
|
320
383
|
```
|
321
384
|
|
385
|
+
## Template Fragments
|
386
|
+
|
387
|
+
Template fragments allow rendering specific parts of a template, instead of the
|
388
|
+
entire template. For example, you can define a template for a web page that
|
389
|
+
includes a form, but may want to render just the form. Instead of extracting the
|
390
|
+
code and putting it into a separate template, you can use template fragments to
|
391
|
+
render just that part of that template:
|
392
|
+
|
393
|
+
```ruby
|
394
|
+
page = Papercraft.html {
|
395
|
+
div {
|
396
|
+
h1 'Page title'
|
397
|
+
p 'Some text'
|
398
|
+
}
|
399
|
+
div(id: 'my-form') {
|
400
|
+
fragment(:form) {
|
401
|
+
form {
|
402
|
+
input(name: 'email')
|
403
|
+
button 'OK'
|
404
|
+
}
|
405
|
+
}
|
406
|
+
}
|
407
|
+
}
|
408
|
+
page.render_fragment(:form)
|
409
|
+
```
|
410
|
+
|
411
|
+
For more information on how to use template fragments, see the [HTMX
|
412
|
+
article](https://htmx.org/essays/template-fragments/).
|
413
|
+
|
322
414
|
## Plain Procs as Templates
|
323
415
|
|
324
416
|
With Papercraft you can write a template as a plain Ruby proc, and later render
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Papercraft
|
4
|
-
|
4
|
+
|
5
5
|
# An ExtensionProxy proxies method calls to a renderer. Extension proxies are
|
6
6
|
# used to provide an namespaced interface to Papercraft extensions. When an
|
7
7
|
# extension is installed using `Papercraft.extension`, a corresponding method
|
@@ -18,13 +18,12 @@ module Papercraft
|
|
18
18
|
@renderer = renderer
|
19
19
|
extend(mod)
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
# Proxies missing methods to the renderer.
|
23
23
|
#
|
24
24
|
# @param sym [Symbol] method name
|
25
25
|
# @param *args [Array] arguments
|
26
26
|
# @param *props [Array] named arguments
|
27
|
-
# @param &block [Proc] block
|
28
27
|
# @return void
|
29
28
|
def method_missing(sym, *args, **props, &block)
|
30
29
|
@renderer.send(sym, *args, **props, &block)
|
@@ -34,7 +33,6 @@ module Papercraft
|
|
34
33
|
#
|
35
34
|
# @param *args [Array] arguments
|
36
35
|
# @param *props [Array] named arguments
|
37
|
-
# @param &block [Proc] block
|
38
36
|
# @return void
|
39
37
|
def p(text = nil, **props, &block)
|
40
38
|
@renderer.p(text, **props, &block)
|
@@ -6,7 +6,6 @@ module Papercraft
|
|
6
6
|
# Emits a SOAP XML tag that identifies the XML document as a SOAP message.
|
7
7
|
#
|
8
8
|
# @param **props [Hash] tag attributes
|
9
|
-
# @param &block [Proc] optional inner XML
|
10
9
|
# @return [void]
|
11
10
|
def Envelope(**props, &block)
|
12
11
|
props[:xmlns__soap] ||= 'http://schemas.xmlsoap.org/soap/envelope/'
|
@@ -16,7 +15,6 @@ module Papercraft
|
|
16
15
|
# Emits a SOAP XML tag that contains header information.
|
17
16
|
#
|
18
17
|
# @param **props [Hash] tag attributes
|
19
|
-
# @param &block [Proc] optional inner XML
|
20
18
|
# @return [void]
|
21
19
|
def Header(**props, &blk)
|
22
20
|
tag('soap:Header', **props, &blk)
|
@@ -25,7 +23,6 @@ module Papercraft
|
|
25
23
|
# Emits a SOAP XML tag that contains header information.
|
26
24
|
#
|
27
25
|
# @param **props [Hash] tag attributes
|
28
|
-
# @param &block [Proc] optional inner XML
|
29
26
|
# @return [void]
|
30
27
|
def Body(**props, &blk)
|
31
28
|
tag('soap:Body', **props, &blk)
|
@@ -34,7 +31,6 @@ module Papercraft
|
|
34
31
|
# Emits a SOAP XML tag that contains errors and status information.
|
35
32
|
#
|
36
33
|
# @param **props [Hash] tag attributes
|
37
|
-
# @param &block [Proc] optional inner XML
|
38
34
|
# @return [void]
|
39
35
|
def Fault(**props, &blk)
|
40
36
|
tag('soap:Fault', **props, &blk)
|
data/lib/papercraft/html.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative './tags'
|
4
|
+
require 'cgi'
|
4
5
|
|
5
|
-
module Papercraft
|
6
|
+
module Papercraft
|
6
7
|
# HTML Markup extensions
|
7
8
|
module HTML
|
8
9
|
include Tags
|
@@ -10,7 +11,7 @@ module Papercraft
|
|
10
11
|
# Emits the p tag (overrides Object#p)
|
11
12
|
#
|
12
13
|
# @param text [String] text content of tag
|
13
|
-
# @param props [Hash] tag attributes
|
14
|
+
# @param **props [Hash] tag attributes
|
14
15
|
# @para block [Proc] nested HTML block
|
15
16
|
# @return [void]
|
16
17
|
def p(text = nil, **props, &block)
|
@@ -55,7 +56,7 @@ module Papercraft
|
|
55
56
|
|
56
57
|
@buffer << '>' << css << '</style>'
|
57
58
|
end
|
58
|
-
|
59
|
+
|
59
60
|
# Emits an inline JS script element.
|
60
61
|
#
|
61
62
|
# @param js [String, nil] Javascript code
|
@@ -82,8 +83,8 @@ module Papercraft
|
|
82
83
|
fn = File.join(root_path, href)
|
83
84
|
version = File.stat(fn).mtime.to_i rescue 0
|
84
85
|
"#{root_url}/#{href}?v=#{version}"
|
85
|
-
end
|
86
|
-
|
86
|
+
end
|
87
|
+
|
87
88
|
# Emits an import map scrit tag. If a hash is given, emits the hash as is.
|
88
89
|
# If a string is given, searches for all *.js files under the given path,
|
89
90
|
# and emits an import map including all found files, with versioned URLs.
|
@@ -131,11 +132,11 @@ module Papercraft
|
|
131
132
|
# Returns true if the given tag is a void element, in order to render a self
|
132
133
|
# closing tag. See spec: https://html.spec.whatwg.org/multipage/syntax.html#void-elements.
|
133
134
|
#
|
134
|
-
# @param
|
135
|
+
# @param tag [String] tag
|
135
136
|
# @return [Bool] is it a void element
|
136
137
|
def is_void_element_tag?(tag)
|
137
138
|
case tag
|
138
|
-
#
|
139
|
+
#
|
139
140
|
when 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr'
|
140
141
|
true
|
141
142
|
else
|
@@ -148,7 +149,7 @@ module Papercraft
|
|
148
149
|
# @param text [String] text
|
149
150
|
# @return [String] escaped text
|
150
151
|
def escape_text(text)
|
151
|
-
|
152
|
+
CGI.escapeHTML(text.to_s)
|
152
153
|
end
|
153
154
|
|
154
155
|
# Converts a tag to its string representation. Underscores will be converted
|
data/lib/papercraft/json.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'json'
|
4
4
|
|
5
|
-
module Papercraft
|
5
|
+
module Papercraft
|
6
6
|
# JSON renderer extensions
|
7
7
|
module JSON
|
8
8
|
# Initializes a JSON renderer, setting up an object stack.
|
@@ -15,10 +15,16 @@ module Papercraft
|
|
15
15
|
# block is evaulated against a new object target, then added to the current
|
16
16
|
# array.
|
17
17
|
#
|
18
|
+
# Papercraft.json {
|
19
|
+
# item 'foo'
|
20
|
+
# item 'bar'
|
21
|
+
# }.render #=> "[\"foo\", \"bar\"]"
|
22
|
+
#
|
18
23
|
# @param value [Object] item
|
19
|
-
# @param &block [Proc] template block
|
20
24
|
# @return [void]
|
21
|
-
def item(value = nil, &block)
|
25
|
+
def item(value = nil, _for: nil, &block)
|
26
|
+
return _for.each { |*a| item(value) { block.(*a)} } if _for
|
27
|
+
|
22
28
|
verify_array_target
|
23
29
|
if block
|
24
30
|
value = enter_object(&block)
|
@@ -32,7 +38,6 @@ module Papercraft
|
|
32
38
|
#
|
33
39
|
# @param key [Object] key
|
34
40
|
# @param value [Object] value
|
35
|
-
# @param &block [Proc] template block
|
36
41
|
# @return [void]
|
37
42
|
def kv(key, value = nil, &block)
|
38
43
|
verify_hash_target
|
@@ -45,12 +50,11 @@ module Papercraft
|
|
45
50
|
# Intercepts method calls by adding key-value pairs to the current object
|
46
51
|
# target.
|
47
52
|
#
|
48
|
-
# @param key [
|
53
|
+
# @param key [Symbol] key
|
49
54
|
# @param value [Object] value
|
50
|
-
# @param &block [Proc] template block
|
51
55
|
# @return [void]
|
52
|
-
def method_missing(
|
53
|
-
kv(
|
56
|
+
def method_missing(key, value = nil, &block)
|
57
|
+
kv(key, value, &block)
|
54
58
|
end
|
55
59
|
|
56
60
|
# Converts the root object target to JSON.
|
@@ -64,7 +68,6 @@ module Papercraft
|
|
64
68
|
|
65
69
|
# Adds a new entry to the object stack and evaluates the given block.
|
66
70
|
#
|
67
|
-
# @param &block [Proc] template block
|
68
71
|
# @return [void]
|
69
72
|
def with_object(&block)
|
70
73
|
@object_stack << nil
|
@@ -115,12 +118,11 @@ module Papercraft
|
|
115
118
|
# Adds a new object to the object stack, evaluates the given template block,
|
116
119
|
# then pops the object off the stack.
|
117
120
|
#
|
118
|
-
# @param &block [Proc] template block
|
119
121
|
# @return [void]
|
120
122
|
def enter_object(&block)
|
121
123
|
@object_stack << nil
|
122
124
|
instance_eval(&block)
|
123
125
|
@object_stack.pop
|
124
|
-
end
|
126
|
+
end
|
125
127
|
end
|
126
128
|
end
|
data/lib/papercraft/renderer.rb
CHANGED
@@ -6,7 +6,7 @@ require_relative './json'
|
|
6
6
|
require_relative './extension_proxy'
|
7
7
|
|
8
8
|
module Papercraft
|
9
|
-
|
9
|
+
|
10
10
|
# A Renderer renders a Papercraft template into a string
|
11
11
|
class Renderer
|
12
12
|
|
@@ -46,7 +46,7 @@ module Papercraft
|
|
46
46
|
# the the renderer instance, so they can look just like normal proc
|
47
47
|
# components. In cases where method names in the module clash with XML
|
48
48
|
# tag names, you can use the `#tag` method to emit the relevant tag.
|
49
|
-
#
|
49
|
+
#
|
50
50
|
# module ComponentLibrary
|
51
51
|
# def card(title, content)
|
52
52
|
# div(class: 'card') {
|
@@ -84,7 +84,8 @@ module Papercraft
|
|
84
84
|
# renderer's scope.
|
85
85
|
#
|
86
86
|
# @param &template [Proc] template block
|
87
|
-
def initialize(&template)
|
87
|
+
def initialize(render_fragment = nil, &template)
|
88
|
+
@render_fragment = render_fragment
|
88
89
|
instance_eval(&template)
|
89
90
|
end
|
90
91
|
|
@@ -96,9 +97,9 @@ module Papercraft
|
|
96
97
|
#
|
97
98
|
# greeter = proc { |name| h1 "Hello, #{name}!" }
|
98
99
|
# Papercraft.html { emit(greeter, 'world') }.render #=> "<h1>Hello, world!</h1>"
|
99
|
-
#
|
100
|
+
#
|
100
101
|
# Papercraft.html { emit 'hi&<bye>' }.render #=> "hi&<bye>"
|
101
|
-
#
|
102
|
+
#
|
102
103
|
# Papercraft.html { emit nil }.render #=> ""
|
103
104
|
#
|
104
105
|
# @param o [Proc, Papercraft::Template, String] emitted object
|
@@ -119,7 +120,7 @@ module Papercraft
|
|
119
120
|
end
|
120
121
|
alias_method :e, :emit
|
121
122
|
|
122
|
-
# Emits a block supplied using
|
123
|
+
# Emits a block supplied using {Template#apply} or {Template#render}.
|
123
124
|
#
|
124
125
|
# div_wrap = Papercraft.html { |*args| div { emit_yield(*args) } }
|
125
126
|
# greeter = div_wrap.apply { |name| h1 "Hello, #{name}!" }
|
@@ -131,10 +132,37 @@ module Papercraft
|
|
131
132
|
def emit_yield(*a, **b)
|
132
133
|
block = @emit_yield_stack&.pop
|
133
134
|
raise Papercraft::Error, "No block given" unless block
|
134
|
-
|
135
|
+
|
135
136
|
instance_exec(*a, **b, &block)
|
136
137
|
end
|
137
|
-
|
138
|
+
|
139
|
+
# Defines a named template fragment. Template fragments allow rendering
|
140
|
+
# specific parts of the same template. A template fragment can be rendered
|
141
|
+
# using {Template#render_fragment}. See also
|
142
|
+
# {https://htmx.org/essays/template-fragments/ HTMX template fragments}.
|
143
|
+
#
|
144
|
+
# form = Papercraft.html {
|
145
|
+
# h1 'Hello'
|
146
|
+
# fragment(:buttons) {
|
147
|
+
# button 'OK'
|
148
|
+
# button 'Cancel'
|
149
|
+
# }
|
150
|
+
# }
|
151
|
+
# form.render_fragment(:buttons) #=> "<button>OK</button><button>Cancel</buttons>"
|
152
|
+
#
|
153
|
+
# @param name [Symbol, String] fragment name
|
154
|
+
# @return [void]
|
155
|
+
def fragment(name, &block)
|
156
|
+
raise Papercraft::Error, "Already in fragment" if @fragment
|
157
|
+
|
158
|
+
begin
|
159
|
+
@fragment = name
|
160
|
+
emit(block)
|
161
|
+
ensure
|
162
|
+
@fragment = nil
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
138
166
|
private
|
139
167
|
|
140
168
|
# Pushes the given block onto the emit_yield stack.
|
data/lib/papercraft/tags.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative './extension_proxy'
|
4
|
+
require 'escape_utils'
|
4
5
|
|
5
|
-
module Papercraft
|
6
|
+
module Papercraft
|
6
7
|
# Markup (HTML/XML) extensions
|
7
8
|
module Tags
|
8
9
|
S_LT = '<'
|
@@ -22,7 +23,11 @@ module Papercraft
|
|
22
23
|
S_TAG_%<TAG>s_PRE = %<tag_pre>s
|
23
24
|
S_TAG_%<TAG>s_CLOSE = %<tag_close>s
|
24
25
|
|
25
|
-
def %<tag>s(text = nil, **props, &block)
|
26
|
+
def %<tag>s(text = nil, _for: nil, **props, &block)
|
27
|
+
return if @render_fragment && @fragment != @render_fragment
|
28
|
+
|
29
|
+
return _for.each { |*a| %<tag>s(text, **props) { block.(*a)} } if _for
|
30
|
+
|
26
31
|
if text.is_a?(Hash) && props.empty?
|
27
32
|
props = text
|
28
33
|
text = nil
|
@@ -52,7 +57,11 @@ module Papercraft
|
|
52
57
|
S_TAG_%<TAG>s_PRE = %<tag_pre>s
|
53
58
|
S_TAG_%<TAG>s_CLOSE = %<tag_close>s
|
54
59
|
|
55
|
-
def %<tag>s(text = nil, **props, &block)
|
60
|
+
def %<tag>s(text = nil, _for: nil, **props, &block)
|
61
|
+
return if @render_fragment && @fragment != @render_fragment
|
62
|
+
|
63
|
+
return _for.each { |*a| %<tag>s(text, **props) { block.(*a)} } if _for
|
64
|
+
|
56
65
|
if text.is_a?(Hash) && props.empty?
|
57
66
|
props = text
|
58
67
|
text = nil
|
@@ -80,9 +89,9 @@ module Papercraft
|
|
80
89
|
INITIAL_BUFFER_CAPACITY = 8192
|
81
90
|
|
82
91
|
# Initializes a tag renderer.
|
83
|
-
def initialize(&template)
|
92
|
+
def initialize(render_fragment = nil, &template)
|
84
93
|
@buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
|
85
|
-
super
|
94
|
+
super(render_fragment)
|
86
95
|
end
|
87
96
|
|
88
97
|
# Returns the rendered template.
|
@@ -130,7 +139,6 @@ module Papercraft
|
|
130
139
|
# h1 'content'
|
131
140
|
# }
|
132
141
|
#
|
133
|
-
# @param &block [Proc] Deferred block to be emitted
|
134
142
|
# @return [void]
|
135
143
|
def defer(&block)
|
136
144
|
if !@parts
|
@@ -141,8 +149,8 @@ module Papercraft
|
|
141
149
|
end
|
142
150
|
@buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
|
143
151
|
end
|
144
|
-
|
145
|
-
|
152
|
+
|
153
|
+
|
146
154
|
# Emits an XML tag with the given content, properties and optional block.
|
147
155
|
# This method is an alternative to emitting XML tags using dynamically
|
148
156
|
# created methods. This is particularly useful when using extensions that
|
@@ -157,9 +165,12 @@ module Papercraft
|
|
157
165
|
# @param sym [Symbol, String] XML tag
|
158
166
|
# @param text [String, nil] tag content
|
159
167
|
# @param **props [Hash] tag attributes
|
160
|
-
# @param &block [Proc] optional inner XML
|
161
168
|
# @return [void]
|
162
|
-
def tag(sym, text = nil, **props, &block)
|
169
|
+
def tag(sym, text = nil, _for: nil, **props, &block)
|
170
|
+
return if @render_fragment && @fragment != @render_fragment
|
171
|
+
|
172
|
+
return _for.each { |*a| tag(sym, text, **props) { block.(*a)} } if _for
|
173
|
+
|
163
174
|
if text.is_a?(Hash) && props.empty?
|
164
175
|
props = text
|
165
176
|
text = nil
|
@@ -189,10 +200,9 @@ module Papercraft
|
|
189
200
|
|
190
201
|
# Catches undefined tag method call and handles it by defining the method.
|
191
202
|
#
|
192
|
-
# @param sym [Symbol]
|
203
|
+
# @param sym [Symbol] tag or component identifier
|
193
204
|
# @param args [Array] method arguments
|
194
205
|
# @param opts [Hash] named method arguments
|
195
|
-
# @param &block [Proc] block passed to method
|
196
206
|
# @return [void]
|
197
207
|
def method_missing(sym, *args, **opts, &block)
|
198
208
|
tag = sym.to_s
|
@@ -211,6 +221,8 @@ module Papercraft
|
|
211
221
|
# @param data [String] text
|
212
222
|
# @return [void]
|
213
223
|
def text(data)
|
224
|
+
return if @render_fragment && @fragment != @render_fragment
|
225
|
+
|
214
226
|
@buffer << escape_text(data)
|
215
227
|
end
|
216
228
|
|
@@ -233,8 +245,8 @@ module Papercraft
|
|
233
245
|
# @param tag [Symbol, String] tag/method name
|
234
246
|
# @param block [Proc] method body
|
235
247
|
# @return [void]
|
236
|
-
def def_tag(
|
237
|
-
self.class.define_method(
|
248
|
+
def def_tag(tag, &block)
|
249
|
+
self.class.define_method(tag, &block)
|
238
250
|
end
|
239
251
|
|
240
252
|
alias_method :orig_extend, :extend
|
@@ -327,17 +339,18 @@ module Papercraft
|
|
327
339
|
# @param obj [Object] emitted object
|
328
340
|
# @return [void]
|
329
341
|
def emit_object(obj)
|
342
|
+
return if @render_fragment && @fragment != @render_fragment
|
343
|
+
|
330
344
|
@buffer << obj.to_s
|
331
345
|
end
|
332
346
|
|
333
347
|
# Renders a deferred proc by evaluating it, then adding the rendered result
|
334
348
|
# to the buffer.
|
335
349
|
#
|
336
|
-
# @param &block [Proc] deferred proc
|
337
350
|
# @return [void]
|
338
351
|
def render_deferred_proc(&block)
|
339
352
|
old_buffer = @buffer
|
340
|
-
|
353
|
+
|
341
354
|
@buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
|
342
355
|
@parts = nil
|
343
356
|
|
@@ -379,8 +392,7 @@ module Papercraft
|
|
379
392
|
props.each { |k, v|
|
380
393
|
case k
|
381
394
|
when :src, :href
|
382
|
-
@buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
|
383
|
-
EscapeUtils.escape_uri(v) << S_QUOTE
|
395
|
+
@buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE << EscapeUtils.escape_uri(v) << S_QUOTE
|
384
396
|
else
|
385
397
|
case v
|
386
398
|
when true
|
data/lib/papercraft/template.rb
CHANGED
@@ -85,7 +85,7 @@ module Papercraft
|
|
85
85
|
# links_with_anchors.render
|
86
86
|
#
|
87
87
|
class Template < Proc
|
88
|
-
|
88
|
+
|
89
89
|
# Determines the rendering mode: `:html` or `:xml`.
|
90
90
|
attr_accessor :mode
|
91
91
|
|
@@ -107,14 +107,15 @@ module Papercraft
|
|
107
107
|
@mime_type = mime_type || STOCK_MIME_TYPE[mode]
|
108
108
|
super(&block)
|
109
109
|
end
|
110
|
-
|
110
|
+
|
111
111
|
H_EMPTY = {}.freeze
|
112
|
-
|
112
|
+
|
113
113
|
# Renders the template with the given parameters and or block, and returns
|
114
114
|
# the string result.
|
115
115
|
#
|
116
|
-
# @param
|
117
|
-
# @
|
116
|
+
# @param *params [any] unnamed parameters
|
117
|
+
# @param **named_params [any] named parameters
|
118
|
+
# @return [String] rendered string
|
118
119
|
def render(*a, **b, &block)
|
119
120
|
template = self
|
120
121
|
Renderer.verify_proc_parameters(template, a, b)
|
@@ -123,7 +124,33 @@ module Papercraft
|
|
123
124
|
instance_exec(*a, **b, &template)
|
124
125
|
end.to_s
|
125
126
|
end
|
126
|
-
|
127
|
+
|
128
|
+
# Renders a template fragment. Any given parameters are passed to the
|
129
|
+
# template just like with {Template#render}. See also
|
130
|
+
# {https://htmx.org/essays/template-fragments/ HTMX template fragments}.
|
131
|
+
#
|
132
|
+
# form = Papercraft.html { |action|
|
133
|
+
# h1 'Hello'
|
134
|
+
# fragment(:buttons) {
|
135
|
+
# button action
|
136
|
+
# button 'Cancel'
|
137
|
+
# }
|
138
|
+
# }
|
139
|
+
# form.render_fragment(:buttons, 'foo') #=> "<button>foo</button><button>Cancel</buttons>"
|
140
|
+
#
|
141
|
+
# @param name [Symbol, String] fragment name
|
142
|
+
# @param *params [any] unnamed parameters
|
143
|
+
# @param **named_params [any] named parameters
|
144
|
+
# @return [String] rendered string
|
145
|
+
def render_fragment(name, *a, **b, &block)
|
146
|
+
template = self
|
147
|
+
Renderer.verify_proc_parameters(template, a, b)
|
148
|
+
renderer_class.new(name) do
|
149
|
+
push_emit_yield_block(block) if block
|
150
|
+
instance_exec(*a, **b, &template)
|
151
|
+
end.to_s
|
152
|
+
end
|
153
|
+
|
127
154
|
# Creates a new template, applying the given parameters and or block to the
|
128
155
|
# current one. Application is one of the principal methods of composing
|
129
156
|
# templates, particularly when passing inner templates as blocks:
|
@@ -140,7 +167,6 @@ module Papercraft
|
|
140
167
|
#
|
141
168
|
# @param *a [<any>] normal parameters
|
142
169
|
# @param **b [Hash] named parameters
|
143
|
-
# @param &block [Proc] inner block
|
144
170
|
# @return [Papercraft::Template] applied template
|
145
171
|
def apply(*a, **b, &block)
|
146
172
|
template = self
|
@@ -149,7 +175,7 @@ module Papercraft
|
|
149
175
|
instance_exec(*a, *x, **b, **y, &template)
|
150
176
|
end)
|
151
177
|
end
|
152
|
-
|
178
|
+
|
153
179
|
# Returns the Renderer class used for rendering the templates, according to
|
154
180
|
# the template's mode.
|
155
181
|
#
|
@@ -173,7 +199,7 @@ module Papercraft
|
|
173
199
|
def mime_type
|
174
200
|
@mime_type
|
175
201
|
end
|
176
|
-
|
202
|
+
|
177
203
|
# def compile
|
178
204
|
# Papercraft::Compiler.new.compile(self)
|
179
205
|
# end
|
data/lib/papercraft/version.rb
CHANGED
data/lib/papercraft/xml.rb
CHANGED
data/lib/papercraft.rb
CHANGED
@@ -13,9 +13,9 @@ require_relative 'papercraft/renderer'
|
|
13
13
|
module Papercraft
|
14
14
|
# Exception class used to signal templating-related errors
|
15
15
|
class Error < RuntimeError; end
|
16
|
-
|
16
|
+
|
17
17
|
class << self
|
18
|
-
|
18
|
+
|
19
19
|
# Installs one or more extensions. Extensions enhance templating capabilities
|
20
20
|
# by adding namespaced methods to emplates. An extension is implemented as a
|
21
21
|
# Ruby module containing one or more methods. Each method in the extension
|
@@ -29,7 +29,7 @@ module Papercraft
|
|
29
29
|
def extension(map)
|
30
30
|
Renderer.extension(map)
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
# Creates a new papercraft template. `Papercraft.html` can take either a proc
|
34
34
|
# argument or a block. In both cases, the proc is converted to a
|
35
35
|
# `Papercraft::Template`.
|
@@ -44,7 +44,7 @@ module Papercraft
|
|
44
44
|
template ||= o
|
45
45
|
Papercraft::Template.new(mode: :html, mime_type: mime_type, &template)
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
# Creates a new Papercraft template in XML mode. `Papercraft.xml` can take
|
49
49
|
# either a proc argument or a block. In both cases, the proc is converted to a
|
50
50
|
# `Papercraft::Template`.
|
@@ -59,7 +59,7 @@ module Papercraft
|
|
59
59
|
template ||= o
|
60
60
|
Papercraft::Template.new(mode: :xml, mime_type: mime_type, &template)
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
# Creates a new Papercraft template in JSON mode. `Papercraft.json` can take
|
64
64
|
# either a proc argument or a block. In both cases, the proc is converted to a
|
65
65
|
# `Papercraft::Template`.
|
@@ -79,13 +79,13 @@ module Papercraft
|
|
79
79
|
# default Kramdown options in order to change the rendering behaviour.
|
80
80
|
#
|
81
81
|
# @param markdown [String] Markdown
|
82
|
-
# @param
|
82
|
+
# @param opts [Hash] Kramdown option overrides
|
83
83
|
# @return [String] HTML
|
84
84
|
def markdown(markdown, **opts)
|
85
85
|
opts = default_kramdown_options.merge(opts)
|
86
86
|
Kramdown::Document.new(markdown, **opts).to_html
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
# Returns the default Kramdown options used for rendering Markdown.
|
90
90
|
#
|
91
91
|
# @return [Hash] Kramdown options
|
@@ -94,7 +94,7 @@ module Papercraft
|
|
94
94
|
entity_output: :numeric,
|
95
95
|
syntax_highlighter: :rouge,
|
96
96
|
input: 'GFM',
|
97
|
-
hard_wrap: false
|
97
|
+
hard_wrap: false
|
98
98
|
}
|
99
99
|
end
|
100
100
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: papercraft
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '1.
|
4
|
+
version: '1.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: escape_utils
|
@@ -16,42 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
19
|
+
version: 1.3.0
|
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
|
-
version: 1.
|
26
|
+
version: 1.3.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: kramdown
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 2.
|
33
|
+
version: 2.4.0
|
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.
|
40
|
+
version: 2.4.0
|
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:
|
47
|
+
version: 4.1.3
|
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:
|
54
|
+
version: 4.1.3
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: kramdown-parser-gfm
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '5.
|
75
|
+
version: '5.19'
|
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.19'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: benchmark-ips
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,15 +114,15 @@ dependencies:
|
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: 2.
|
117
|
+
version: 2.2.0
|
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
|
-
version: 2.
|
125
|
-
description:
|
124
|
+
version: 2.2.0
|
125
|
+
description:
|
126
126
|
email: sharon@noteflakes.com
|
127
127
|
executables: []
|
128
128
|
extensions: []
|
@@ -153,7 +153,7 @@ metadata:
|
|
153
153
|
documentation_uri: https://www.rubydoc.info/gems/papercraft
|
154
154
|
homepage_uri: https://github.com/digital-fabric/papercraft
|
155
155
|
changelog_uri: https://github.com/digital-fabric/papercraft/blob/master/CHANGELOG.md
|
156
|
-
post_install_message:
|
156
|
+
post_install_message:
|
157
157
|
rdoc_options:
|
158
158
|
- "--title"
|
159
159
|
- Papercraft
|
@@ -173,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
173
|
version: '0'
|
174
174
|
requirements: []
|
175
175
|
rubygems_version: 3.4.1
|
176
|
-
signing_key:
|
176
|
+
signing_key:
|
177
177
|
specification_version: 4
|
178
178
|
summary: 'Papercraft: component-based HTML templating for Ruby'
|
179
179
|
test_files: []
|