papercraft 1.0 → 1.1

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: 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