papercraft 0.13 → 0.17

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