papercraft 2.24 → 3.0.1

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: ea6b8d53d5110e44678302644fef523e5e9735b7f862f67b20a9f1bb4883c8b8
4
- data.tar.gz: 3aacb8f0fb578c691230aefc5a1c0c16b1ddae30666004e977094f1f4a7793da
3
+ metadata.gz: 16f2be33c18fddd9873081dce306913a4d047f16d3c9ad52e11c013bcf61d68b
4
+ data.tar.gz: 2cd0c3724af7c10bb1310de2ff1b3943535919a7293451d2d81b69960b8354fa
5
5
  SHA512:
6
- metadata.gz: 3014b7b8a22b5128f5aecb7fd7e7c181d6af5fd1f0727f71dafaa1994f16fda643c6f80aad04fcffc332042e392c11803d9f75d4b18ec8704edc94f374a927ab
7
- data.tar.gz: e8d2017554d39c57d18ccd4ddf3a39f5fc4592d1c16584c47f5c31028a131c28379b43adad55557b70191df34d32f28d4e78989ed8d59700fc0f78bfd69dfcde
6
+ metadata.gz: 97b5f5fad9b4856b1c1d2bf7452ca23c08baf64ff3ff25a99ee625df444e0bf41d1233e0bd9de82f65c4fe14cdc556d8c41523be67a02638b3afd6d358566af2
7
+ data.tar.gz: a04d7ed27195679adf453ceb510c6ea94cfb35fb16510e19b6aafb62d636fed6bf868f91bb4b7a1a62574cf7f02cf5c24cf9d0e234decb1e0ea8e8c42d44cb0b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # 3.0.1 2025-10-21
2
+
3
+ - Update Sirop, Prism dependencies
4
+
5
+ # 3.0.0 2025-10-19
6
+
7
+ - Improve implementation of `Papercraft.apply`
8
+ - Add support for rendering self-closing XML tags
9
+ - Streamline Papercraft API
10
+ - Add support for `Papercraft.render { ... }`
11
+ - Prefix internal Proc extensions with `__papercraft_`
12
+ - Change API to use `Papercraft.html` instead of `Proc#render`. Same for
13
+ `apple`, `render_xml` etc.
14
+
1
15
  # 2.24 2025-10-14
2
16
 
3
17
  - Update gem links
data/README.md CHANGED
@@ -21,10 +21,12 @@
21
21
  ```ruby
22
22
  require 'papercraft'
23
23
 
24
- -> {
25
- h1 "Hello from Papercraft!"
26
- }.render
27
- #=> "<h1>Hello from Papercraft</h1>"
24
+ Papercraft.html {
25
+ div {
26
+ h1 "Hello from Papercraft!"
27
+ }
28
+ }
29
+ #=> "<div><h1>Hello from Papercraft</h1></div>"
28
30
  ```
29
31
 
30
32
  Papercraft is a templating engine for dynamically producing HTML in Ruby apps.
@@ -164,12 +164,23 @@ module Papercraft
164
164
  # adjust_whitespace(node.location)
165
165
  is_void = is_void_element?(tag)
166
166
  is_raw_inner_text = is_raw_inner_text_element?(tag)
167
+ is_empty = !node.block && !node.inner_text
167
168
 
168
- if is_void && (node.block || node.inner_text)
169
+ if is_void && !is_empty
169
170
  raise Papercraft::Error, "Void element #{tag} cannot contain child nodes or inner text"
170
171
  end
171
172
 
172
- emit_html(node.tag_location, format_html_tag_open(node.tag_location, tag, node.attributes))
173
+ if @mode == :xml && is_empty
174
+ emit_html(
175
+ node.tag_location,
176
+ format_xml_tag_self_closing(node.tag_location, tag, node.attributes)
177
+ )
178
+ return
179
+ end
180
+
181
+ emit_html(
182
+ node.tag_location, format_html_tag_open(node.tag_location, tag, node.attributes)
183
+ )
173
184
  return if is_void
174
185
 
175
186
  case node.block
@@ -178,7 +189,7 @@ module Papercraft
178
189
  when Prism::BlockArgumentNode
179
190
  flush_html_parts!
180
191
  adjust_whitespace(node.block)
