papercraft 1.0 → 1.2
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 +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: []
|