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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d769e6ec71d8c6d60ebb0aa727037b691562a31ec1fc1a05ccb0f25f29858b7a
4
- data.tar.gz: 97b15299e8b4ca3d801aec881bd0ea4cee245650225f9c6e41972333ab9f8011
3
+ metadata.gz: 5ce5c8e506f96cd5e49625ba875bec30df1fe7849cf726e715383edf1feec4dd
4
+ data.tar.gz: fa5a04e6baf5b8e8e36d08a8e9bc962da886c48881489f699b1abab51bc06181
5
5
  SHA512:
6
- metadata.gz: ff74adc23fc616b579f9729cecc595e2eebe92d2b96b6edcceff4eae043c7ca8048e3baea14f275533667809183cd7961e14c74a6afb71b04fe7902806c8678c
7
- data.tar.gz: 8fcecbac3baa12296ae3386ecb31eb0db0d7e001f4f38dd1c055ab497a3113471338d26cca8812582091e10c17ab5413c1e37cdf172d07ca1eac011206935d90
6
+ metadata.gz: 9fd2b6f1ce19479f6a57a5ca75f238b7fb7dee33a3275c2d7f786a1630a5ea5ca5f2d3210946c2ee1dd55676dce65fe0d3503bab830980e5d11fef216a76b5fd
7
+ data.tar.gz: 32bd431564ba19783822b5f0596d6f13fc9bd3fee6c498b0d5a9d9722bd536f19ee90fec74a8933460324fbe47d1eab860f3700b6d07bc904c60b131933c5a7b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 1.2 2023-08-21
2
+
3
+ - Update dependencies
4
+ - Implement template fragments
5
+
6
+ ## 1.1 2023-07-03
7
+
8
+ - Add direct iteration using the `_for` attribute
9
+
1
10
  ## 1.0 2023-03-30
2
11
 
3
12
  - Add support for Array attribute values
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 HTML, XML or JSON.
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)
@@ -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 text [String] tag
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
- EscapeUtils.escape_html(text.to_s)
152
+ CGI.escapeHTML(text.to_s)
152
153
  end
153
154
 
154
155
  # Converts a tag to its string representation. Underscores will be converted
@@ -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 [Object] 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(sym, value = nil, &block)
53
- kv(sym, value, &block)
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
@@ -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 `Component#apply` or `Component#render`.
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.
@@ -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] XML tag or component identifier
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(sym, &block)
237
- self.class.define_method(sym, &block)
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
@@ -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 context [Hash] context
117
- # @return [String]
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Papercraft
4
- VERSION = '1.0'
4
+ VERSION = '1.2'
5
5
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'escape_utils'
4
3
  require_relative './tags'
4
+ require 'escape_utils'
5
5
 
6
- module Papercraft
6
+ module Papercraft
7
7
  # XML renderer extensions
8
8
  module XML
9
9
  include Tags
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 **opts [Hash] Kramdown option overrides
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.0'
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-03-30 00:00:00.000000000 Z
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.2.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.2.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.3.1
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.3.1
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: 3.27.0
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: 3.27.0
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.15'
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.15'
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.1.0
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.1.0
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: []