papercraft 1.0 → 1.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: d769e6ec71d8c6d60ebb0aa727037b691562a31ec1fc1a05ccb0f25f29858b7a
4
- data.tar.gz: 97b15299e8b4ca3d801aec881bd0ea4cee245650225f9c6e41972333ab9f8011
3
+ metadata.gz: 41c765213957fc2681e2f4cd7d94e7580e45394128d61a800349aa6e848c6b00
4
+ data.tar.gz: 5c2b0a91c3d4c744db20536d08ae741a07dab3a9cfc2c5fa790ea987224b5c7e
5
5
  SHA512:
6
- metadata.gz: ff74adc23fc616b579f9729cecc595e2eebe92d2b96b6edcceff4eae043c7ca8048e3baea14f275533667809183cd7961e14c74a6afb71b04fe7902806c8678c
7
- data.tar.gz: 8fcecbac3baa12296ae3386ecb31eb0db0d7e001f4f38dd1c055ab497a3113471338d26cca8812582091e10c17ab5413c1e37cdf172d07ca1eac011206935d90
6
+ metadata.gz: cf87f3ae1bcf718ad7b03e5b1b895cd8065e2aee72f7044cf0fb822db029477aa98297b8caf66d9e3db1ee664d9e42a954afb203f856fa6f190765defe5139e1
7
+ data.tar.gz: bfd2847daa9f2d9be7a0aa7e0adee170771e3fcdd6ca0ad407478f7b5bff2848b63639d21c26d1b9d040ac44b741d9032ac24742e839e19f1327d81ffd88e901
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 1.1 2023-07-03
2
+
3
+ - Add direct iteration using the `_for` attribute
4
+
1
5
  ## 1.0 2023-03-30
2
6
 
3
7
  - 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,6 +72,7 @@ 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)
@@ -130,7 +132,7 @@ html.render #=> "<div id="greeter"><p>Hello!</p></div>"
130
132
 
131
133
  ## Using with Tilt
132
134
 
