papercraft 1.1 → 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: 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: []