181
- emit("; #{format_code(node.block.expression)}.__compiled_proc__.(__buffer__)")
192
+ emit("; #{format_code(node.block.expression)}.__papercraft_compiled_proc.(__buffer__)")
182
193
  end
183
194
 
184
195
  if node.inner_text
@@ -213,7 +224,7 @@ module Papercraft
213
224
  emit(format_code(node.call_node.receiver))
214
225
  emit('::')
215
226
  end
216
- emit("#{node.call_node.name}.__compiled_proc__.(__buffer__")
227
+ emit("#{node.call_node.name}.__papercraft_compiled_proc.(__buffer__")
217
228
  if node.call_node.arguments
218
229
  emit(', ')
219
230
  visit(node.call_node.arguments)
@@ -229,17 +240,17 @@ module Papercraft
229
240
  args = node.call_node.arguments.arguments
230
241
  first_arg = args.first
231
242
 
232
- block_embed = node.block && "&(->(__buffer__) #{format_code(node.block)}.__compiled__!)"
243
+ block_embed = node.block && "&(->(__buffer__) #{format_code(node.block)}.__papercraft_compiled!)"
233
244
  block_embed = ", #{block_embed}" if block_embed && node.call_node.arguments
234
245
 
235
246
  flush_html_parts!
236
247
  adjust_whitespace(node.location)
237
248
 
238
249
  if args.length == 1
239
- emit("; #{format_code(first_arg)}.__compiled_proc__.(__buffer__#{block_embed})")
250
+ emit("; #{format_code(first_arg)}.__papercraft_compiled_proc.(__buffer__#{block_embed})")
240
251
  else
241
252
  args_code = format_code_comma_separated_nodes(args[1..])
242
- emit("; #{format_code(first_arg)}.__compiled_proc__.(__buffer__, #{args_code}#{block_embed})")
253
+ emit("; #{format_code(first_arg)}.__papercraft_compiled_proc.(__buffer__, #{args_code}#{block_embed})")
243
254
  end
244
255
  end
245
256
 
@@ -336,7 +347,7 @@ module Papercraft
336
347
  def visit_extension_tag_node(node)
337
348
  flush_html_parts!
338
349
  adjust_whitespace(node.location)
339
- emit("; Papercraft::Extensions[#{node.tag.inspect}].__compiled_proc__.(__buffer__")
350
+ emit("; Papercraft::Extensions[#{node.tag.inspect}].__papercraft_compiled_proc.(__buffer__")
340
351
  if node.call_node.arguments
341
352
  emit(', ')
342
353
  visit(node.call_node.arguments)
@@ -367,7 +378,7 @@ module Papercraft
367
378
  end
368
379
  block_params = block_params.empty? ? '' : ", #{block_params.join(', ')}"
369
380
 
370
- emit(", &(proc { |__buffer__#{block_params}| #{block_body} }).__compiled__!")
381
+ emit(", &(proc { |__buffer__#{block_params}| #{block_body} }).__papercraft_compiled!")
371
382
  end
372
383
  emit(")")
373
384
  end
@@ -382,7 +393,7 @@ module Papercraft
382
393
  guard = @render_yield_used ?
383
394
  '' : "; raise(LocalJumpError, 'no block given (render_yield)') if !__block__"
384
395
  @render_yield_used = true
385
- emit("#{guard}; __block__.__compiled_proc__.(__buffer__")
396
+ emit("#{guard}; __block__.__papercraft_compiled_proc.(__buffer__")
386
397
  if node.call_node.arguments
387
398
  emit(', ')
388
399
  visit(node.call_node.arguments)
@@ -398,7 +409,7 @@ module Papercraft
398
409
  flush_html_parts!
399
410
  adjust_whitespace(node.location)
400
411
  @render_children_used = true
401
- emit("; __block__&.__compiled_proc__&.(__buffer__")
412
+ emit("; __block__&.__papercraft_compiled_proc&.(__buffer__")
402
413
  if node.call_node.arguments
403
414
  emit(', ')
404
415
  visit(node.call_node.arguments)
@@ -410,7 +421,7 @@ module Papercraft
410
421
  flush_html_parts!
411
422
  adjust_whitespace(node.location)
412
423
 
