papercraft 1.0 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
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: []