papercraft 0.13 → 0.17

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: 5250720b438ab385dca9f3b91487fc2e5c887ba7e709015f8e26a1a2e78fa8d6
4
- data.tar.gz: '049f2399c403f55fdd0c0ee9de3b9bac5d314d0c57520184ddc59f361f7ec87d'
3
+ metadata.gz: 31c80224c2a9dc3a6d594d923e9a16ccd8127284bce501f1d7f1dc25386bf25c
4
+ data.tar.gz: 45dc43d353bc1f3c06e62d1b439751570ce7d46a915b0a72f1ce3ea182b0c225
5
5
  SHA512:
6
- metadata.gz: 176f86dbf9e69deaf1fd60edbcba23401c1288a96d9eb5210175815e1ea1b297178daf2ba3533ae8020d18b8bc1b7003bcf39b2175c4ad95439982d622c01fad
7
- data.tar.gz: 9f623e968a7462225e4f504c880a911557d024b45393658bec01426ebff934bfa9c94094ca52feae8c2a23b642054e9cd3b88df25644019a61f62c5fda4350ad
6
+ metadata.gz: 67529d1336456dded5d911548941c1accc695bd017b889b378ed008e177707b100e11e90b1adbe887d45530cfb94950808e21257955999d795b388dbeb13206e
7
+ data.tar.gz: 1caca448eb2a317b1616a529e82803f6571e26e8b0f36a19e771b2b4a9a96cabb8695ad5a01148acfa7481b016742e9c546c794fd2a9017ec54d299184d36c94
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## 0.17 2022-01-23
2
+
3
+ - Refactor markdown code, add `Papercraft.markdown` method (#8)
4
+
5
+ ## 0.16 2022-01-23
6
+
7
+ - Implement JSON templating (#7)
8
+ - Add support for MIME types (#6)
9
+ - Change entrypoint from `Kernel#H`, `Kernel#X` to `Papercraft.html`, `.xml` (#5)
10
+
11
+ ## 0.15 2022-01-20
12
+
13
+ - Fix tag method line reference
14
+ - Don't clobber ArgumentError exception
15
+
16
+ ## 0.14 2022-01-19
17
+
18
+ - Add support for #emit_yield in applied component (#4)
19
+
1
20
  ## 0.13 2022-01-19
2
21
 
3
22
  - Add support for partial parameter application (#3)
@@ -33,7 +52,7 @@
33
52
  ## 0.8 2021-12-22
34
53
 
35
54
  - Cleanup and refactor code
36
- - Add X global method for XML templates
55
+ - Add Papercraft.xml global method for XML templates
37
56
  - Make `Component` a descendant of `Proc`
38
57
  - Introduce new component API
39
58
  - 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,10 +354,18 @@ 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
 
361
+ The `#emit_markdown` method is available only to HTML templates. If you need to
362
+ render markdown in XML or JSON templates (usually for implementing RSS or JSON
363
+ feeds), you can use `Papercraft.markdown` directly:
364
+
365
+ ```ruby
366
+ Papercraft.markdown('# title') #=> "<h1>title</h1>"
367
+ ```
368
+
357
369
  The default Kramdown options are:
358
370
 
359
371
  ```ruby
@@ -366,10 +378,10 @@ The default Kramdown options are:
366
378
  ```
367
379
 
368
380
  The deafult options can be configured by accessing
369
- `Papercraft::HTML.kramdown_options`, e.g.:
381
+ `Papercraft.default_kramdown_options`, e.g.:
370
382
 
371
383
  ```ruby
372
- Papercraft::HTML.kramdown_options[:auto_ids] = false
384
+ Papercraft.default_kramdown_options[:auto_ids] = false
373
385
  ```
374
386
 
375
387
  ## Deferred evaluation
@@ -393,7 +405,7 @@ class that can collect JS and CSS dependencies from the different components
393
405
  integrated into the page, and adds them to the page's `<head>` element:
394
406
 
395
407
  ```ruby
396
- default_layout = H { |**args|
408
+ default_layout = Papercraft.html { |**args|
397
409
  @dependencies = DependencyMananger.new
398
410
  head {
399
411
  defer { emit @dependencies.head_markup }
@@ -479,7 +491,7 @@ The call to `Papercraft::extension` lets us access the different methods of
479
491
  we'll be able to express the above markup as follows:
480
492
 
481
493
  ```ruby
482
- H {
494
+ Papercraft.html {
483
495
  bootstrap.card(style: 'width: 18rem') {
484
496
  bootstrap.card_title 'Card title'
485
497
  bootstrap.card_subtitle 'Card subtitle'
@@ -490,6 +502,37 @@ H {
490
502
  }
491
503
  ```
492
504
 
505
+ ## JSON templating
506
+
507
+ You can create a JSON template using the same API used for HTML and XML
508
+ templating. The only difference is that for adding array items you'll need to
509
+ use the `#item` method:
510
+
511
+ ```ruby
512
+ Papercraft.json {
513
+ item 1
514
+ item 2
515
+ item 3
516
+ }.render #=> "[1,2,3]"
517
+ ```
518
+
519
+ Otherwise, you can create arbitrarily complex JSON structures by mixing hashes
520
+ and arrays:
521
+
522
+ ```Ruby
523
+ Papercraft.json {
524
+ foo {
525
+ bar {
526
+ item nil
527
+ item true
528
+ item 123.456
529
+ }
530
+ }
531
+ }.render #=> "{\"foo\":{\"bar\":[null,true,123.456]}}"
532
+ ```
533
+
534
+ Papercraft uses the [JSON gem](https://rubyapi.org/3.1/o/json) under the hood.
535
+
493
536
  ## API Reference
494
537
 
495
538
  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(*a, *x, **b, **y, &template) }
142
- end)
143
- else
144
- Component.new(&proc do |*x, **y|
145
- instance_exec(*a, *x, **b, **y, &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)
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'kramdown'
4
- require 'rouge'
5
- require 'kramdown-parser-gfm'
6
-
7
3
  module Papercraft
8
4
  # HTML Markup extensions
9
5
  module HTML
@@ -83,41 +79,7 @@ module Papercraft
83
79
  # @param **opts [Hash] Kramdown options
84
80
  # @return [void]
85
81
  def emit_markdown(markdown, **opts)
86
- emit Kramdown::Document.new(markdown, **kramdown_options(opts)).to_html
87
- end
88
-
89
- class << self
90
- # Returns the default Kramdown options used for converting Markdown to
91
- # HTML.
92
- #
93
- # @return [Hash] Default Kramdown options
94
- def kramdown_options
95
- @kramdown_options ||= {
96
- entity_output: :numeric,
97
- syntax_highlighter: :rouge,
98
- input: 'GFM',
99
- hard_wrap: false
100
- }
101
- end
102
-
103
- # Sets the default Kramdown options used for converting Markdown to
104
- # HTML.
105
- #
106
- # @param opts [Hash] New deafult Kramdown options
107
- # @return [Hash] New default Kramdown options
108
- def kramdown_options=(opts)
109
- @kramdown_options = opts
110
- end
111
- end
112
-
113
- private
114
-
115
- # Returns the default Kramdown options, merged with the given overrides.
116
- #
117
- # @param opts [Hash] Kramdown option overrides
118
- # @return [Hash] Merged Kramdown options
119
- def kramdown_options(opts)
120
- HTML.kramdown_options.merge(**opts)
82
+ emit Papercraft.markdown(markdown, **opts)
121
83
  end
122
84
  end
123
85
  end
@@ -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,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'escape_utils'
4
+
3
5
  require_relative './html'
6
+ require_relative './json'
4
7
  require_relative './extension_proxy'
5
8
 
6
9
  module Papercraft
@@ -55,7 +58,7 @@ module Papercraft
55
58
  # end
56
59
  #
57
60
  # Papercraft.extension(components: ComponentLibrary)
58
- # H { components.card('Foo', '**Bar**') }
61
+ # Papercraft.html { components.card('Foo', '**Bar**') }
59
62
  #
60
63
  # @param map [Hash] hash mapping methods to extension modules
61
64
  # @return [void]
@@ -112,7 +115,7 @@ module Papercraft
112
115
 
113
116
  # The tag method template below is optimized for performance. Do not touch!
114
117
 
115
- S_TAG_METHOD_LINE = __LINE__ + 1
118
+ S_TAG_METHOD_LINE = __LINE__ + 2
116
119
  S_TAG_METHOD = <<~EOF
117
120
  S_TAG_%<TAG>s_PRE = %<tag_pre>s
118
121
  S_TAG_%<TAG>s_CLOSE = %<tag_close>s
@@ -210,11 +213,11 @@ module Papercraft
210
213
  # `#to_s` which is then added to the rendering buffer, without any escaping.
211
214
  #
212
215
  # greeter = proc { |name| h1 "Hello, #{name}!" }
213
- # H { emit(greeter, 'world') }.render #=> "<h1>Hello, world!</h1>"
216
+ # Papercraft.html { emit(greeter, 'world') }.render #=> "<h1>Hello, world!</h1>"
214
217
  #
215
- # H { emit 'hi&<bye>' }.render #=> "hi&<bye>"
218
+ # Papercraft.html { emit 'hi&<bye>' }.render #=> "hi&<bye>"
216
219
  #
217
- # H { emit nil }.render #=> ""
220
+ # Papercraft.html { emit nil }.render #=> ""
218
221
  #
219
222
  # @param o [Proc, Papercraft::Component, String] emitted object
220
223
  # @param *a [Array<any>] arguments to pass to a proc
@@ -234,7 +237,7 @@ module Papercraft
234
237
 
235
238
  # Emits a block supplied using `Component#apply` or `Component#render`.
236
239
  #
237
- # div_wrap = H { |*args| div { emit_yield(*args) } }
240
+ # div_wrap = Papercraft.html { |*args| div { emit_yield(*args) } }
238
241
  # greeter = div_wrap.apply { |name| h1 "Hello, #{name}!" }
239
242
  # greeter.render('world') #=> "<div><h1>Hello, world!</h1></div>"
240
243
  #
@@ -242,9 +245,10 @@ module Papercraft
242
245
  # @param **b [Hash] named arguments to pass to a proc
243
246
  # @return [void]
244
247
  def emit_yield(*a, **b)
245
- raise Papercraft::Error, "No block given" unless @inner_block
248
+ block = @emit_yield_stack&.pop
249
+ raise Papercraft::Error, "No block given" unless block
246
250
 
247
- instance_exec(*a, **b, &@inner_block)
251
+ instance_exec(*a, **b, &block)
248
252
  end
249
253
 
250
254
  # Defers the given block to be evaluated later. Deferred evaluation allows
@@ -255,7 +259,7 @@ module Papercraft
255
259
  # adding elements to the `<head>` section. Here's how a title can be
256
260
  # controlled from a nested component:
257
261
  #
258
- # layout = H {
262
+ # layout = Papercraft.html {
259
263
  # html {
260
264
  # head {
261
265
  # defer { title @title }
@@ -304,19 +308,19 @@ module Papercraft
304
308
  private
305
309
 
306
310
  # Escapes text. This method must be overriden in descendant classes.
311
+ #
312
+ # @param text [String] text to be escaped
307
313
  def escape_text(text)
308
314
  raise NotImplementedError
309
315
  end
310
316
 
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
317
+ # Pushes the given block onto the emit_yield stack.
318
+ #
319
+ # @param block [Proc] block
320
+ def push_emit_yield_block(block)
321
+ (@emit_yield_stack ||= []) << block
318
322
  end
319
-
323
+
320
324
  # Emits tag attributes into the rendering buffer
321
325
  # @param props [Hash] tag attributes
322
326
  # @return [void]
@@ -379,4 +383,8 @@ module Papercraft
379
383
  EscapeUtils.escape_xml(text.to_s)
380
384
  end
381
385
  end
386
+
387
+ class JSONRenderer < Renderer
388
+ include JSON
389
+ end
382
390
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Papercraft
4
- VERSION = '0.13'
4
+ VERSION = '0.17'
5
5
  end
data/lib/papercraft.rb CHANGED
@@ -1,60 +1,110 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'escape_utils'
3
+ require 'kramdown'
4
+ require 'rouge'
5
+ require 'kramdown-parser-gfm'
4
6
 
5
7
  require_relative 'papercraft/component'
6
8
  require_relative 'papercraft/renderer'
7
9
  require_relative 'papercraft/encoding'
8
10
  # require_relative 'papercraft/compiler'
9
11
 
12
+
10
13
  # Papercraft is a component-based HTML templating library
11
14
  module Papercraft
12
15
  # Exception class used to signal templating-related errors
13
16
  class Error < RuntimeError; end
14
-
15
- # Installs one or more extensions. Extensions enhance templating capabilities
16
- # by adding namespaced methods to emplates. An extension is implemented as a
17
- # Ruby module containing one or more methods. Each method in the extension
18
- # module can be used to render a specific HTML element or a set of elements.
19
- #
20
- # This is a convenience method. For more information on using Papercraft
21
- # extensions, see `Papercraft::Renderer::extension`
22
- #
23
- # @param map [Hash] hash mapping methods to extension modules
24
- # @return [void]
25
- def self.extension(map)
26
- Renderer.extension(map)
27
- end
28
- end
29
-
30
- # Kernel extensions
31
- module ::Kernel
32
17
 
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`.
35
- #
36
- # H(proc { h1 'hi' }).render #=> "<h1>hi</h1>"
37
- # H { h1 'hi' }.render #=> "<h1>hi</h1>"
38
- #
39
- # @param template [Proc] template block
40
- # @return [Papercraft::Component] Papercraft component
41
- def H(o = nil, &template)
42
- return o if o.is_a?(Papercraft::Component)
43
- template ||= o
44
- Papercraft::Component.new(mode: :html, &template)
45
- end
18
+ class << self
19
+
20
+ # Installs one or more extensions. Extensions enhance templating capabilities
21
+ # by adding namespaced methods to emplates. An extension is implemented as a
22
+ # Ruby module containing one or more methods. Each method in the extension
23
+ # module can be used to render a specific HTML element or a set of elements.
24
+ #
25
+ # This is a convenience method. For more information on using Papercraft
26
+ # extensions, see `Papercraft::Renderer::extension`
27
+ #
28
+ # @param map [Hash] hash mapping methods to extension modules
29
+ # @return [void]
30
+ def extension(map)
31
+ Renderer.extension(map)
32
+ end
33
+
34
+ # Creates a new papercraft component. `Papercraft.html` can take either a proc
35
+ # argument or a block. In both cases, the proc is converted to a
36
+ # `Papercraft::Component`.
37
+ #
38
+ # Papercraft.html(proc { h1 'hi' }).render #=> "<h1>hi</h1>"
39
+ # Papercraft.html { h1 'hi' }.render #=> "<h1>hi</h1>"
40
+ #
41
+ # @param template [Proc] template block
42
+ # @return [Papercraft::Component] Papercraft component
43
+ def html(o = nil, mime_type: nil, &template)
44
+ return o if o.is_a?(Papercraft::Component)
45
+ template ||= o
46
+ Papercraft::Component.new(mode: :html, mime_type: mime_type, &template)
47
+ end
48
+
49
+ # Creates a new papercraft component in XML mode. `Papercraft.xml` can take
50
+ # either a proc argument or a block. In both cases, the proc is converted to a
51
+ # `Papercraft::Component`.
52
+ #
53
+ # Papercraft.xml(proc { item 'foo' }).render #=> "<item>foo</item>"
54
+ # Papercraft.xml { item 'foo' }.render #=> "<item>foo</item>"
55
+ #
56
+ # @param template [Proc] template block
57
+ # @return [Papercraft::Component] Papercraft component
58
+ def xml(o = nil, mime_type: nil, &template)
59
+ return o if o.is_a?(Papercraft::Component)
60
+ template ||= o
61
+ Papercraft::Component.new(mode: :xml, mime_type: mime_type, &template)
62
+ end
63
+
64
+ # Creates a new papercraft component in JSON mode. `Papercraft.json` can take
65
+ # either a proc argument or a block. In both cases, the proc is converted to a
66
+ # `Papercraft::Component`.
67
+ #
68
+ # Papercraft.json(proc { item 42 }).render #=> "[42]"
69
+ # Papercraft.json { foo 'bar' }.render #=> "{\"foo\": \"bar\"}"
70
+ #
71
+ # @param template [Proc] template block
72
+ # @return [Papercraft::Component] Papercraft component
73
+ def json(o = nil, mime_type: nil, &template)
74
+ return o if o.is_a?(Papercraft::Component)
75
+ template ||= o
76
+ Papercraft::Component.new(mode: :json, mime_type: mime_type, &template)
77
+ end
78
+
79
+ # Renders Markdown into HTML. The `opts` argument will be merged with the
80
+ # default Kramdown options in order to change the rendering behaviour.
81
+ #
82
+ # @param markdown [String] Markdown
83
+ # @param **opts [Hash] Kramdown option overrides
84
+ # @return [String] HTML
85
+ def markdown(markdown, **opts)
86
+ opts = default_kramdown_options.merge(opts)
87
+ Kramdown::Document.new(markdown, **opts).to_html
88
+ end
89
+
90
+ # Returns the default Kramdown options used for rendering Markdown.
91
+ #
92
+ # @return [Hash] Kramdown options
93
+ def default_kramdown_options
94
+ @default_kramdown_options ||= {
95
+ entity_output: :numeric,
96
+ syntax_highlighter: :rouge,
97
+ input: 'GFM',
98
+ hard_wrap: false
99
+ }
100
+ end
46
101
 
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`.
49
- #
50
- # X(proc { item 'foo' }).render #=> "<item>foo</item>"
51
- # X { item 'foo' }.render #=> "<item>foo</item>"
52
- #
53
- # @param template [Proc] template block
54
- # @return [Papercraft::Component] Papercraft component
55
- def X(o = nil, &template)
56
- return o if o.is_a?(Papercraft::Component)
57
- template ||= o
58
- Papercraft::Component.new(mode: :xml, &template)
102
+ # Sets the default Kramdown options used for rendering Markdown.
103
+ #
104
+ # @param opts [Hash] Kramdown options
105
+ # @return [void]
106
+ def default_kramdown_options=(opts)
107
+ @default_kramdown_options = opts
108
+ end
59
109
  end
60
110
  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.13'
4
+ version: '0.17'
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-19 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