papercraft 0.12 → 0.16

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