papercraft 1.1 → 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: 41c765213957fc2681e2f4cd7d94e7580e45394128d61a800349aa6e848c6b00
4
- data.tar.gz: 5c2b0a91c3d4c744db20536d08ae741a07dab3a9cfc2c5fa790ea987224b5c7e
3
+ metadata.gz: 5ce5c8e506f96cd5e49625ba875bec30df1fe7849cf726e715383edf1feec4dd
4
+ data.tar.gz: fa5a04e6baf5b8e8e36d08a8e9bc962da886c48881489f699b1abab51bc06181
5
5
  SHA512:
6
- metadata.gz: cf87f3ae1bcf718ad7b03e5b1b895cd8065e2aee72f7044cf0fb822db029477aa98297b8caf66d9e3db1ee664d9e42a954afb203f856fa6f190765defe5139e1
7
- data.tar.gz: bfd2847daa9f2d9be7a0aa7e0adee170771e3fcdd6ca0ad407478f7b5bff2848b63639d21c26d1b9d040ac44b741d9032ac24742e839e19f1327d81ffd88e901
6
+ metadata.gz: 9fd2b6f1ce19479f6a57a5ca75f238b7fb7dee33a3275c2d7f786a1630a5ea5ca5f2d3210946c2ee1dd55676dce65fe0d3503bab830980e5d11fef216a76b5fd
7
+ data.tar.gz: 32bd431564ba19783822b5f0596d6f13fc9bd3fee6c498b0d5a9d9722bd536f19ee90fec74a8933460324fbe47d1eab860f3700b6d07bc904c60b131933c5a7b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 1.2 2023-08-21
2
+
3
+ - Update dependencies
4
+ - Implement template fragments
5
+
1
6
  ## 1.1 2023-07-03
2
7
 
3
8
  - Add direct iteration using the `_for` attribute
