papercraft 0.12 → 0.16

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: be072094c35a6f89327b9c88b90264ad3db61cb43311d0e3baca865f403fdbc5
4
- data.tar.gz: 7e565933dd83c48c05bac8c648cbe9b0007bc43f8fc5bd862af46dcd00dfb328
3
+ metadata.gz: 996d95088c85415ca883c27b6b1c98e65af5c046e3121ea08f092ac7bcdcc588
4
+ data.tar.gz: 8fe294225c04dcfc82289fd2b9047760260b814712e83e9bf5ea7d361dad6950
5
5
  SHA512:
6
- metadata.gz: 9b8da36fb3c7298e8121b9c612cb81752609e1e3d1f01a94e4bf939864b29de718884989203077c5c1392926a54205c921adbcaa1bf3aa75d84694b7b4db4dcc
7
- data.tar.gz: 778163311cb86ee425c4617ff54b10e8853d9b92404ef29abac848a4252818386b2e23058c355678c286a5bae3db9d4a7bfb182acb0a4aacf00a042431cc196a
6
+ metadata.gz: 54fdbe164a4a2541153e57b90cdb35ba71b6887fbe2d199505da3389730f1bd937fe0254f8c019b39d81957058c5f51ad71bc900f324e3178b979f368642ee57
7
+ data.tar.gz: fa05ea9ae79a57a3eace2e708b01fe5f44412591875776cf1a2fe95cc6d9d1586357756e702e8a17e5458b3fa4c3e3224290bc234b71fdcd426db0dedff31dc0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## 0.16 2022-01-23
2
+
3
+ - Implement JSON templating (#7)
4
+ - Add support for MIME types (#6)
5
+ - Change entrypoint from `Kernel#H`, `Kernel#X` to `Papercraft.html`, `.xml` (#5)
6
+
7
+ ## 0.15 2022-01-20
8
+
9
+ - Fix tag method line reference
10
+ - Don't clobber ArgumentError exception
11
+
12
+ ## 0.14 2022-01-19
13
+
14
+ - Add support for #emit_yield in applied component (#4)
15
+
16
+ ## 0.13 2022-01-19
17
+
18
+ - Add support for partial parameter application (#3)
19
+
1
20
  ## 0.12 2022-01-06
2
21
 
3
22
  - Improve documentation
@@ -29,7 +48,7 @@
29
48
  ## 0.8 2021-12-22
30
49
 
31
50
  - Cleanup and refactor code
32
- - Add X global method for XML templates
51
+ - Add Papercraft.xml global method for XML templates
33
52
  - Make `Component` a descendant of `Proc`
34
53
  - Introduce new component API
35
54
  - Rename Rubyoshka to Papercraft
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  <h1 align="center">
2
+ <img src="papercraft.png">
3
+ <br>
2
4
  Papercraft
3
5
  </h1>
4
6
 
5
- <h4 align="center">Composable HTML templating for Ruby</h4>
7
+ <h4 align="center">Composable templating for Ruby</h4>
6
8
 
7
9
  <p align="center">
8
10
  <a href="http://rubygems.org/gems/papercraft">
@@ -25,26 +27,28 @@
25
27
  ```ruby
26
28
  require 'papercraft'
27
29
 
28
- page = H { |*args|
30
+ page = Papercraft.html { |*args|
29
31
  html {
30
32
  head { }
31
33
  body { emit_yield *args }
32
34
  }
33
35
  }
36
+ page.render { p 'foo' }
37
+ #=> "<html><head/><body><p>foo</p></body></html>"
34
38
 
35
- hello = H.apply { |name| h1 "Hello, #{name}!" }
39
+ hello = page.apply { |name| h1 "Hello, #{name}!" }
36
40
  hello.render('world')
37
41
  #=> "<html><head/><body><h1>Hello, world!</h1></body></html>"
38
42
  ```
39
43
 
40
- Papercraft is an HTML templating engine for Ruby that offers the following
41
- features:
44
+ Papercraft is a templating engine for Ruby that offers the following features:
42
45
 
43
- - HTML and XML templating using plain Ruby syntax
46
+ - HTML, XML and JSON templating using plain Ruby syntax
44
47
  - Minimal boilerplate
45
48
  - Mix logic and tags freely
46
49
  - Automatic HTML and XML escaping
47
50
  - Composable components
51
+ - Standard or custom MIME types
48
52
  - Explicit parameter passing to nested components
49
53
  - Higher order components
50
54
  - Built-in support for rendering [Markdown](#emitting-markdown)
@@ -71,7 +75,7 @@ To create a template use the global method `Kernel#H`:
71
75
  ```ruby
72
76
  require 'papercraft'
73
77
 
74
- html = H {
78
+ html = Papercraft.html {
75
79
  div(id: 'greeter') { p 'Hello!' }
76
80
  }
77
81
  ```
@@ -87,7 +91,7 @@ html.render #=> "<div id="greeter"><p>Hello!</p></div>"
87
91
  Tags are added using unqualified method calls, and can be nested using blocks:
88
92
 
89
93
  ```ruby
90
- H {
94
+ Papercraft.html {
91
95
  html {
92
96
  head {
93
97
  title 'page title'
@@ -104,19 +108,19 @@ H {
104
108
  Tag methods accept a string argument, a block, or no argument at all:
105
109
 
106
110
  ```ruby
107
- H { p 'hello' }.render #=> "<p>hello</p>"
111
+ Papercraft.html { p 'hello' }.render #=> "<p>hello</p>"
108
112
 
109
- H { p { span '1'; span '2' } }.render #=> "<p><span>1</span><span>2</span></p>"
113
+ Papercraft.html { p { span '1'; span '2' } }.render #=> "<p><span>1</span><span>2</span></p>"
110
114
 
111
- H { hr() }.render #=> "<hr/>"
115
+ Papercraft.html { hr() }.render #=> "<hr/>"
112
116
  ```
113
117
 
114
118
  Tag methods also accept tag attributes, given as a hash:
115
119
 
116
120
  ```ruby
117
- H { img src: '/my.gif' }.render #=> "<img src="/my.gif"/>
121
+ Papercraft.html { img src: '/my.gif' }.render #=> "<img src="/my.gif"/>
118
122
 
119
- H { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
123
+ Papercraft.html { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
120
124
  ```
121
125
 
122
126
  ## Template parameters
@@ -126,14 +130,14 @@ parameters are specified as block parameters, and are passed to the template on
126
130
  rendering:
127
131
 
128
132
  ```ruby
129
- greeting = H { |name| h1 "Hello, #{name}!" }
133
+ greeting = Papercraft.html { |name| h1 "Hello, #{name}!" }
130
134
  greeting.render('world') #=> "<h1>Hello, world!</h1>"
131
135
  ```
132
136
 
133
137
  Templates can also accept named parameters:
134
138
 
135
139
  ```ruby
136
- greeting = H { |name:| h1 "Hello, #{name}!" }
140
+ greeting = Papercraft.html { |name:| h1 "Hello, #{name}!" }
137
141
  greeting.render(name: 'world') #=> "<h1>Hello, world!</h1>"
138
142
  ```
139
143
 
@@ -143,7 +147,7 @@ Since Papercraft templates are just a bunch of Ruby, you can easily write your
143
147
  view logic right in the template:
144
148
 
145
149
  ```ruby
146
- H { |user = nil|
150
+ Papercraft.html { |user = nil|
147
151
  if user
148
152
  span "Hello, #{user.name}!"
149
153
  else
@@ -157,7 +161,7 @@ H { |user = nil|
157
161
  Templates can also accept and render blocks by using `emit_yield`:
158
162
 
159
163
  ```ruby
160
- page = H {
164
+ page = Papercraft.html {
161
165
  html {
162
166
  body { emit_yield }
163
167
  }
@@ -174,14 +178,14 @@ it by passing it as a block to `H`:
174
178
 
175
179
  ```ruby
176
180
  greeting = proc { |name| h1 "Hello, #{name}!" }
177
- H(&greeting).render('world')
181
+ Papercraft.html(&greeting).render('world')
178
182
  ```
179
183
 
180
184
  Components can also be expressed using lambda notation:
181
185
 
182
186
  ```ruby
183
187
  greeting = ->(name) { h1 "Hello, #{name}!" }
184
- H(&greeting).render('world')
188
+ Papercraft.html(&greeting).render('world')
185
189
  ```
186
190
 
187
191
  ## Component composition
@@ -208,7 +212,7 @@ ItemList = ->(items) {
208
212
  }
209
213
  }
210
214
 
211
- page = H { |title, items|
215
+ page = Papercraft.html { |title, items|
212
216
  html5 {
213
217
  head { Title(title) }
214
218
  body { ItemList(items) }
@@ -227,7 +231,7 @@ non-constant components by invoking the `#emit` method:
227
231
  ```ruby
228
232
  greeting = -> { span "Hello, world" }
229
233
 
230
- H {
234
+ Papercraft.html {
231
235
  div {
232
236
  emit greeting
233
237
  }
@@ -245,12 +249,12 @@ or block to the original component:
245
249
 
246
250
  ```ruby
247
251
  # parameter application
248
- hello = H { |name| h1 "Hello, #{name}!" }
252
+ hello = Papercraft.html { |name| h1 "Hello, #{name}!" }
249
253
  hello_world = hello.apply('world')
250
254
  hello_world.render #=> "<h1>Hello, world!</h1>"
251
255
 
252
256
  # block application
253
- div_wrap = H { div { emit_yield } }
257
+ div_wrap = Papercraft.html { div { emit_yield } }
254
258
  wrapped_h1 = div_wrap.apply { h1 'hi' }
255
259
  wrapped_h1.render #=> "<div><h1>hi</h1></div>"
256
260
 
@@ -269,8 +273,8 @@ markup, enhancing components or injecting component parameters.
269
273
  Here is a HOC that takes a component as parameter:
270
274
 
271
275
  ```ruby
272
- div_wrap = H { |inner| div { emit inner } }
273
- greeter = H { h1 'hi' }
276
+ div_wrap = Papercraft.html { |inner| div { emit inner } }
277
+ greeter = Papercraft.html { h1 'hi' }
274
278
  wrapped_greeter = div_wrap.apply(greeter)
275
279
  wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
276
280
  ```
@@ -278,7 +282,7 @@ wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
278
282
  The inner component can also be passed as a block, as shown above:
279
283
 
280
284
  ```ruby
281
- div_wrap = H { div { emit_yield } }
285
+ div_wrap = Papercraft.html { div { emit_yield } }
282
286
  wrapped_greeter = div_wrap.apply { h1 'hi' }
283
287
  wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
284
288
  ```
@@ -292,7 +296,7 @@ this by creating a `default` page template that takes a block, then use `#apply`
292
296
  to create the other templates:
293
297
 
294
298
  ```ruby
295
- default_layout = H { |**params|
299
+ default_layout = Papercraft.html { |**params|
296
300
  html5 {
297
301
  head {
298
302
  title: params[:title]
@@ -321,7 +325,7 @@ article_layout.render(
321
325
  Raw HTML can be emitted using `#emit`:
322
326
 
323
327
  ```ruby
324
- wrapped = H { |html| div { emit html } }
328
+ wrapped = Papercraft.html { |html| div { emit html } }
325
329
  wrapped.render("<h1>hi</h1>") #=> "<div><h1>hi</h1></div>"
326
330
  ```
327
331
 
@@ -331,7 +335,7 @@ To emit a string with proper HTML encoding, without wrapping it in an HTML
331
335
  element, use `#text`:
332
336
 
333
337
  ```ruby
334
- H { str 'hi&lo' }.render #=> "hi&amp;lo"
338
+ Papercraft.html { str 'hi&lo' }.render #=> "hi&amp;lo"
335
339
  ```
336
340
 
337
341
  ## Emitting Markdown
@@ -341,7 +345,7 @@ Markdown is rendered using the
341
345
  `#emit_markdown`:
342
346
 
343
347
  ```ruby
344
- template = H { |md| div { emit_markdown md } }
348
+ template = Papercraft.html { |md| div { emit_markdown md } }
345
349
  template.render("Here's some *Markdown*") #=> "<div><p>Here's some <em>Markdown</em><p>\n</div>"
346
350
  ```
347
351
 
@@ -350,7 +354,7 @@ options](https://kramdown.gettalong.org/options.html#available-options) can be
350
354
  specified by adding them to the `#emit_markdown` call:
351
355
 
352
356
  ```ruby
353
- template = H { |md| div { emit_markdown md, auto_ids: false } }
357
+ template = Papercraft.html { |md| div { emit_markdown md, auto_ids: false } }
354
358
  template.render("# title") #=> "<div><h1>title</h1></div>"
355
359
  ```
356
360
 
@@ -393,7 +397,7 @@ class that can collect JS and CSS dependencies from the different components
393
397
  integrated into the page, and adds them to the page's `<head>` element:
394
398
 
395
399
  ```ruby
396
- default_layout = H { |**args|
400
+ default_layout = Papercraft.html { |**args|
397
401
  @dependencies = DependencyMananger.new
398
402
  head {
399
403
  defer { emit @dependencies.head_markup }
@@ -479,7 +483,7 @@ The call to `Papercraft::extension` lets us access the different methods of
479
483
  we'll be able to express the above markup as follows:
480
484
 
481
485
  ```ruby
482
- H {
486
+ Papercraft.html {
483
487
  bootstrap.card(style: 'width: 18rem') {
484
488
  bootstrap.card_title 'Card title'
485
489
  bootstrap.card_subtitle 'Card subtitle'
@@ -490,6 +494,37 @@ H {
490
494
  }
491
495
  ```
492
496
 
497
+ ## JSON templating
498
+
499
+ You can create a JSON template using the same API used for HTML and XML
500
+ templating. The only difference is that for adding array items you'll need to
501
+ use the `#item` method:
502
+
503
+ ```ruby
504
+ Papercraft.json {
505
+ item 1
506
+ item 2
507
+ item 3
508
+ }.render #=> "[1,2,3]"
509
+ ```
510
+
511
+ Otherwise, you can create arbitrarily complex JSON structures by mixing hashes
512
+ and arrays:
513
+
514
+ ```Ruby
515
+ Papercraft.json {
516
+ foo {
517
+ bar {
518
+ item nil
519
+ item true
520
+ item 123.456
521
+ }
522
+ }
523
+ }.render #=> "{\"foo\":{\"bar\":[null,true,123.456]}}"
524
+ ```
525
+
526
+ Papercraft uses the [JSON gem](https://rubyapi.org/3.1/o/json) under the hood.
527
+
493
528
  ## API Reference
494
529
 
495
530
  The API reference for this library can be found
@@ -11,21 +11,26 @@ module Papercraft
11
11
  # class is simply a special kind of Proc, which has some enhanced
12
12
  # capabilities, allowing it to be easily composed in a variety of ways.
13
13
  #
14
- # Components are usually created using the global methods `H` or `X`, for HTML
15
- # or XML templates, respectively:
14
+ # Components are usually created using the class methods `html`, `xml` or
15
+ # `json`, for HTML, XML or JSON templates, respectively:
16
16
  #
17
- # greeter = H { |name| h1 "Hello, #{name}!" }
17
+ # greeter = Papercraft.html { |name| h1 "Hello, #{name}!" }
18
18
  # greeter.render('world') #=> "<h1>Hello, world!</h1>"
19
19
  #
20
20
  # Components can also be created using the normal constructor:
21
21
  #
22
- # greeter = Papercraft::Component.new { |name| h1 "Hello, #{name}!" }
22
+ # greeter = Papercraft::Component.new(mode: :html) { |name| h1 "Hello, #{name}!" }
23
23
  # greeter.render('world') #=> "<h1>Hello, world!</h1>"
24
24
  #
25
+ # The different methods for creating components can also take a custom MIME
26
+ # type, by passing a `mime_type` named argument:
27
+ #
28
+ # json = Papercraft.json(mime_type: 'application/feed+json') { ... }
29
+ #
25
30
  # In the component block, HTML elements are created by simply calling
26
31
  # unqualified methods:
27
32
  #
28
- # page_layout = H {
33
+ # page_layout = Papercraft.html {
29
34
  # html5 {
30
35
  # head {
31
36
  # title 'foo'
@@ -41,7 +46,7 @@ module Papercraft
41
46
  # `greeter` template shown above takes a single `name` parameter. Here's how a
42
47
  # anchor component could be implemented with named parameters:
43
48
  #
44
- # anchor = H { |uri: , text: | a(text, href: uri) }
49
+ # anchor = Papercraft.html { |uri: , text: | a(text, href: uri) }
45
50
  #
46
51
  # The above component could later be rendered by passing the needed arguments:
47
52
  #
@@ -51,7 +56,7 @@ module Papercraft
51
56
  #
52
57
  # A component can be included in another component using the `emit` method:
53
58
  #
54
- # links = H {
59
+ # links = Papercraft.html {
55
60
  # emit anchor, uri: '/posts', text: 'Posts'
56
61
  # emit anchor, uri: '/archive', text: 'Archive'
57
62
  # emit anchor, uri: '/about', text: 'About'
@@ -60,7 +65,7 @@ module Papercraft
60
65
  # Another way of composing components is to pass the components themselves as
61
66
  # parameters:
62
67
  #
63
- # links = H { |anchors|
68
+ # links = Papercraft.html { |anchors|
64
69
  # anchors.each { |a| emit a }
65
70
  # }
66
71
  # links.render([
@@ -84,14 +89,22 @@ module Papercraft
84
89
  # Determines the rendering mode: `:html` or `:xml`.
85
90
  attr_accessor :mode
86
91
 
92
+ STOCK_MIME_TYPE = {
93
+ html: 'text/html',
94
+ xml: 'application/xml',
95
+ json: 'application/json'
96
+ }.freeze
97
+
87
98
  # Initializes a component with the given block. The rendering mode (HTML or
88
99
  # XML) can be passed in the `mode:` parameter. If `mode:` is not specified,
89
100
  # the component defaults to HTML.
90
101
  #
91
102
  # @param mode [:html, :xml] rendering mode
103
+ # @param mime_type [String, nil] the component's mime type (nil for default)
92
104
  # @param block [Proc] nested HTML block
93
- def initialize(mode: :html, &block)
105
+ def initialize(mode: :html, mime_type: nil, &block)
94
106
  @mode = mode
107
+ @mime_type = mime_type || STOCK_MIME_TYPE[mode]
95
108
  super(&block)
96
109
  end
97
110
 
@@ -106,21 +119,16 @@ module Papercraft
106
119
  template = self
107
120
  Renderer.verify_proc_parameters(template, a, b)
108
121
  renderer_class.new do
109
- if block
110
- with_block(block) { instance_exec(*a, **b, &template) }
111
- else
112
- instance_exec(*a, **b, &template)
113
- end
122
+ push_emit_yield_block(block) if block
123
+ instance_exec(*a, **b, &template)
114
124
  end.to_s
115
- rescue ArgumentError => e
116
- raise Papercraft::Error, e.message
117
125
  end
118
126
 
119
127
  # Creates a new component, applying the given parameters and or block to the
120
128
  # current one. Application is one of the principal methods of composing
121
129
  # components, particularly when passing inner components as blocks:
122
130
  #
123
- # article_wrapper = H {
131
+ # article_wrapper = Papercraft.html {
124
132
  # article {
125
133
  # emit_yield
126
134
  # }
@@ -136,15 +144,10 @@ module Papercraft
136
144
  # @return [Papercraft::Component] applied component
137
145
  def apply(*a, **b, &block)
138
146
  template = self
139
- if block
140
- Component.new(&proc do |*x, **y|
141
- with_block(block) { instance_exec(*x, **y, &template) }
142
- end)
143
- else
144
- Component.new(&proc do |*x, **y|
145
- instance_exec(*a, **b, &template)
146
- end)
147
- end
147
+ Component.new(mode: @mode, mime_type: @mime_type, &proc do |*x, **y|
148
+ push_emit_yield_block(block) if block
149
+ instance_exec(*a, *x, **b, **y, &template)
150
+ end)
148
151
  end
149
152
 
150
153
  # Returns the Renderer class used for rendering the templates, according to
@@ -157,10 +160,16 @@ module Papercraft
157
160
  HTMLRenderer
158
161
  when :xml
159
162
  XMLRenderer
163
+ when :json
164
+ JSONRenderer
160
165
  else
161
166
  raise "Invalid mode #{@mode.inspect}"
162
167
  end
163
168
  end
169
+
170
+ def mime_type
171
+ @mime_type
172
+ end
164
173
 
165
174
  # def compile
166
175
  # Papercraft::Compiler.new.compile(self)
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Papercraft
6
+ # JSON renderer extensions
7
+ module JSON
8
+ def object_stack
9
+ @object_stack ||= [nil]
10
+ end
11
+
12
+ def with_object(&block)
13
+ object_stack << nil
14
+ instance_eval(&block)
15
+ end
16
+
17
+ def verify_array_target
18
+ case object_stack[-1]
19
+ when nil
20
+ object_stack[-1] = []
21
+ when Hash
22
+ raise "Mixing array and hash values"
23
+ end
24
+ end
25
+
26
+ def verify_hash_target
27
+ case object_stack[-1]
28
+ when nil
29
+ object_stack[-1] = {}
30
+ when Array
31
+ raise "Mixing array and hash values"
32
+ end
33
+ end
34
+
35
+ def push_array_item(value)
36
+ object_stack[-1] << value
37
+ end
38
+
39
+ def push_kv_item(key, value)
40
+ object_stack[-1][key] = value
41
+ end
42
+
43
+ def enter_object(&block)
44
+ object_stack << nil
45
+ instance_eval(&block)
46
+ object_stack.pop
47
+ end
48
+
49
+ def item(value = nil, &block)
50
+ verify_array_target
51
+ if block
52
+ value = enter_object(&block)
53
+ end
54
+ push_array_item(value)
55
+ end
56
+
57
+ def kv(key, value, &block)
58
+ verify_hash_target
59
+ if block
60
+ value = enter_object(&block)
61
+ end
62
+ push_kv_item(key, value)
63
+ end
64
+
65
+ def method_missing(sym, value = nil, &block)
66
+ kv(sym, value, &block)
67
+ end
68
+
69
+ def to_s
70
+ object_stack[0].to_json
71
+ end
72
+ end
73
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative './html'
4
+ require_relative './json'
4
5
  require_relative './extension_proxy'
5
6
 
6
7
  module Papercraft
@@ -55,7 +56,7 @@ module Papercraft
55
56
  # end
56
57
  #
57
58
  # Papercraft.extension(components: ComponentLibrary)
58
- # H { components.card('Foo', '**Bar**') }
59
+ # Papercraft.html { components.card('Foo', '**Bar**') }
59
60
  #
60
61
  # @param map [Hash] hash mapping methods to extension modules
61
62
  # @return [void]
@@ -112,7 +113,7 @@ module Papercraft
112
113
 
113
114
  # The tag method template below is optimized for performance. Do not touch!
114
115
 
115
- S_TAG_METHOD_LINE = __LINE__ + 1
116
+ S_TAG_METHOD_LINE = __LINE__ + 2
116
117
  S_TAG_METHOD = <<~EOF
117
118
  S_TAG_%<TAG>s_PRE = %<tag_pre>s
118
119
  S_TAG_%<TAG>s_CLOSE = %<tag_close>s
@@ -210,11 +211,11 @@ module Papercraft
210
211
  # `#to_s` which is then added to the rendering buffer, without any escaping.
211
212
  #
212
213
  # greeter = proc { |name| h1 "Hello, #{name}!" }
213
- # H { emit(greeter, 'world') }.render #=> "<h1>Hello, world!</h1>"
214
+ # Papercraft.html { emit(greeter, 'world') }.render #=> "<h1>Hello, world!</h1>"
214
215
  #
215
- # H { emit 'hi&<bye>' }.render #=> "hi&<bye>"
216
+ # Papercraft.html { emit 'hi&<bye>' }.render #=> "hi&<bye>"
216
217
  #
217
- # H { emit nil }.render #=> ""
218
+ # Papercraft.html { emit nil }.render #=> ""
218
219
  #
219
220
  # @param o [Proc, Papercraft::Component, String] emitted object
220
221
  # @param *a [Array<any>] arguments to pass to a proc
@@ -234,7 +235,7 @@ module Papercraft
234
235
 
235
236
  # Emits a block supplied using `Component#apply` or `Component#render`.
236
237
  #
237
- # div_wrap = H { |*args| div { emit_yield(*args) } }
238
+ # div_wrap = Papercraft.html { |*args| div { emit_yield(*args) } }
238
239
  # greeter = div_wrap.apply { |name| h1 "Hello, #{name}!" }
239
240
  # greeter.render('world') #=> "<div><h1>Hello, world!</h1></div>"
240
241
  #
@@ -242,9 +243,10 @@ module Papercraft
242
243
  # @param **b [Hash] named arguments to pass to a proc
243
244
  # @return [void]
244
245
  def emit_yield(*a, **b)
245
- raise Papercraft::Error, "No block given" unless @inner_block
246
+ block = @emit_yield_stack&.pop
247
+ raise Papercraft::Error, "No block given" unless block
246
248
 
247
- instance_exec(*a, **b, &@inner_block)
249
+ instance_exec(*a, **b, &block)
248
250
  end
249
251
 
250
252
  # Defers the given block to be evaluated later. Deferred evaluation allows
@@ -255,7 +257,7 @@ module Papercraft
255
257
  # adding elements to the `<head>` section. Here's how a title can be
256
258
  # controlled from a nested component:
257
259
  #
258
- # layout = H {
260
+ # layout = Papercraft.html {
259
261
  # html {
260
262
  # head {
261
263
  # defer { title @title }
@@ -304,19 +306,19 @@ module Papercraft
304
306
  private
305
307
 
306
308
  # Escapes text. This method must be overriden in descendant classes.
309
+ #
310
+ # @param text [String] text to be escaped
307
311
  def escape_text(text)
308
312
  raise NotImplementedError
309
313
  end
310
314
 
311
- # Sets up a block to be called with `#emit_yield`
312
- def with_block(block, &run_block)
313
- old_block = @inner_block
314
- @inner_block = block
315
- instance_eval(&run_block)
316
- ensure
317
- @inner_block = old_block
315
+ # Pushes the given block onto the emit_yield stack.
316
+ #
317
+ # @param block [Proc] block
318
+ def push_emit_yield_block(block)
319
+ (@emit_yield_stack ||= []) << block
318
320
  end
319
-
321
+
320
322
  # Emits tag attributes into the rendering buffer
321
323
  # @param props [Hash] tag attributes
322
324
  # @return [void]
@@ -379,4 +381,8 @@ module Papercraft
379
381
  EscapeUtils.escape_xml(text.to_s)
380
382
  end
381
383
  end
384
+
385
+ class JSONRenderer < Renderer
386
+ include JSON
387
+ end
382
388
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Papercraft
4
- VERSION = '0.12'
4
+ VERSION = '0.16'
5
5
  end
data/lib/papercraft.rb CHANGED
@@ -25,36 +25,49 @@ module Papercraft
25
25
  def self.extension(map)
26
26
  Renderer.extension(map)
27
27
  end
28
- end
29
28
 
30
- # Kernel extensions
31
- module ::Kernel
32
-
33
- # Creates a new papercraft component. `#H` can take either a proc argument or
34
- # a block. In both cases, the proc is converted to a `Papercraft::Component`.
29
+ # Creates a new papercraft component. `Papercraft.html` can take either a proc
30
+ # argument or a block. In both cases, the proc is converted to a
31
+ # `Papercraft::Component`.
32
+ #
33
+ # Papercraft.html(proc { h1 'hi' }).render #=> "<h1>hi</h1>"
34
+ # Papercraft.html { h1 'hi' }.render #=> "<h1>hi</h1>"
35
+ #
36
+ # @param template [Proc] template block
37
+ # @return [Papercraft::Component] Papercraft component
38
+ def self.html(o = nil, mime_type: nil, &template)
39
+ return o if o.is_a?(Papercraft::Component)
40
+ template ||= o
41
+ Papercraft::Component.new(mode: :html, mime_type: mime_type, &template)
42
+ end
43
+
44
+ # Creates a new papercraft component in XML mode. `Papercraft.xml` can take
45
+ # either a proc argument or a block. In both cases, the proc is converted to a
46
+ # `Papercraft::Component`.
35
47
  #
36
- # H(proc { h1 'hi' }).render #=> "<h1>hi</h1>"
37
- # H { h1 'hi' }.render #=> "<h1>hi</h1>"
48
+ # Papercraft.xml(proc { item 'foo' }).render #=> "<item>foo</item>"
49
+ # Papercraft.xml { item 'foo' }.render #=> "<item>foo</item>"
38
50
  #
39
51
  # @param template [Proc] template block
40
52
  # @return [Papercraft::Component] Papercraft component
41
- def H(o = nil, &template)
53
+ def self.xml(o = nil, mime_type: nil, &template)
42
54
  return o if o.is_a?(Papercraft::Component)
43
55
  template ||= o
44
- Papercraft::Component.new(mode: :html, &template)
56
+ Papercraft::Component.new(mode: :xml, mime_type: mime_type, &template)
45
57
  end
46
58
 
47
- # Creates a new papercraft component in XML mode. `#X` can take either a proc argument or
48
- # a block. In both cases, the proc is converted to a `Papercraft::Component`.
59
+ # Creates a new papercraft component in JSON mode. `Papercraft.json` can take
60
+ # either a proc argument or a block. In both cases, the proc is converted to a
61
+ # `Papercraft::Component`.
49
62
  #
50
- # X(proc { item 'foo' }).render #=> "<item>foo</item>"
51
- # X { item 'foo' }.render #=> "<item>foo</item>"
63
+ # Papercraft.json(proc { item 42 }).render #=> "[42]"
64
+ # Papercraft.json { foo 'bar' }.render #=> "{\"foo\": \"bar\"}"
52
65
  #
53
66
  # @param template [Proc] template block
54
67
  # @return [Papercraft::Component] Papercraft component
55
- def X(o = nil, &template)
68
+ def self.json(o = nil, mime_type: nil, &template)
56
69
  return o if o.is_a?(Papercraft::Component)
57
70
  template ||= o
58
- Papercraft::Component.new(mode: :xml, &template)
71
+ Papercraft::Component.new(mode: :json, mime_type: mime_type, &template)
59
72
  end
60
73
  end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: papercraft
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.12'
4
+ version: '0.16'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-06 00:00:00.000000000 Z
11
+ date: 2022-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: escape_utils
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '='
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: 1.2.1
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
26
  version: 1.2.1
27
27
  - !ruby/object:Gem::Dependency
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 2.3.0
33
+ version: 2.3.1
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.0
40
+ version: 2.3.1
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.26.0
47
+ version: 3.27.0
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.26.0
54
+ version: 3.27.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: kramdown-parser-gfm
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -70,56 +70,56 @@ dependencies:
70
70
  name: minitest
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '='
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 5.11.3
75
+ version: '5.15'
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.11.3
82
+ version: '5.15'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: benchmark-ips
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '='
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
89
  version: 2.7.2
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '='
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: 2.7.2
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: erubis
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - '='
101
+ - - "~>"
102
102
  - !ruby/object:Gem::Version
103
103
  version: 2.7.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - '='
108
+ - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: 2.7.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: tilt
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - '='
115
+ - - "~>"
116
116
  - !ruby/object:Gem::Version
117
117
  version: 2.0.9
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
124
  version: 2.0.9
125
125
  description:
@@ -137,6 +137,7 @@ files:
137
137
  - lib/papercraft/encoding.rb
138
138
  - lib/papercraft/extension_proxy.rb
139
139
  - lib/papercraft/html.rb
140
+ - lib/papercraft/json.rb
140
141
  - lib/papercraft/renderer.rb
141
142
  - lib/papercraft/version.rb
142
143
  homepage: http://github.com/digital-fabric/papercraft