413
- emit("; #{node.call_node.receiver.name}.__compiled_proc__.(__buffer__")
424
+ emit("; #{node.call_node.receiver.name}.__papercraft_compiled_proc.(__buffer__")
414
425
  if node.call_node.arguments
415
426
  emit(', ')
416
427
  visit(node.call_node.arguments)
@@ -418,7 +429,7 @@ module Papercraft
418
429
  if node.call_node.block
419
430
  emit(", &(->")
420
431
  visit(node.call_node.block)
421
- emit(").__compiled_proc__")
432
+ emit(").__papercraft_compiled_proc")
422
433
  end
423
434
  emit(")")
424
435
  end
@@ -489,6 +500,15 @@ module Papercraft
489
500
  RAW_INNER_TEXT_TAGS.include?(tag.to_s)
490
501
  end
491
502
 
503
+ def format_xml_tag_self_closing(loc, tag, attributes)
504
+ tag = convert_tag(tag)
505
+ if attributes && attributes&.elements.size > 0 || @@html_debug_attribute_injector
506
+ "<#{tag} #{format_html_attributes(loc, attributes)}/>"
507
+ else
508
+ "<#{tag}/>"
509
+ end
510
+ end
511
+
492
512
  # Formats an open tag with optional attributes.
493
513
  #
494
514
  # @param loc [Prism::Location] tag location
@@ -8,76 +8,34 @@ module Papercraft
8
8
  # Returns true if proc is marked as compiled.
9
9
  #
10
10
  # @return [bool] is the proc marked as compiled
11
- def __compiled__?
12
- @__compiled__
11
+ def __papercraft_compiled?
12
+ @__papercraft_compiled
13
13
  end
14
14
 
15
15
  # Marks the proc as compiled, i.e. can render directly and takes a string
16
16
  # buffer as first argument.
17
17
  #
18
18
  # @return [self]
19
- def __compiled__!
20
- @__compiled__ = true
19
+ def __papercraft_compiled!
20
+ @__papercraft_compiled = true
21
21
  self
22
22
  end
23
23
 
24
- # Returns the compiled proc for the given proc. If marked as compiled, returns
24
+ # Returns the compiled proc for the proc. If marked as compiled, returns
25
25
  # self.
26
26
  #
27
27
  # @param mode [Symbol] compilation mode (:html, :xml)
28
28
  # @return [Proc] compiled proc or self
29
- def __compiled_proc__(mode: :html)
30
- @__compiled_proc__ ||= @__compiled__ ? self : Papercraft.compile(self, mode:)
29
+ def __papercraft_compiled_proc(mode: :html)
30
+ @__papercraft_compiled_proc ||= @__papercraft_compiled ?
31
+ self : Papercraft.compile(self, mode:)
31
32
  end
32
33
 
33
- # Renders the proc to HTML with the given arguments.
34
+ # Returns the render cache for the proc.
34
35
  #
35
- # @return [String] HTML string
36
- def render(*a, **b, &c)
37
- __compiled_proc__.(+'', *a, **b, &c)
38
- rescue Exception => e
39
- e.is_a?(Papercraft::Error) ? raise : raise(Papercraft.translate_backtrace(e))
40
- end
41
-
42
- # Renders the proc to XML with the given arguments.
43
- #
44
- # @return [String] XML string
45
- def render_xml(*a, **b, &c)
46
- __compiled_proc__(mode: :xml).(+'', *a, **b, &c)
47
- rescue Exception => e
48
- e.is_a?(Papercraft::Error) ? raise : raise(Papercraft.translate_backtrace(e))
49
- end
50
-
51
- # Returns a proc that applies the given arguments to the original proc. The
52
- # returned proc calls the *compiled* form of the proc, merging the
53
- # positional and keywords parameters passed to `#apply` with parameters
54
- # passed to the applied proc. If a block is given, it is wrapped in a proc
55
- # that passed merged parameters to the block.
56
- #
57
- # @param *pos1 [Array<any>] applied positional parameters
58
- # @param **kw1 [Hash<any, any] applied keyword parameters
59
- # @return [Proc] applied proc
60
- def apply(*pos1, **kw1, &block)
61
- compiled = __compiled_proc__
62
- c_compiled = block&.__compiled_proc__
63
-
64
- ->(__buffer__, *pos2, **kw2, &block2) {
65
- c_proc = c_compiled && ->(__buffer__, *pos3, **kw3) {
66
- c_compiled.(__buffer__, *pos3, **kw3, &block2)
67
- }.__compiled__!
68
-
69
- compiled.(__buffer__, *pos1, *pos2, **kw1, **kw2, &c_proc)
70
- }.__compiled__!
71
- end
72
-
73
- # Caches and returns the rendered HTML for the template with the given
74
- # arguments.
75
- #
76
- # @param key [any] Cache key
77
- # @return [String] HTML string
78
- def render_cache(key, *args, **kargs, &block)
79
- @render_cache ||= {}
80
- @render_cache[key] ||= render(*args, **kargs, &block)
36
+ # @return [Hash] cache hash
37
+ def __papercraft_render_cache
38
+ @__papercraft_render_cache ||= {}
81
39
  end
