papercraft 0.14 → 0.18

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