133
- Papercraft templates can also be rendered using Tilt:
135
+ Papercraft templates can also be rendered using [Tilt](https://github.com/jeremyevans/tilt):
134
136
 
135
137
  ```ruby
136
138
  require 'tilt/papercraft'
@@ -271,6 +273,66 @@ below](#emitting-raw-html).
271
273
  JSON templates are rendered using the `json` gem bundled with Ruby, which takes
272
274
  care of escaping text values.
273
275
 
276
+ ## Direct Iteration
277
+
278
+ Papercraft enables iterating directly over any enumerable data source. Instead
279
+ of rendering each item in a given data container by wrapping it inside of an
280
+ `#each` block, we can simply pass the data source *directly* to the tag using
281
+ the `_for` attribute. This is particularly useful when we need to create a set
282
+ of nested tags for each item. Consider the following example:
283
+
284
+ ```ruby
285
+ data = %w{foo bar}
286
+
287
+ Papercraft.html {
288
+ data.each { |item|
289
+ tr {
290
+ td item
291
+ }
292
+ }
293
+ }.render #=> '<tr><td>foo</td></tr><tr><td>bar</td></tr>'
294
+ ```
295
+
296
+ Instead of using `data.each` to iterate over the list of data, we can directly
297
+ pass the data source to the `tr` tag using the special `_for` attribute:
298
+
299
+ ```ruby
300
+ Papercraft.html {
301
+ tr(_for: data) { |item|
302
+ td item
303
+ }
304
+ }.render #=> '<tr><td>foo</td></tr><tr><td>bar</td></tr>'
305
+ ```
306
+
307
+ Note that this will work with any data source that is an `Enumerable` or an
308
+ `Enumerator`. For example, you can use `#each_with_index` or iterate over a
309
+ hash. Papercraft will pass all yielded values to the given block:
310
+
311
+ ```ruby
312
+ data = %{foo bar}
313
+ Papercraft.html {
314
+ tr(_for: data.each_with_index) { |item, idx|
315
+ td idx + 1
316
+ td item
317
+ }
318
+ }.render #=> '<tr><td>1</td><td>foo</td></tr><tr><td>2</td><td>bar</td></tr>'
319
+
320
+ data = [
321
+ { name: 'foo', age: 16 },
322
+ { name: 'bar', age: 32 }
323
+ ]
324
+ Papercraft.html {
325
+ div(_for: data, class: 'row') { |row|
326
+ div(_for: row) { |k, v|
327
+ span k
328
+ span v
329
+ }
330
+ }
331
+ }.render
332
+ #=> '<div class="row"><div><span>name</span><span>foo</span></div><div><span>age</span><span>16</span></div></div>'
333
+ #=> '<div class="row"><div><span>name</span><span>bar</span></div><div><span>age</span><span>32</span></div></div>'
334
+ ```
335
+
274
336
  ## Template Parameters
275
337
 
276
338
  In Papercraft, parameters are always passed explicitly. This means that template
@@ -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,7 +18,7 @@ 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
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative './tags'
4
4
 
5
- module Papercraft
5
+ module Papercraft
6
6
  # HTML Markup extensions
7
7
  module HTML
8
8
  include Tags
@@ -55,7 +55,7 @@ module Papercraft
55
55
 
56
56
  @buffer << '>' << css << '</style>'
57
57
  end
58
-
58
+
59
59
  # Emits an inline JS script element.
60
60
  #
61
61
  # @param js [String, nil] Javascript code
@@ -82,8 +82,8 @@ module Papercraft
82
82
  fn = File.join(root_path, href)
83
83
  version = File.stat(fn).mtime.to_i rescue 0
84
84
  "#{root_url}/#{href}?v=#{version}"
85
- end
86
-
85
+ end
86
+
87
87
  # Emits an import map scrit tag. If a hash is given, emits the hash as is.
88
88
  # If a string is given, searches for all *.js files under the given path,
89
89
  # and emits an import map including all found files, with versioned URLs.
@@ -135,7 +135,7 @@ module Papercraft
135
135
  # @return [Bool] is it a void element
136
136
  def is_void_element_tag?(tag)
137
137
  case tag
138
- #
138
+ #
139
139
  when 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr'
140
140
  true
141
141
  else
@@ -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.
@@ -18,7 +18,9 @@ module Papercraft
18
18
  # @param value [Object] item
19
19
  # @param &block [Proc] template block
20
20
  # @return [void]
21
- def item(value = nil, &block)
21
+ def item(value = nil, _for: nil, &block)
22
+ return _for.each { |*a| item(value) { block.(*a)} } if _for
23
+
22
24
  verify_array_target
23
25
  if block
24
26
  value = enter_object(&block)
@@ -121,6 +123,6 @@ module Papercraft
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') {
@@ -96,9 +96,9 @@ module Papercraft
96
96
  #
97
97
  # greeter = proc { |name| h1 "Hello, #{name}!" }
98
98
  # Papercraft.html { emit(greeter, 'world') }.render #=> "<h1>Hello, world!</h1>"
99
- #
99
+ #
100
100
  # Papercraft.html { emit 'hi&<bye>' }.render #=> "hi&<bye>"
101
- #
101
+ #
102
102
  # Papercraft.html { emit nil }.render #=> ""
103
103
  #
104
104
  # @param o [Proc, Papercraft::Template, String] emitted object
@@ -131,10 +131,10 @@ module Papercraft
131
131
  def emit_yield(*a, **b)
132
132
  block = @emit_yield_stack&.pop
133
133
  raise Papercraft::Error, "No block given" unless block
134
-
134
+
135
135
  instance_exec(*a, **b, &block)
136
136
  end
137
-
137
+
138
138
  private
139
139
 
140
140
  # Pushes the given block onto the emit_yield stack.
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative './extension_proxy'
4
4
 
5
- module Papercraft
5
+ module Papercraft
6
6
  # Markup (HTML/XML) extensions
7
7
  module Tags
8
8
  S_LT = '<'
@@ -22,7 +22,9 @@ module Papercraft
22
22
  S_TAG_%<TAG>s_PRE = %<tag_pre>s
23
23
  S_TAG_%<TAG>s_CLOSE = %<tag_close>s
24
24
 
25
- def %<tag>s(text = nil, **props, &block)
25
+ def %<tag>s(text = nil, _for: nil, **props, &block)
26
+ return _for.each { |*a| %<tag>s(text, **props) { block.(*a)} } if _for
27
+
26
28
  if text.is_a?(Hash) && props.empty?
27
29
  props = text
28
30
  text = nil
@@ -52,7 +54,9 @@ module Papercraft
52
54
  S_TAG_%<TAG>s_PRE = %<tag_pre>s
53
55
  S_TAG_%<TAG>s_CLOSE = %<tag_close>s
54
56
 
55
- def %<tag>s(text = nil, **props, &block)
57
+ def %<tag>s(text = nil, _for: nil, **props, &block)
58
+ return _for.each { |*a| %<tag>s(text, **props) { block.(*a)} } if _for
59
+
56
60
  if text.is_a?(Hash) && props.empty?
57
61
  props = text
58
62
  text = nil
@@ -141,8 +145,8 @@ module Papercraft
141
145
  end
142
146
  @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
143
147
  end
144
-
145
-
148
+
149
+
146
150
  # Emits an XML tag with the given content, properties and optional block.
147
151
  # This method is an alternative to emitting XML tags using dynamically
148
152
  # created methods. This is particularly useful when using extensions that
@@ -159,7 +163,9 @@ module Papercraft
159
163
  # @param **props [Hash] tag attributes
160
164
  # @param &block [Proc] optional inner XML
161
165
  # @return [void]
162
- def tag(sym, text = nil, **props, &block)
166
+ def tag(sym, text = nil, _for: nil, **props, &block)
167
+ return _for.each { |*a| tag(sym, text, **props) { block.(*a)} } if _for
168
+
163
169
  if text.is_a?(Hash) && props.empty?
164
170
  props = text
165
171
  text = nil
@@ -337,7 +343,7 @@ module Papercraft
337
343
  # @return [void]
338
344
  def render_deferred_proc(&block)
339
345
  old_buffer = @buffer
340
-
346
+
341
347
  @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
342
348
  @parts = nil
343
349
 
@@ -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,9 +107,9 @@ 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
  #
@@ -123,7 +123,7 @@ module Papercraft
123
123
  instance_exec(*a, **b, &template)
124
124
  end.to_s
125
125
  end
126
-
126
+
127
127
  # Creates a new template, applying the given parameters and or block to the
128
128
  # current one. Application is one of the principal methods of composing
129
129
  # templates, particularly when passing inner templates as blocks:
@@ -149,7 +149,7 @@ module Papercraft
149
149
  instance_exec(*a, *x, **b, **y, &template)
150
150
  end)
151
151
  end
152
-
152
+
153
153
  # Returns the Renderer class used for rendering the templates, according to
154
154
  # the template's mode.
155
155
  #
@@ -173,7 +173,7 @@ module Papercraft
173
173
  def mime_type
174
174
  @mime_type
175
175
  end
176
-
176
+
177
177
  # def compile
178
178
  # Papercraft::Compiler.new.compile(self)
179
179
  # end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Papercraft
4
- VERSION = '1.0'
4
+ VERSION = '1.1'
5
5
  end
@@ -3,7 +3,7 @@
3
3
  require 'escape_utils'
4
4
  require_relative './tags'
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`.
@@ -85,7 +85,7 @@ module Papercraft
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.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-30 00:00:00.000000000 Z
11
+ date: 2023-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: escape_utils