82
40
  end
83
41
  end
@@ -8,21 +8,34 @@ module Papercraft
8
8
 
9
9
  # @param proc [Proc] template proc
10
10
  # @param mode [Symbol] mode (:html, :xml)
11
- def initialize(proc, mode: :html)
12
- @proc = proc
11
+ def initialize(proc = nil, mode: :html, &block)
12
+ @proc = proc || block
13
+ raise ArgumentError, "No template proc given" if !@proc
14
+
13
15
  @mode = mode
14
16
  end
15
17
 
18
+ # Renders the template.
19
+ #
20
+ # @return [String] generated HTML
16
21
  def render(*, **, &)
17
- (mode == :xml) ? @proc.render_xml(*, **, &) : @proc.render(*, **, &)
22
+ (mode == :xml) ? Papercraft.xml(@proc, *, **, &) : Papercraft.html(@proc, *, **, &)
18
23
  end
24
+ alias_method :call, :render
19
25
 
26
+ # Applies the given parameters and block to the template, returning an
27
+ # applied template.
28
+ #
29
+ # @return [Papercraft::Template] applied template
20
30
  def apply(*, **, &)
21
- Template.new(@proc.apply(*, **, &), mode: @mode)
31
+ Template.new(Papercraft.apply(@proc, *, **, &), mode: @mode)
22
32
  end
23
33
 
24
- def __compiled_proc__
25
- @proc.__compiled_proc__(mode: @mode)
34
+ # Returns the compiled proc for the template.
35
+ #
36
+ # @return [Proc] compiled proc
37
+ def __papercraft_compiled_proc
38
+ @proc.__papercraft_compiled_proc(mode: @mode)
26
39
  end
27
40
  end
28
41
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Papercraft
4
- VERSION = '2.24'
4
+ VERSION = '3.0.1'
5
5
  end
data/lib/papercraft.rb CHANGED
@@ -114,10 +114,11 @@ module Papercraft
114
114
  # @param opts [Hash] Kramdown option overrides
115
115
  # @return [Kramdown::Document] Kramdown document
116
116
  def markdown_doc(markdown, **opts)
117
- @markdown_deps_loaded ||= true.tap do
117
+ @markdown_deps_loaded ||= begin
118
118
  require 'kramdown'
119
119
  require 'rouge'
120
120
  require 'kramdown-parser-gfm'
121
+ true
121
122
  end
122
123
 
123
124
  opts = default_kramdown_options.merge(opts)
@@ -168,7 +169,7 @@ module Papercraft
168
169
  # @return [Array<String>] source map
169
170
  def source_map(proc)
170
171
  loc = proc.source_location
171
- fn = proc.__compiled__? ? loc.first : Papercraft::Compiler.source_location_to_fn(loc)
172
+ fn = proc.__papercraft_compiled? ? loc.first : Papercraft::Compiler.source_location_to_fn(loc)
172
173
  Papercraft::Compiler.source_map_store[fn]
173
174
  end
174
175
 
@@ -186,8 +187,96 @@ module Papercraft
186
187
  # @param mode [Symbol] compilation mode (:html, :xml)
187
188
  # @return [Proc] compiled proc
188
189
  def compile(proc, mode: :html)
189
- Papercraft::Compiler.compile(proc, mode:).__compiled__!
190
+ Papercraft::Compiler.compile(proc, mode:).__papercraft_compiled!
190
191
  rescue Sirop::Error
