papercraft 0.14 → 0.18

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: 8ecbcde7e33f35958acc4ca0c5c0623468563f14322675a3c7315ed92e0b289c
4
- data.tar.gz: 5f6bdcdee15ef9ca04bc982405bd07ea3da49c4a43fbd8fd95a70494e0c43dab
3
+ metadata.gz: a887e5946cfe19b6874046cb4ef173a1de4deb11509ea4c7dca5c6434e0d710d
4
+ data.tar.gz: 3bdd374b54c72dac9f8137f5878cd43f0415c84eadebfef50fc90eb30029bf3b
5
5
  SHA512:
6
- metadata.gz: 7496b82113125d5f20287cb8b2f6607acfe7c16dda2a4670152867a175c877c33865f85ad155e733509b55e36067d0c3ea60578bf55f041e95bb46e3c1fa84f9
7
- data.tar.gz: 91d5a7059eaf4b760412d9440f8ae08b48486c6ca6fc423a1ce32eb61a9a376385b4830b88b8a2ca1721971c48e4525a99d732d7650522fa71a55654c63aae86
6
+ metadata.gz: 8d4a911c94bb39ab8da06e3f61a965a7db13725b143a2cf38feae44cbb1ac0ae0a8d79b899008a14c87d890ba5ebff61fb3616b5a2014bd84049da8af95a9029
7
+ data.tar.gz: fe798bfc67006d21e6bc62cfa32d50141720f44d0089d9da58b7b7406b8d1386e8f77d10f4a34863dad05968e0342acd9d94be68afeca98955189853b367a8e0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 0.18 2022-02-04
2
+
3
+ - Cleanup and update examples
4
+ - Fix behaviour of #emit with block
5
+ - Improve README
6
+
7
+ ## 0.17 2022-01-23
8
+
9
+ - Refactor markdown code, add `Papercraft.markdown` method (#8)
10
+
11
+ ## 0.16 2022-01-23
12
+
13
+ - Implement JSON templating (#7)
14
+ - Add support for MIME types (#6)
15
+ - Change entrypoint from `Kernel#H`, `Kernel#X` to `Papercraft.html`, `.xml` (#5)
16
+
17
+ ## 0.15 2022-01-20
18
+
19
+ - Fix tag method line reference
20
+ - Don't clobber ArgumentError exception
21
+
1
22
  ## 0.14 2022-01-19
2
23
 
3
24
  - Add support for #emit_yield in applied component (#4)
@@ -37,7 +58,7 @@
37
58
  ## 0.8 2021-12-22
38
59
 
39
60
  - Cleanup and refactor code
40
- - Add X global method for XML templates
61
+ - Add Papercraft.xml global method for XML templates
41
62
  - Make `Component` a descendant of `Proc`
42
63
  - Introduce new component API
43
64
  - 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">
@@ -22,33 +24,63 @@
22
24
 
23
25
  ## What is Papercraft?
24
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
+
25
45
  ```ruby
26
46
  require 'papercraft'
27
47
 
28
- page = H { |*args|
48
+ page = Papercraft.html { |*args|
29
49
  html {
30
- head { }
50
+ head { title 'Title' }
31
51
  body { emit_yield *args }
32
52
  }
33
53
  }
54
+ page.render { p 'foo' }
55
+ #=> "<html><head><title>Title</title></head><body><p>foo</p></body></html>"
34
56
 
35
- hello = H.apply { |name| h1 "Hello, #{name}!" }
57
+ hello = page.apply { |name| h1 "Hello, #{name}!" }
36
58
  hello.render('world')
37
- #=> "<html><head/><body><h1>Hello, world!</h1></body></html>"
59
+ #=> "<html><head><title>Title</title></head><body><h1>Hello, world!</h1></body></html>"
38
60
  ```
39
61
 
40
- Papercraft is an HTML templating engine for Ruby that offers the following
41
- features:
42
-
43
- - HTML and XML templating using plain Ruby syntax
44
- - Minimal boilerplate
45
- - Mix logic and tags freely
46
- - Automatic HTML and XML escaping
47
- - Composable components
48
- - Explicit parameter passing to nested components
49
- - Higher order components
50
- - Built-in support for rendering [Markdown](#emitting-markdown)
51
- - Support for namespaced extensions
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 components](#higher-order-components)
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)
52
84
 
53
85
  ## Installing Papercraft
54
86
 
@@ -64,30 +96,33 @@ Or manually:
64
96
  $ gem install papercraft
65
97
  ```
66
98
 
67
- ## Getting started
99
+ ## Basic usage
68
100
 
69
- To create a template use the global method `Kernel#H`:
101
+ To create an HTML template use `Papercraft.html`:
70
102
 
71
103
  ```ruby
72
104
  require 'papercraft'
73
105
 
74
- html = H {
106
+ html = Papercraft.html {
75
107
  div(id: 'greeter') { p 'Hello!' }
76
108
  }
77
109
  ```
78
110
 
111
+ (You can also use `Papercraft.xml` and `Papercraft.json` to create XML and JSON
112
+ templates, respectively.)
113
+
79
114
  Rendering a template is done using `#render`:
80
115
 
81
116
  ```ruby
82
117
  html.render #=> "<div id="greeter"><p>Hello!</p></div>"
83
118
  ```
84
119
 
85
- ## All about tags
120
+ ## Adding tags
86
121
 
87
122
  Tags are added using unqualified method calls, and can be nested using blocks:
88
123
 
89
124
  ```ruby
90
- H {
125
+ Papercraft.html {
91
126
  html {
92
127
  head {
93
128
  title 'page title'
@@ -104,19 +139,19 @@ H {
104
139
  Tag methods accept a string argument, a block, or no argument at all:
105
140
 
106
141
  ```ruby
107
- H { p 'hello' }.render #=> "<p>hello</p>"
142
+ Papercraft.html { p 'hello' }.render #=> "<p>hello</p>"
108
143
 
109
- H { p { span '1'; span '2' } }.render #=> "<p><span>1</span><span>2</span></p>"
144
+ Papercraft.html { p { span '1'; span '2' } }.render #=> "<p><span>1</span><span>2</span></p>"
110
145
 
111
- H { hr() }.render #=> "<hr/>"
146
+ Papercraft.html { hr() }.render #=> "<hr/>"
112
147
  ```
113
148
 
114
149
  Tag methods also accept tag attributes, given as a hash:
115
150
 
116
151
  ```ruby
117
- H { img src: '/my.gif' }.render #=> "<img src="/my.gif"/>
152
+ Papercraft.html { img src: '/my.gif' }.render #=> "<img src="/my.gif"/>
118
153
 
119
- H { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
154
+ Papercraft.html { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
120
155
  ```
121
156
 
122
157
  ## Template parameters
@@ -126,24 +161,24 @@ parameters are specified as block parameters, and are passed to the template on
126
161
  rendering:
127
162
 
128
163
  ```ruby
129
- greeting = H { |name| h1 "Hello, #{name}!" }
164
+ greeting = Papercraft.html { |name| h1 "Hello, #{name}!" }
130
165
  greeting.render('world') #=> "<h1>Hello, world!</h1>"
131
166
  ```
132
167
 
133
168
  Templates can also accept named parameters:
134
169
 
135
170
  ```ruby
136
- greeting = H { |name:| h1 "Hello, #{name}!" }
171
+ greeting = Papercraft.html { |name:| h1 "Hello, #{name}!" }
137
172
  greeting.render(name: 'world') #=> "<h1>Hello, world!</h1>"
138
173
  ```
139
174
 
140
- ## Logic in templates
175
+ ## Template logic
141
176
 
142
177
  Since Papercraft templates are just a bunch of Ruby, you can easily write your
143
178
  view logic right in the template:
144
179
 
145
180
  ```ruby
146
- H { |user = nil|
181
+ Papercraft.html { |user = nil|
147
182
  if user
148
183
  span "Hello, #{user.name}!"
149
184
  else
@@ -157,7 +192,7 @@ H { |user = nil|
157
192
  Templates can also accept and render blocks by using `emit_yield`:
158
193
 
159
194
  ```ruby
160
- page = H {
195
+ page = Papercraft.html {
161
196
  html {
162
197
  body { emit_yield }
163
198
  }
@@ -167,24 +202,24 @@ page = H {
167
202
  page.render { h1 'hi' }
168
203
  ```
169
204
 
170
- ## Plain procs as components
205
+ ## Plain procs as templates
171
206
 
172
207
  With Papercraft you can write a template as a plain Ruby proc, and later render
173
- it by passing it as a block to `H`:
208
+ it by passing it as a block to `Papercraft.html`:
174
209
 
175
210
  ```ruby
176
211
  greeting = proc { |name| h1 "Hello, #{name}!" }
177
- H(&greeting).render('world')
212
+ Papercraft.html(&greeting).render('world')
178
213
  ```
179
214
 
180
215
  Components can also be expressed using lambda notation:
181
216
 
182
217
  ```ruby
183
218
  greeting = ->(name) { h1 "Hello, #{name}!" }
184
- H(&greeting).render('world')
219
+ Papercraft.html(&greeting).render('world')
185
220
  ```
186
221
 
187
- ## Component composition
222
+ ## Template composition
188
223
 
189
224
  Papercraft makes it easy to compose multiple components into a whole HTML
190
225
  document. A Papercraft component can contain other components, as the following
@@ -208,7 +243,7 @@ ItemList = ->(items) {
208
243
  }
209
244
  }
210
245
 
211
- page = H { |title, items|
246
+ page = Papercraft.html { |title, items|
212
247
  html5 {
213
248
  head { Title(title) }
214
249
  body { ItemList(items) }
@@ -227,7 +262,7 @@ non-constant components by invoking the `#emit` method:
227
262
  ```ruby
228
263
  greeting = -> { span "Hello, world" }
229
264
 
230
- H {
265
+ Papercraft.html {
231
266
  div {
232
267
  emit greeting
233
268
  }
@@ -245,12 +280,12 @@ or block to the original component:
245
280
 
246
281
  ```ruby
247
282
  # parameter application
248
- hello = H { |name| h1 "Hello, #{name}!" }
283
+ hello = Papercraft.html { |name| h1 "Hello, #{name}!" }
249
284
  hello_world = hello.apply('world')
250
285
  hello_world.render #=> "<h1>Hello, world!</h1>"
251
286
 
252
287
  # block application
253
- div_wrap = H { div { emit_yield } }
288
+ div_wrap = Papercraft.html { div { emit_yield } }
254
289
  wrapped_h1 = div_wrap.apply { h1 'hi' }
255
290
  wrapped_h1.render #=> "<div><h1>hi</h1></div>"
256
291
 
@@ -269,8 +304,8 @@ markup, enhancing components or injecting component parameters.
269
304
  Here is a HOC that takes a component as parameter:
270
305
 
271
306
  ```ruby
272
- div_wrap = H { |inner| div { emit inner } }
273
- greeter = H { h1 'hi' }
307
+ div_wrap = Papercraft.html { |inner| div { emit inner } }
308
+ greeter = Papercraft.html { h1 'hi' }
274
309
  wrapped_greeter = div_wrap.apply(greeter)
275
310
  wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
276
311
  ```
@@ -278,7 +313,7 @@ wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
278
313
  The inner component can also be passed as a block, as shown above:
279
314
 
280
315
  ```ruby
281
- div_wrap = H { div { emit_yield } }
316
+ div_wrap = Papercraft.html { div { emit_yield } }
282
317
  wrapped_greeter = div_wrap.apply { h1 'hi' }
283
318
  wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
284
319
  ```
@@ -292,7 +327,7 @@ this by creating a `default` page template that takes a block, then use `#apply`
292
327
  to create the other templates:
293
328
 
294
329
  ```ruby
295
- default_layout = H { |**params|
330
+ default_layout = Papercraft.html { |**params|
296
331
  html5 {
297
332
  head {
298
333
  title: params[:title]
@@ -321,7 +356,7 @@ article_layout.render(
321
356
  Raw HTML can be emitted using `#emit`:
322
357
 
323
358
  ```ruby
324
- wrapped = H { |html| div { emit html } }
359
+ wrapped = Papercraft.html { |html| div { emit html } }
325
360
  wrapped.render("<h1>hi</h1>") #=> "<div><h1>hi</h1></div>"
326
361
  ```
327
362
 
@@ -331,7 +366,7 @@ To emit a string with proper HTML encoding, without wrapping it in an HTML
331
366
  element, use `#text`:
332
367
 
333
368
  ```ruby
334
- H { str 'hi&lo' }.render #=> "hi&amp;lo"
369
+ Papercraft.html { str 'hi&lo' }.render #=> "hi&amp;lo"
335
370
  ```
336
371
 
337
372
  ## Emitting Markdown
@@ -341,7 +376,7 @@ Markdown is rendered using the
341
376
  `#emit_markdown`:
342
377
 
343
378
  ```ruby
344
- template = H { |md| div { emit_markdown md } }
379
+ template = Papercraft.html { |md| div { emit_markdown md } }
345
380
  template.render("Here's some *Markdown*") #=> "<div><p>Here's some <em>Markdown</em><p>\n</div>"
346
381
  ```
347
382
 
@@ -350,10 +385,18 @@ options](https://kramdown.gettalong.org/options.html#available-options) can be
350
385
  specified by adding them to the `#emit_markdown` call:
351
386
 
352
387
  ```ruby
353
- template = H { |md| div { emit_markdown md, auto_ids: false } }
388
+ template = Papercraft.html { |md| div { emit_markdown md, auto_ids: false } }
354
389
  template.render("# title") #=> "<div><h1>title</h1></div>"
355
390
  ```
356
391
 
392
+ The `#emit_markdown` method is available only to HTML templates. If you need to
393
+ render markdown in XML or JSON templates (usually for implementing RSS or JSON
394
+ feeds), you can use `Papercraft.markdown` directly:
395
+
396
+ ```ruby
397
+ Papercraft.markdown('# title') #=> "<h1>title</h1>"
398
+ ```
399
+
357
400
  The default Kramdown options are:
358
401
 
359
402
  ```ruby
@@ -366,10 +409,26 @@ The default Kramdown options are:
366
409
  ```
367
410
 
368
411
  The deafult options can be configured by accessing
369
- `Papercraft::HTML.kramdown_options`, e.g.:
412
+ `Papercraft.default_kramdown_options`, e.g.:
370
413
 
371
414
  ```ruby
372
- Papercraft::HTML.kramdown_options[:auto_ids] = false
415
+ Papercraft.default_kramdown_options[:auto_ids] = false
416
+ ```
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
373
432
  ```
374
433
 
375
434
  ## Deferred evaluation
@@ -393,7 +452,7 @@ class that can collect JS and CSS dependencies from the different components
393
452
  integrated into the page, and adds them to the page's `<head>` element:
394
453
 
395
454
  ```ruby
396
- default_layout = H { |**args|
455
+ default_layout = Papercraft.html { |**args|
397
456
  @dependencies = DependencyMananger.new
398
457
  head {
399
458
  defer { emit @dependencies.head_markup }
@@ -479,7 +538,7 @@ The call to `Papercraft::extension` lets us access the different methods of
479
538
  we'll be able to express the above markup as follows:
480
539
 
481
540
  ```ruby
482
- H {
541
+ Papercraft.html {
483
542
  bootstrap.card(style: 'width: 18rem') {
484
543
  bootstrap.card_title 'Card title'
485
544
  bootstrap.card_subtitle 'Card subtitle'
@@ -490,6 +549,74 @@ H {
490
549
  }
491
550
  ```
492
551
 
552
+
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:
593
+
594
+ ```ruby
595
+ Papercraft.json {
596
+ item 1
597
+ item 2
598
+ item 3
599
+ }.render #=> "[1,2,3]"
600
+ ```
601
+
602
+ Otherwise, you can create arbitrarily complex JSON structures by mixing hashes
603
+ and arrays:
604
+
605
+ ```Ruby
606
+ Papercraft.json {
607
+ foo {
608
+ bar {
609
+ item nil
610
+ item true
611
+ item 123.456
612
+ }
613
+ }
614
+ }.render #=> "{\"foo\":{\"bar\":[null,true,123.456]}}"
615
+ ```
616
+
617
+ Papercraft uses the [JSON gem](https://rubyapi.org/3.1/o/json) under the hood in
618
+ order to generate actual JSON.
619
+
493
620
  ## API Reference
494
621
 
495
622
  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
 
@@ -109,15 +122,13 @@ module Papercraft
109
122
  push_emit_yield_block(block) if block
110
123
  instance_exec(*a, **b, &template)
111
124
  end.to_s
112
- rescue ArgumentError => e
113
- raise Papercraft::Error, e.message
114
125
  end
115
126
 
116
127
  # Creates a new component, applying the given parameters and or block to the
117
128
  # current one. Application is one of the principal methods of composing
118
129
  # components, particularly when passing inner components as blocks:
119
130
  #
120
- # article_wrapper = H {
131
+ # article_wrapper = Papercraft.html {
121
132
  # article {
122
133
  # emit_yield
123
134
  # }
@@ -133,7 +144,7 @@ module Papercraft
133
144
  # @return [Papercraft::Component] applied component
134
145
  def apply(*a, **b, &block)
135
146
  template = self
136
- Component.new(&proc do |*x, **y|
147
+ Component.new(mode: @mode, mime_type: @mime_type, &proc do |*x, **y|
137
148
  push_emit_yield_block(block) if block
138
149
  instance_exec(*a, *x, **b, **y, &template)
139
150
  end)
@@ -149,10 +160,16 @@ module Papercraft
149
160
  HTMLRenderer
150
161
  when :xml
151
162
  XMLRenderer
163
+ when :json
164
+ JSONRenderer
152
165
  else
153
166
  raise "Invalid mode #{@mode.inspect}"
154
167
  end
155
168
  end
169
+
170
+ def mime_type
171
+ @mime_type
172
+ end
156
173
 
157
174
  # def compile
158
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,20 +213,21 @@ 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
221
224
  # @param **b [Hash] named arguments to pass to a proc
222
225
  # @return [void]
223
- def emit(o, *a, **b)
226
+ def emit(o, *a, **b, &block)
224
227
  case o
225
228
  when ::Proc
226
229
  Renderer.verify_proc_parameters(o, a, b)
230
+ push_emit_yield_block(block) if block
227
231
  instance_exec(*a, **b, &o)
228
232
  when nil
229
233
  else
@@ -234,7 +238,7 @@ module Papercraft
234
238
 
235
239
  # Emits a block supplied using `Component#apply` or `Component#render`.
236
240
  #
237
- # div_wrap = H { |*args| div { emit_yield(*args) } }
241
+ # div_wrap = Papercraft.html { |*args| div { emit_yield(*args) } }
238
242
  # greeter = div_wrap.apply { |name| h1 "Hello, #{name}!" }
239
243
  # greeter.render('world') #=> "<div><h1>Hello, world!</h1></div>"
240
244
  #
@@ -256,7 +260,7 @@ module Papercraft
256
260
  # adding elements to the `<head>` section. Here's how a title can be
257
261
  # controlled from a nested component:
258
262
  #
259
- # layout = H {
263
+ # layout = Papercraft.html {
260
264
  # html {
261
265
  # head {
262
266
  # defer { title @title }
@@ -380,4 +384,8 @@ module Papercraft
380
384
  EscapeUtils.escape_xml(text.to_s)
381
385
  end
382
386
  end
387
+
388
+ class JSONRenderer < Renderer
389
+ include JSON
390
+ end
383
391
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Papercraft
4
- VERSION = '0.14'
4
+ VERSION = '0.18'
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.14'
4
+ version: '0.18'
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-02-04 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