data/README.md CHANGED
@@ -72,10 +72,11 @@ hello.render('world')
72
72
  - [Adding Tags](#adding-tags)
73
73
  - [Tag and Attribute Formatting](#tag-and-attribute-formatting)
74
74
  - [Escaping Content](#escaping-content)
75
- - [Direct Iteration][#direct-iteration]
75
+ - [Direct Iteration](#direct-iteration)
76
76
  - [Template Parameters](#template-parameters)
77
77
  - [Template Logic](#template-logic)
78
78
  - [Template Blocks](#template-blocks)
79
+ - [Template Fragments](#template-fragments)
79
80
  - [Plain Procs as Templates](#plain-procs-as-templates)
80
81
  - [Template Composition](#template-composition)
81
82
  - [Parameter and Block Application](#parameter-and-block-application)
@@ -381,6 +382,35 @@ page = Papercraft.html {
381
382
  page.render { h1 'hi' }
382
383
  ```
383
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
+
384
414
  ## Plain Procs as Templates
385
415
 
386
416
  With Papercraft you can write a template as a plain Ruby proc, and later render
@@ -24,7 +24,6 @@ module Papercraft
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,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative './tags'
4
+ require 'cgi'
4
5
 
5
6
  module Papercraft
6
7
  # HTML Markup extensions
@@ -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)
@@ -131,7 +132,7 @@ 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
@@ -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
@@ -15,8 +15,12 @@ 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
25
  def item(value = nil, _for: nil, &block)
22
26
  return _for.each { |*a| item(value) { block.(*a)} } if _for
@@ -34,7 +38,6 @@ module Papercraft
34
38
  #
35
39
  # @param key [Object] key
36
40
  # @param value [Object] value
37
- # @param &block [Proc] template block
38
41
  # @return [void]
39
42
  def kv(key, value = nil, &block)
40
43
  verify_hash_target
@@ -47,12 +50,11 @@ module Papercraft
47
50
  # Intercepts method calls by adding key-value pairs to the current object
48
51
  # target.
49
52
  #
50
- # @param key [Object] key
53
+ # @param key [Symbol] key
51
54
  # @param value [Object] value
52
- # @param &block [Proc] template block
53
55
  # @return [void]
54
- def method_missing(sym, value = nil, &block)
55
- kv(sym, value, &block)
56
+ def method_missing(key, value = nil, &block)
57
+ kv(key, value, &block)
56
58
  end
57
59
 
58
60
  # Converts the root object target to JSON.
@@ -66,7 +68,6 @@ module Papercraft
66
68
 
67
69
  # Adds a new entry to the object stack and evaluates the given block.
68
70
  #
69
- # @param &block [Proc] template block
70
71
  # @return [void]
71
72
  def with_object(&block)
72
73
  @object_stack << nil
@@ -117,7 +118,6 @@ module Papercraft
117
118
  # Adds a new object to the object stack, evaluates the given template block,
118
119
  # then pops the object off the stack.
119
120
  #
120
- # @param &block [Proc] template block
121
121
  # @return [void]
122
122
  def enter_object(&block)
123
123
  @object_stack << nil
@@ -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
 
@@ -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}!" }
@@ -135,6 +136,33 @@ module Papercraft
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,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative './extension_proxy'
4
+ require 'escape_utils'
4
5
 
5
6
  module Papercraft
6
7
  # Markup (HTML/XML) extensions
@@ -23,6 +24,8 @@ module Papercraft
23
24
  S_TAG_%<TAG>s_CLOSE = %<tag_close>s
24
25
 
25
26
  def %<tag>s(text = nil, _for: nil, **props, &block)
27
+ return if @render_fragment && @fragment != @render_fragment
28
+
26
29
  return _for.each { |*a| %<tag>s(text, **props) { block.(*a)} } if _for
27
30
 
28
31
  if text.is_a?(Hash) && props.empty?
@@ -55,6 +58,8 @@ module Papercraft
55
58
  S_TAG_%<TAG>s_CLOSE = %<tag_close>s
56
59
 
57
60
  def %<tag>s(text = nil, _for: nil, **props, &block)
61
+ return if @render_fragment && @fragment != @render_fragment
62
+
58
63
  return _for.each { |*a| %<tag>s(text, **props) { block.(*a)} } if _for
59
64
 
60
65
  if text.is_a?(Hash) && props.empty?
@@ -84,9 +89,9 @@ module Papercraft
84
89
  INITIAL_BUFFER_CAPACITY = 8192
85
90
 
86
91
  # Initializes a tag renderer.
87
- def initialize(&template)
92
+ def initialize(render_fragment = nil, &template)
88
93
  @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
89
- super
94
+ super(render_fragment)
90
95
  end
91
96
 
92
97
  # Returns the rendered template.
@@ -134,7 +139,6 @@ module Papercraft
134
139
  # h1 'content'
135
140
  # }
136
141
  #
137
- # @param &block [Proc] Deferred block to be emitted
138
142
  # @return [void]
139
143
  def defer(&block)
140
144
  if !@parts
@@ -161,9 +165,10 @@ module Papercraft
161
165
  # @param sym [Symbol, String] XML tag
162
166
  # @param text [String, nil] tag content
163
167
  # @param **props [Hash] tag attributes
164
- # @param &block [Proc] optional inner XML
165
168
  # @return [void]
166
169
  def tag(sym, text = nil, _for: nil, **props, &block)
170
+ return if @render_fragment && @fragment != @render_fragment
171
+
167
172
  return _for.each { |*a| tag(sym, text, **props) { block.(*a)} } if _for
168
173
 
169
174
  if text.is_a?(Hash) && props.empty?
@@ -195,10 +200,9 @@ module Papercraft
195
200
 
196
201
  # Catches undefined tag method call and handles it by defining the method.
197
202
  #
198
- # @param sym [Symbol] XML tag or component identifier
203
+ # @param sym [Symbol] tag or component identifier
199
204
  # @param args [Array] method arguments
200
205
  # @param opts [Hash] named method arguments
201
- # @param &block [Proc] block passed to method
202
206
  # @return [void]
203
207
  def method_missing(sym, *args, **opts, &block)
204
208
  tag = sym.to_s
@@ -217,6 +221,8 @@ module Papercraft
217
221
  # @param data [String] text
218
222
  # @return [void]
219
223
  def text(data)
224
+ return if @render_fragment && @fragment != @render_fragment
225
+
220
226
  @buffer << escape_text(data)
221
227
  end
222
228
 
@@ -239,8 +245,8 @@ module Papercraft
239
245
  # @param tag [Symbol, String] tag/method name
240
246
  # @param block [Proc] method body
241
247
  # @return [void]
242
- def def_tag(sym, &block)
243
- self.class.define_method(sym, &block)
248
+ def def_tag(tag, &block)
249
+ self.class.define_method(tag, &block)
244
250
  end
245
251
 
246
252
  alias_method :orig_extend, :extend
@@ -333,13 +339,14 @@ module Papercraft
333
339
  # @param obj [Object] emitted object
334
340
  # @return [void]
335
341
  def emit_object(obj)
342
+ return if @render_fragment && @fragment != @render_fragment
343
+
336
344
  @buffer << obj.to_s
337
345
  end
338
346
 
339
347
  # Renders a deferred proc by evaluating it, then adding the rendered result
340
348
  # to the buffer.
341
349
  #
342
- # @param &block [Proc] deferred proc
343
350
  # @return [void]
344
351
  def render_deferred_proc(&block)
345
352
  old_buffer = @buffer
@@ -385,8 +392,7 @@ module Papercraft
385
392
  props.each { |k, v|
386
393
  case k
387
394
  when :src, :href
388
- @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
389
- EscapeUtils.escape_uri(v) << S_QUOTE
395
+ @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE << EscapeUtils.escape_uri(v) << S_QUOTE
390
396
  else
391
397
  case v
392
398
  when true
@@ -113,8 +113,9 @@ module Papercraft
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)
@@ -124,6 +125,32 @@ module Papercraft
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Papercraft
4
- VERSION = '1.1'
4
+ VERSION = '1.2'
5
5
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'escape_utils'
4
3
  require_relative './tags'
4
+ require 'escape_utils'
5
5
 
6
6
  module Papercraft
7
7
  # XML renderer extensions
data/lib/papercraft.rb CHANGED
@@ -79,7 +79,7 @@ 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)
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.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-07-03 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: []