191
192
  raise Papercraft::Error, "Can't compile eval'd template"
192
193
  end
194
+
195
+ # Renders the given template to HTML with the given arguments. The template
196
+ # can be passed either as the first parameter, or as a block, if no parameter
197
+ # is given.
198
+ #
199
+ # @param template [Proc] template proc
200
+ # @return [String] HTML string
201
+ def html(template = nil, *pos, **kw, &block)
202
+ if !template
203
+ template = block
204
+ elsif template.is_a?(Template)
205
+ template = template.proc
206
+ end
207
+ raise ArgumentError, "No template given" if !template
208
+
209
+ template.__papercraft_compiled_proc.(+'', *pos, **kw, &block)
210
+ rescue Exception => e
211
+ e.is_a?(Papercraft::Error) ? raise : raise(Papercraft.translate_backtrace(e))
212
+ end
213
+
214
+ # Renders the given template to XML with the given arguments. The template can
215
+ # be passed either as the first parameter, or as a block, if no parameter is
216
+ # given.
217
+ #
218
+ # @param template [Proc] template proc
219
+ # @return [String] XML string
220
+ def xml(template = nil, *pos, **kw, &block)
221
+ if !template
222
+ template = block
223
+ elsif template.is_a?(Template)
224
+ template = template.proc
225
+ end
226
+ raise ArgumentError, "No template given" if !template
227
+
228
+ template = template.proc if template.is_a?(Template)
229
+ template.__papercraft_compiled_proc(mode: :xml).(+'', *pos, **kw, &block)
230
+ rescue Exception => e
231
+ e.is_a?(Papercraft::Error) ? raise : raise(Papercraft.translate_backtrace(e))
232
+ end
233
+
234
+ # Returns a proc that applies the given arguments to the original proc. The
235
+ # returned proc calls the *compiled* form of the proc, merging the
236
+ # positional and keywords parameters passed to `#apply` with parameters
237
+ # passed to the applied proc. If a block is given, it is wrapped in a proc
238
+ # that passed merged parameters to the block.
239
+ #
240
+ # @param template [Proc] template proc
241
+ # @param *pos1 [Array<any>] applied positional parameters
242
+ # @param **kw1 [Hash<any, any] applied keyword parameters
243
+ # @return [Proc] applied proc
244
+ def apply(template, *pos1, **kw1, &block1)
245
+ template = template.proc if template.is_a?(Template)
246
+ compiled = template.__papercraft_compiled_proc
247
+ block1_compiled = block1&.__papercraft_compiled_proc
248
+
249
+ ->(__buffer__, *pos2, **kw2, &block2) {
250
+ if block2
251
+ block2_compiled = block1_compiled ?
252
+ ->(__buffer__, *pos3, **kw3) {
253
+ block1_compiled.(__buffer__, *pos3, **kw3, &block2)
254
+ }.__papercraft_compiled! :
255
+ block2.__papercraft_compiled_proc
256
+ compiled.(__buffer__, *pos1, *pos2, **kw1, **kw2, &block2_compiled)
257
+ else
258
+ compiled.(__buffer__, *pos1, *pos2, **kw1, **kw2, &block1_compiled)
259
+ end
260
+ }.__papercraft_compiled!
261
+ end
262
+
263
+ # Caches and returns the rendered HTML for the template with the given
264
+ # arguments.
265
+ #
266
+ # @param template [Proc] template proc
267
+ # @param key [any] Cache key
268
+ # @return [String] HTML string
269
+ def cache_html(template, key, *, **, &)
270
+ template.__papercraft_render_cache[key] ||= html(template, *, **, &)
271
+ end
272
+
273
+ # Caches and returns the rendered XML for the template with the given
274
+ # arguments.
275
+ #
276
+ # @param template [Proc] template proc
277
+ # @param key [any] Cache key
278
+ # @return [String] XML string
279
+ def cache_xml(template, key, *, **, &)
280
+ template.__papercraft_render_cache[key] ||= xml(template, *, **, &)
281
+ end
193
282
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: papercraft
3
3
  version: !ruby/object:Gem::Version
4
- version: '2.24'
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '1.0'
18
+ version: 1.0.1
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '1.0'
25
+ version: 1.0.1
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: kramdown
28
28
  requirement: !ruby/object:Gem::Requirement