papercraft 0.17 → 0.21
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 +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +135 -51
- data/lib/papercraft/extension_proxy.rb +3 -1
- data/lib/papercraft/html.rb +32 -0
- data/lib/papercraft/json.rb +89 -36
- data/lib/papercraft/renderer.rb +12 -240
- data/lib/papercraft/tags.rb +261 -0
- data/lib/papercraft/{component.rb → template.rb} +29 -26
- data/lib/papercraft/version.rb +1 -1
- data/lib/papercraft/xml.rb +39 -0
- data/lib/papercraft.rb +17 -17
- data/papercraft.png +0 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a23bf614d84747a18eecf84545604c9c2f58710dc3fa0e0c651a863a53c919b0
|
4
|
+
data.tar.gz: 6b1ca8f4a90801beff11807dd357ce801b7526b5d114d5cca5f41f41aec69958
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dcd7eada8280cb0fcd68528eaea0d2500ff99ac149c8495dd5a3955a93c3f181301a4decc9eeb4d7efdd8cfe2c4f6047c9f838e5084b413c1118a76d6abc4252
|
7
|
+
data.tar.gz: 840ce0fc2a83de9b5a83bb10a5f4ed98986f7201737d6e31a4e89877aeb6bb22b2f2449c6efa08302473c073e26c830cfbe9e267456b450a56222b57a429fd15
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
## 0.21 2022-02-13
|
2
|
+
|
3
|
+
- Refactor and improve documentation
|
4
|
+
|
5
|
+
## 0.20 2022-02-13
|
6
|
+
|
7
|
+
- Add support for XML namespaced tags and attributes (#9)
|
8
|
+
- Move and refactor HTML/XML common code to Tags module
|
9
|
+
|
10
|
+
## 0.19 2022-02-05
|
11
|
+
|
12
|
+
- Rename `Papercraft::Component` to `Papercraft::Template`
|
13
|
+
|
14
|
+
## 0.18 2022-02-04
|
15
|
+
|
16
|
+
- Cleanup and update examples
|
17
|
+
- Fix behaviour of #emit with block
|
18
|
+
- Improve README
|
19
|
+
|
1
20
|
## 0.17 2022-01-23
|
2
21
|
|
3
22
|
- Refactor markdown code, add `Papercraft.markdown` method (#8)
|
data/README.md
CHANGED
@@ -24,35 +24,63 @@
|
|
24
24
|
|
25
25
|
## What is Papercraft?
|
26
26
|
|
27
|
+
Papercraft is a templating engine for dynamically producing HTML, XML or JSON.
|
28
|
+
Papercraft templates are expressed in plain Ruby, leading to easier debugging,
|
29
|
+
better protection against HTML/XML injection attacks, and better code reuse.
|
30
|
+
|
31
|
+
Papercraft templates can be composed in a variety of ways, facilitating the
|
32
|
+
usage of layout templates, and enabling a component-oriented approach to
|
33
|
+
building complex web interfaces.
|
34
|
+
|
35
|
+
In Papercraft, dynamic data is passed explicitly to the template as block
|
36
|
+
arguments, making the data flow easy to follow and understand. Papercraft also
|
37
|
+
lets developers create derivative templates using full or partial parameter
|
38
|
+
application.
|
39
|
+
|
40
|
+
Papercraft includes built-in support for rendering Markdown (using
|
41
|
+
[Kramdown](https://github.com/gettalong/kramdown/)), as well as support for
|
42
|
+
creating template extensions in order to allow the creation of component
|
43
|
+
libraries.
|
44
|
+
|
27
45
|
```ruby
|
28
46
|
require 'papercraft'
|
29
47
|
|
30
48
|
page = Papercraft.html { |*args|
|
31
49
|
html {
|
32
|
-
head { }
|
50
|
+
head { title 'Title' }
|
33
51
|
body { emit_yield *args }
|
34
52
|
}
|
35
53
|
}
|
36
54
|
page.render { p 'foo' }
|
37
|
-
#=> "<html><head
|
55
|
+
#=> "<html><head><title>Title</title></head><body><p>foo</p></body></html>"
|
38
56
|
|
39
57
|
hello = page.apply { |name| h1 "Hello, #{name}!" }
|
40
58
|
hello.render('world')
|
41
|
-
#=> "<html><head
|
42
|
-
```
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
-
|
47
|
-
-
|
48
|
-
-
|
49
|
-
-
|
50
|
-
-
|
51
|
-
-
|
52
|
-
-
|
53
|
-
-
|
54
|
-
-
|
55
|
-
-
|
59
|
+
#=> "<html><head><title>Title</title></head><body><h1>Hello, world!</h1></body></html>"
|
60
|
+
```
|
61
|
+
|
62
|
+
## Table of content
|
63
|
+
|
64
|
+
- [Installing papercraft](#installing-papercraft)
|
65
|
+
- [Basic usage](#basic-usage)
|
66
|
+
- [Adding tags](#adding-tags)
|
67
|
+
- [Template parameters](#template-parameters)
|
68
|
+
- [Template logic](#template-logic)
|
69
|
+
- [Template blocks](#template-blocks)
|
70
|
+
- [Plain procs as templates](#plain-procs-as-templates)
|
71
|
+
- [Template composition](#template-composition)
|
72
|
+
- [Parameter and block application](#parameter-and-block-application)
|
73
|
+
- [Higher-order templates](#higher-order-templates)
|
74
|
+
- [Layout template composition](#layout-template-composition)
|
75
|
+
- [Emitting raw HTML](#emitting-raw-html)
|
76
|
+
- [Emitting a string with HTML Encoding](#emitting-a-string-with-html-encoding)
|
77
|
+
- [Emitting Markdown](#emitting-markdown)
|
78
|
+
- [Working with MIME types](#working-with-mime-types)
|
79
|
+
- [Deferred evaluation](#deferred-evaluation)
|
80
|
+
- [Papercraft extensions](#papercraft-extensions)
|
81
|
+
- [XML templates](#xml-templates)
|
82
|
+
- [JSON templates](#json-templates)
|
83
|
+
- [API Reference](#api-reference)
|
56
84
|
|
57
85
|
## Installing Papercraft
|
58
86
|
|
@@ -68,9 +96,9 @@ Or manually:
|
|
68
96
|
$ gem install papercraft
|
69
97
|
```
|
70
98
|
|
71
|
-
##
|
99
|
+
## Basic usage
|
72
100
|
|
73
|
-
To create
|
101
|
+
To create an HTML template use `Papercraft.html`:
|
74
102
|
|
75
103
|
```ruby
|
76
104
|
require 'papercraft'
|
@@ -80,13 +108,16 @@ html = Papercraft.html {
|
|
80
108
|
}
|
81
109
|
```
|
82
110
|
|
111
|
+
(You can also use `Papercraft.xml` and `Papercraft.json` to create XML and JSON
|
112
|
+
templates, respectively.)
|
113
|
+
|
83
114
|
Rendering a template is done using `#render`:
|
84
115
|
|
85
116
|
```ruby
|
86
117
|
html.render #=> "<div id="greeter"><p>Hello!</p></div>"
|
87
118
|
```
|
88
119
|
|
89
|
-
##
|
120
|
+
## Adding tags
|
90
121
|
|
91
122
|
Tags are added using unqualified method calls, and can be nested using blocks:
|
92
123
|
|
@@ -141,7 +172,7 @@ greeting = Papercraft.html { |name:| h1 "Hello, #{name}!" }
|
|
141
172
|
greeting.render(name: 'world') #=> "<h1>Hello, world!</h1>"
|
142
173
|
```
|
143
174
|
|
144
|
-
##
|
175
|
+
## Template logic
|
145
176
|
|
146
177
|
Since Papercraft templates are just a bunch of Ruby, you can easily write your
|
147
178
|
view logic right in the template:
|
@@ -171,10 +202,10 @@ page = Papercraft.html {
|
|
171
202
|
page.render { h1 'hi' }
|
172
203
|
```
|
173
204
|
|
174
|
-
## Plain procs as
|
205
|
+
## Plain procs as templates
|
175
206
|
|
176
207
|
With Papercraft you can write a template as a plain Ruby proc, and later render
|
177
|
-
it by passing it as a block to `
|
208
|
+
it by passing it as a block to `Papercraft.html`:
|
178
209
|
|
179
210
|
```ruby
|
180
211
|
greeting = proc { |name| h1 "Hello, #{name}!" }
|
@@ -188,10 +219,10 @@ greeting = ->(name) { h1 "Hello, #{name}!" }
|
|
188
219
|
Papercraft.html(&greeting).render('world')
|
189
220
|
```
|
190
221
|
|
191
|
-
##
|
222
|
+
## Template composition
|
192
223
|
|
193
|
-
Papercraft makes it easy to compose multiple
|
194
|
-
document. A Papercraft
|
224
|
+
Papercraft makes it easy to compose multiple templates into a whole HTML
|
225
|
+
document. A Papercraft template can contain other templates, as the following
|
195
226
|
example shows.
|
196
227
|
|
197
228
|
```ruby
|
@@ -219,14 +250,14 @@ page = Papercraft.html { |title, items|
|
|
219
250
|
}
|
220
251
|
}
|
221
252
|
|
222
|
-
page.render('Hello from
|
253
|
+
page.render('Hello from composed templates', [
|
223
254
|
{ id: 1, text: 'foo', checked: false },
|
224
255
|
{ id: 2, text: 'bar', checked: true }
|
225
256
|
])
|
226
257
|
```
|
227
258
|
|
228
|
-
In addition to using
|
229
|
-
non-constant
|
259
|
+
In addition to using templates defined as constants, you can also use
|
260
|
+
non-constant templates by invoking the `#emit` method:
|
230
261
|
|
231
262
|
```ruby
|
232
263
|
greeting = -> { span "Hello, world" }
|
@@ -241,11 +272,11 @@ Papercraft.html {
|
|
241
272
|
## Parameter and block application
|
242
273
|
|
243
274
|
Parameters and blocks can be applied to a template without it being rendered, by
|
244
|
-
using `#apply`. This mechanism is what allows
|
245
|
-
creation of higher-order
|
275
|
+
using `#apply`. This mechanism is what allows template composition and the
|
276
|
+
creation of higher-order templates.
|
246
277
|
|
247
|
-
The `#apply` method returns a new
|
248
|
-
or block to the original
|
278
|
+
The `#apply` method returns a new template which applies the given parameters and
|
279
|
+
or block to the original template:
|
249
280
|
|
250
281
|
```ruby
|
251
282
|
# parameter application
|
@@ -258,19 +289,19 @@ div_wrap = Papercraft.html { div { emit_yield } }
|
|
258
289
|
wrapped_h1 = div_wrap.apply { h1 'hi' }
|
259
290
|
wrapped_h1.render #=> "<div><h1>hi</h1></div>"
|
260
291
|
|
261
|
-
# wrap a
|
292
|
+
# wrap a template
|
262
293
|
wrapped_hello_world = div_wrap.apply(&hello_world)
|
263
294
|
wrapped_hello_world.render #=> "<div><h1>Hello, world!</h1></div>"
|
264
295
|
```
|
265
296
|
|
266
|
-
## Higher-order
|
297
|
+
## Higher-order templates
|
267
298
|
|
268
|
-
Papercraft also lets you create higher-order
|
269
|
-
|
270
|
-
|
271
|
-
markup, enhancing
|
299
|
+
Papercraft also lets you create higher-order templates, that is,
|
300
|
+
templates that take other templates as parameters, or as blocks. Higher-order
|
301
|
+
templates are handy for creating layouts, wrapping templates in arbitrary
|
302
|
+
markup, enhancing templates or injecting template parameters.
|
272
303
|
|
273
|
-
Here is a
|
304
|
+
Here is a higher-order template that takes a template as parameter:
|
274
305
|
|
275
306
|
```ruby
|
276
307
|
div_wrap = Papercraft.html { |inner| div { emit inner } }
|
@@ -279,7 +310,7 @@ wrapped_greeter = div_wrap.apply(greeter)
|
|
279
310
|
wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
280
311
|
```
|
281
312
|
|
282
|
-
The inner
|
313
|
+
The inner template can also be passed as a block, as shown above:
|
283
314
|
|
284
315
|
```ruby
|
285
316
|
div_wrap = Papercraft.html { div { emit_yield } }
|
@@ -289,7 +320,7 @@ wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
|
289
320
|
|
290
321
|
## Layout template composition
|
291
322
|
|
292
|
-
One of the principal uses of higher-order
|
323
|
+
One of the principal uses of higher-order templates is the creation of nested
|
293
324
|
layouts. Suppose we have a website with a number of different layouts, and we'd
|
294
325
|
like to avoid having to repeat the same code in the different layouts. We can do
|
295
326
|
this by creating a `default` page template that takes a block, then use `#apply`
|
@@ -384,11 +415,27 @@ The deafult options can be configured by accessing
|
|
384
415
|
Papercraft.default_kramdown_options[:auto_ids] = false
|
385
416
|
```
|
386
417
|
|
418
|
+
## Working with MIME types
|
419
|
+
|
420
|
+
Papercraft lets you set and interrogate a template's MIME type, in order to be
|
421
|
+
able to dynamically set the `Content-Type` HTTP response header. A template's
|
422
|
+
MIME type can be set when creating the template, e.g. `Papercraft.xml(mime_type:
|
423
|
+
'application/rss+xml')`. You can interrogate the template's MIME type using
|
424
|
+
`#mime_type`:
|
425
|
+
|
426
|
+
```ruby
|
427
|
+
# using Qeweney (https://github.com/digital-fabric/qeweney)
|
428
|
+
def serve_template(req, template)
|
429
|
+
body = template.render
|
430
|
+
respond(body, 'Content-Type' => template.mime_type)
|
431
|
+
end
|
432
|
+
```
|
433
|
+
|
387
434
|
## Deferred evaluation
|
388
435
|
|
389
436
|
Deferred evaluation allows deferring the rendering of parts of a template until
|
390
|
-
the last moment, thus allowing an inner
|
391
|
-
outer
|
437
|
+
the last moment, thus allowing an inner template to manipulate the state of the
|
438
|
+
outer template. To in order to defer a part of a template, use `#defer`, and
|
392
439
|
include any markup in the provided block. This technique, in in conjunction with
|
393
440
|
holding state in instance variables, is an alternative to passing parameters,
|
394
441
|
which can be limiting in some situations.
|
@@ -397,11 +444,11 @@ A few use cases for deferred evaulation come to mind:
|
|
397
444
|
|
398
445
|
- Setting the page title.
|
399
446
|
- Adding a flash message to a page.
|
400
|
-
- Using
|
447
|
+
- Using templates that dynamically add static dependencies (JS and CSS) to the
|
401
448
|
page.
|
402
449
|
|
403
450
|
The last use case is particularly interesting. Imagine a `DependencyMananger`
|
404
|
-
class that can collect JS and CSS dependencies from the different
|
451
|
+
class that can collect JS and CSS dependencies from the different templates
|
405
452
|
integrated into the page, and adds them to the page's `<head>` element:
|
406
453
|
|
407
454
|
```ruby
|
@@ -502,11 +549,47 @@ Papercraft.html {
|
|
502
549
|
}
|
503
550
|
```
|
504
551
|
|
505
|
-
## JSON templating
|
506
552
|
|
507
|
-
|
508
|
-
|
509
|
-
|
553
|
+
|
554
|
+
## XML templates
|
555
|
+
|
556
|
+
XML templates behave largely the same as HTML templates, with a few minor
|
557
|
+
differences. XML templates employ a different encoding algorithm, and lack some
|
558
|
+
specific HTML functionality, such as emitting Markdown.
|
559
|
+
|
560
|
+
Here's an example showing how to create an RSS feed:
|
561
|
+
|
562
|
+
```ruby
|
563
|
+
rss = Papercraft.xml(mime_type: 'text/xml; charset=utf-8') { |resource:, **props|
|
564
|
+
rss(version: '2.0', 'xmlns:atom' => 'http://www.w3.org/2005/Atom') {
|
565
|
+
channel {
|
566
|
+
title 'Noteflakes'
|
567
|
+
link 'https://noteflakes.com/'
|
568
|
+
description 'A website by Sharon Rosner'
|
569
|
+
language 'en-us'
|
570
|
+
pubDate Time.now.httpdate
|
571
|
+
emit '<atom:link href="https://noteflakes.com/feeds/rss" rel="self" type="application/rss+xml" />'
|
572
|
+
|
573
|
+
article_entries = resource.page_list('/articles').reverse
|
574
|
+
|
575
|
+
article_entries.each { |e|
|
576
|
+
item {
|
577
|
+
title e[:title]
|
578
|
+
link "https://noteflakes.com#{e[:url]}"
|
579
|
+
guid "https://noteflakes.com#{e[:url]}"
|
580
|
+
pubDate e[:date].to_time.httpdate
|
581
|
+
description e[:html_content]
|
582
|
+
}
|
583
|
+
}
|
584
|
+
}
|
585
|
+
}
|
586
|
+
}
|
587
|
+
```
|
588
|
+
|
589
|
+
## JSON templates
|
590
|
+
|
591
|
+
JSON templates behave largely the same as HTML and XML templates. The only major
|
592
|
+
difference is that for adding array items you'll need to use the `#item` method:
|
510
593
|
|
511
594
|
```ruby
|
512
595
|
Papercraft.json {
|
@@ -531,7 +614,8 @@ Papercraft.json {
|
|
531
614
|
}.render #=> "{\"foo\":{\"bar\":[null,true,123.456]}}"
|
532
615
|
```
|
533
616
|
|
534
|
-
Papercraft uses the [JSON gem](https://rubyapi.org/3.1/o/json) under the hood
|
617
|
+
Papercraft uses the [JSON gem](https://rubyapi.org/3.1/o/json) under the hood in
|
618
|
+
order to generate actual JSON.
|
535
619
|
|
536
620
|
## API Reference
|
537
621
|
|
@@ -10,6 +10,7 @@ module Papercraft
|
|
10
10
|
class ExtensionProxy
|
11
11
|
|
12
12
|
# Initializes a new ExtensionProxy.
|
13
|
+
#
|
13
14
|
# @param renderer [Papercraft::Renderer] renderer to proxy to
|
14
15
|
# @param mod [Module] extension module
|
15
16
|
# @return [void]
|
@@ -18,7 +19,8 @@ module Papercraft
|
|
18
19
|
extend(mod)
|
19
20
|
end
|
20
21
|
|
21
|
-
# Proxies missing methods to the renderer
|
22
|
+
# Proxies missing methods to the renderer.
|
23
|
+
#
|
22
24
|
# @param sym [Symbol] method name
|
23
25
|
# @param *args [Array] arguments
|
24
26
|
# @param &block [Proc] block
|
data/lib/papercraft/html.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './tags'
|
4
|
+
|
3
5
|
module Papercraft
|
4
6
|
# HTML Markup extensions
|
5
7
|
module HTML
|
8
|
+
include Tags
|
9
|
+
|
6
10
|
# Emits the p tag (overrides Object#p)
|
7
11
|
#
|
8
12
|
# @param text [String] text content of tag
|
@@ -81,5 +85,33 @@ module Papercraft
|
|
81
85
|
def emit_markdown(markdown, **opts)
|
82
86
|
emit Papercraft.markdown(markdown, **opts)
|
83
87
|
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Escapes the given text using HTML entities.
|
92
|
+
#
|
93
|
+
# @param text [String] text
|
94
|
+
# @return [String] escaped text
|
95
|
+
def escape_text(text)
|
96
|
+
EscapeUtils.escape_html(text.to_s)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Converts a tag to its string representation. Underscores will be converted
|
100
|
+
# to dashes.
|
101
|
+
#
|
102
|
+
# @param tag [Symbol, String] tag
|
103
|
+
# @return [String] tag string
|
104
|
+
def tag_repr(tag)
|
105
|
+
tag.to_s.tr('_', '-')
|
106
|
+
end
|
107
|
+
|
108
|
+
# Converts an attribute to its string representation. Underscores will be
|
109
|
+
# converted to dashes.
|
110
|
+
#
|
111
|
+
# @param att [Symbol, String] attribute
|
112
|
+
# @return [String] attribute string
|
113
|
+
def att_repr(att)
|
114
|
+
att.to_s.tr('_', '-')
|
115
|
+
end
|
84
116
|
end
|
85
117
|
end
|
data/lib/papercraft/json.rb
CHANGED
@@ -5,69 +5,122 @@ require 'json'
|
|
5
5
|
module Papercraft
|
6
6
|
# JSON renderer extensions
|
7
7
|
module JSON
|
8
|
-
|
9
|
-
|
8
|
+
# Initializes a JSON renderer, setting up an object stack.
|
9
|
+
def initialize(&template)
|
10
|
+
@object_stack = [nil]
|
11
|
+
super
|
10
12
|
end
|
11
13
|
|
14
|
+
# Adds an array item to the current object target. If a block is given, the
|
15
|
+
# block is evaulated against a new object target, then added to the current
|
16
|
+
# array.
|
17
|
+
#
|
18
|
+
# @param value [Object] item
|
19
|
+
# @param &block [Proc] template block
|
20
|
+
# @return [void]
|
21
|
+
def item(value = nil, &block)
|
22
|
+
verify_array_target
|
23
|
+
if block
|
24
|
+
value = enter_object(&block)
|
25
|
+
end
|
26
|
+
push_array_item(value)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Adds a key-value item to the current object target. If a block is given,
|
30
|
+
# the block is evaulated against a new object target, then used as the
|
31
|
+
# value.
|
32
|
+
#
|
33
|
+
# @param key [Object] key
|
34
|
+
# @param value [Object] value
|
35
|
+
# @param &block [Proc] template block
|
36
|
+
# @return [void]
|
37
|
+
def kv(key, value = nil, &block)
|
38
|
+
verify_hash_target
|
39
|
+
if block
|
40
|
+
value = enter_object(&block)
|
41
|
+
end
|
42
|
+
push_kv_item(key, value)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Intercepts method calls by adding key-value pairs to the current object
|
46
|
+
# target.
|
47
|
+
#
|
48
|
+
# @param key [Object] key
|
49
|
+
# @param value [Object] value
|
50
|
+
# @param &block [Proc] template block
|
51
|
+
# @return [void]
|
52
|
+
def method_missing(sym, value = nil, &block)
|
53
|
+
kv(sym, value, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Converts the root object target to JSON.
|
57
|
+
#
|
58
|
+
# @return [String] JSON template result
|
59
|
+
def to_s
|
60
|
+
@object_stack[0].to_json
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Adds a new entry to the object stack and evaluates the given block.
|
66
|
+
#
|
67
|
+
# @param &block [Proc] template block
|
68
|
+
# @return [void]
|
12
69
|
def with_object(&block)
|
13
|
-
object_stack << nil
|
70
|
+
@object_stack << nil
|
14
71
|
instance_eval(&block)
|
15
72
|
end
|
16
73
|
|
74
|
+
# Verifies that the current object target is not a hash.
|
75
|
+
#
|
76
|
+
# @return [bool]
|
17
77
|
def verify_array_target
|
18
|
-
case object_stack[-1]
|
78
|
+
case @object_stack[-1]
|
19
79
|
when nil
|
20
|
-
object_stack[-1] = []
|
80
|
+
@object_stack[-1] = []
|
21
81
|
when Hash
|
22
82
|
raise "Mixing array and hash values"
|
23
83
|
end
|
24
84
|
end
|
25
85
|
|
86
|
+
# Verifies that the current object target is not an array.
|
87
|
+
#
|
88
|
+
# @return [bool]
|
26
89
|
def verify_hash_target
|
27
|
-
case object_stack[-1]
|
90
|
+
case @object_stack[-1]
|
28
91
|
when nil
|
29
|
-
object_stack[-1] = {}
|
92
|
+
@object_stack[-1] = {}
|
30
93
|
when Array
|
31
94
|
raise "Mixing array and hash values"
|
32
95
|
end
|
33
96
|
end
|
34
97
|
|
98
|
+
# Pushes an array item to the current object target.
|
99
|
+
#
|
100
|
+
# @param value [Object] item
|
101
|
+
# @return [void]
|
35
102
|
def push_array_item(value)
|
36
|
-
object_stack[-1] << value
|
103
|
+
@object_stack[-1] << value
|
37
104
|
end
|
38
105
|
|
106
|
+
# Pushes a key value into the current object target.
|
107
|
+
#
|
108
|
+
# @param key [Object] key
|
109
|
+
# @param value [Object] value
|
110
|
+
# @return [void]
|
39
111
|
def push_kv_item(key, value)
|
40
|
-
object_stack[-1][key] = value
|
112
|
+
@object_stack[-1][key] = value
|
41
113
|
end
|
42
114
|
|
115
|
+
# Adds a new object to the object stack, evaluates the given template block,
|
116
|
+
# then pops the object off the stack.
|
117
|
+
#
|
118
|
+
# @param &block [Proc] template block
|
119
|
+
# @return [void]
|
43
120
|
def enter_object(&block)
|
44
|
-
object_stack << nil
|
121
|
+
@object_stack << nil
|
45
122
|
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
|
123
|
+
@object_stack.pop
|
124
|
+
end
|
72
125
|
end
|
73
126
|
end
|