papercraft 0.29 → 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: 1ca707074311c909d6e2b4aa86748f1ffbd5b614d73fb78e85985d174b69e263
4
- data.tar.gz: f53dc4fa5088472ccc805f50938f25539950446fe14e97facb2cc346722d13fe
3
+ metadata.gz: 41c765213957fc2681e2f4cd7d94e7580e45394128d61a800349aa6e848c6b00
4
+ data.tar.gz: 5c2b0a91c3d4c744db20536d08ae741a07dab3a9cfc2c5fa790ea987224b5c7e
5
5
  SHA512:
6
- metadata.gz: 0d216f2c8db4beed8c7fe184d3b8e0a13ba8af4684d4e46f5506f0b1eb6c3bfaa0ad76b7a5b1b6e28a8ba37a730545ca00139fd0bf35b74628d117c504fc1e9c
7
- data.tar.gz: 27ac04cf8b75b5d6abda511513618fb6ef20417365ac7b45855d909323db3c536204f85d719a44e2374b095d955f4eb0c25901290990dab861c908fa4d73142e
6
+ metadata.gz: cf87f3ae1bcf718ad7b03e5b1b895cd8065e2aee72f7044cf0fb822db029477aa98297b8caf66d9e3db1ee664d9e42a954afb203f856fa6f190765defe5139e1
7
+ data.tar.gz: bfd2847daa9f2d9be7a0aa7e0adee170771e3fcdd6ca0ad407478f7b5bff2848b63639d21c26d1b9d040ac44b741d9032ac24742e839e19f1327d81ffd88e901
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 1.1 2023-07-03
2
+
3
+ - Add direct iteration using the `_for` attribute
4
+
5
+ ## 1.0 2023-03-30
6
+
7
+ - Add support for Array attribute values
8
+
1
9
  ## 0.29 2023-03-11
2
10
 
3
11
  - Add Tilt integration (#15)
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)
@@ -84,6 +86,7 @@ hello.render('world')
84
86
  - [Emitting Markdown](#emitting-markdown)
85
87
  - [Working with MIME Types](#working-with-mime-types)
86
88
  - [Deferred Evaluation](#deferred-evaluation)
89
+ - [HTML Templates](#html-templates)
87
90
  - [XML Templates](#xml-templates)
88
91
  - [JSON Templates](#json-templates)
89
92
  - [Papercraft Extensions](#papercraft-extensions)
@@ -129,7 +132,7 @@ html.render #=> "<div id="greeter"><p>Hello!</p></div>"
129
132
 
130
133
  ## Using with Tilt
131
134
 
132
- Papercraft templates can also be rendered using Tilt:
135
+ Papercraft templates can also be rendered using [Tilt](https://github.com/jeremyevans/tilt):
133
136
 
134
137
  ```ruby
135
138
  require 'tilt/papercraft'
@@ -198,14 +201,28 @@ Papercraft.html { hr() }.render #=> "<hr/>"
198
201
  Tag methods also accept tag attributes, given as a hash:
199
202
 
200
203
  ```ruby
201
- Papercraft.html { img src: '/my.gif' }.render #=> "<img src="/my.gif"/>
204
+ Papercraft.html { img src: '/my.gif' }.render #=> "<img src=\"/my.gif\"/>"
202
205
 
203
206
  Papercraft.html { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
204
207
  ```
205
208
 
209
+ A `true` attribute value will emit a valueless attribute. A `nil` or `false`
210
+ attribute value will emit nothing:
211
+
212
+ ```ruby
213
+ Papercraft.html { button disabled: nil }.render #=> "<button></button>"
214
+ Papercraft.html { button disabled: true }.render #=> "<button disabled></button>"
215
+ ```
216
+
217
+ An attribute value given as an array will be joined by space characters:
218
+
219
+ ```ruby
220
+ Papercraft.html { div class: [:foo, :bar] }.render #=> "<div class=\"foo bar\"></div>"
221
+ ```
222
+
206
223
  ## Tag and Attribute Formatting
207
224
 
208
- Papercraft does not make any presumption about what tags and attributes you can
225
+ Papercraft does not make any assumption about what tags and attributes you can
209
226
  use. You can mix upper and lower case letters, and you can include arbitrary
210
227
  characters in tag and attribute names. However, in order to best adhere to the
211
228
  HTML and XML specs and common practices, tag names and attributes will be
@@ -256,6 +273,66 @@ below](#emitting-raw-html).
256
273
  JSON templates are rendered using the `json` gem bundled with Ruby, which takes
257
274
  care of escaping text values.
258
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
+
259
336
  ## Template Parameters
260
337
 
261
338
  In Papercraft, parameters are always passed explicitly. This means that template
@@ -276,7 +353,7 @@ greeting.render(name: 'world') #=> "<h1>Hello, world!</h1>"
276
353
 
277
354
  ## Template Logic
278
355
 
279
- Since Papercraft templates are just a bunch of Ruby, you can easily write your
356
+ Since Papercraft templates are just a bunch of Ruby, you can easily embed your
280
357
  view logic right in the template:
281
358
 
282
359
  ```ruby
@@ -585,6 +662,24 @@ page = default_layout.apply {
585
662
  }
586
663
  ```
587
664
 
665
+ ## HTML Templates
666
+
667
+ HTML templates include a few HTML-specific methods to facilitate writing modern
668
+ HTML:
669
+
670
+ - `html5 { ... }` - emits an HTML 5 DOCTYPE (`<!DOCTYPE html>`)
671
+ - `import_map(root_path, root_url)` - emits an import map including all files
672
+ matching `<root_path>/*.js`, based on the given `root_url`
673
+ - `js_module(js)` - emits a `<script type="module">` element
674
+ - `link_stylesheet(href, **attributes)` - emits a `<link rel="stylesheet" ...>`
675
+ element
676
+ - `script(js, **attributes)` - emits an inline `<script>` element
677
+ - `style(css, **attributes)` - emits an inline `<style>` element
678
+ - `versioned_file_href(href, root_path, root_url)` - calculates a versioned href
679
+ for the given file
680
+
681
+ [HTML docs](https://www.rubydoc.info/gems/papercraft/Papercraft/HTML)
682
+
588
683
  ## XML Templates
589
684
 
590
685
  XML templates behave largely the same as HTML templates, with a few minor
@@ -620,6 +715,8 @@ rss = Papercraft.xml(mime_type: 'text/xml; charset=utf-8') { |resource:, **props
620
715
  }
621
716
  ```
622
717
 
718
+ [XML docs](https://www.rubydoc.info/gems/papercraft/Papercraft/XML)
719
+
623
720
  ## JSON Templates
624
721
 
625
722
  JSON templates behave largely the same as HTML and XML templates. The only major
@@ -651,6 +748,8 @@ Papercraft.json {
651
748
  Papercraft uses the [JSON gem](https://rubyapi.org/3.1/o/json) under the hood in
652
749
  order to generate actual JSON.
653
750
 
751
+ [JSON docs](https://www.rubydoc.info/gems/papercraft/Papercraft/JSON)
752
+
654
753
  ## Papercraft Extensions
655
754
 
656
755
  Papercraft extensions are modules that contain one or more methods that can be
@@ -857,6 +956,8 @@ xml = Papercraft.xml {
857
956
  }
858
957
  ```
859
958
 
959
+ [SOAP docs](https://www.rubydoc.info/gems/papercraft/Papercraft/Extensions/Soap)
960
+
860
961
  ## API Reference
861
962
 
862
963
  The API reference for this library can be found
@@ -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
 
@@ -387,6 +393,10 @@ module Papercraft
387
393
  @buffer << S_SPACE << att_repr(k)
388
394
  when false, nil
389
395
  # emit nothing
396
+ when Array
397
+ v = v.join(' ')
398
+ @buffer << S_SPACE << att_repr(k) <<
399
+ S_EQUAL_QUOTE << escape_text(v) << S_QUOTE
390
400
  else
391
401
  @buffer << S_SPACE << att_repr(k) <<
392
402
  S_EQUAL_QUOTE << escape_text(v) << S_QUOTE
@@ -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 = '0.29'
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: '0.29'
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-11 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