papercraft 0.16 → 0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +145 -53
- data/lib/papercraft/html.rb +7 -34
- data/lib/papercraft/renderer.rb +11 -155
- data/lib/papercraft/tags.rb +152 -0
- data/lib/papercraft/{component.rb → template.rb} +26 -26
- data/lib/papercraft/version.rb +1 -1
- data/lib/papercraft/xml.rb +26 -0
- data/lib/papercraft.rb +96 -59
- data/papercraft.png +0 -0
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2eaf6ba31b9300176af0bf55b76f7bbdc16508d0e495ed1d76f9f273ea88f250
|
|
4
|
+
data.tar.gz: 90e132928ebfe78e577e71c8dc498f0ce1b1f2c58f453fbedaad96121c448a48
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fcada325a3f8cd1c4cbe38674efaf6a86d0eb947ad5815d07778e2a09a54bee2c7d6b731e87d110390e9c87abc0a61f81cbc4cb30fdce11a4fc8a8cd322466a0
|
|
7
|
+
data.tar.gz: 031c86f7dade95af3dca979b4543fcb26e1fab2eb98c5642746d6efcf8b4bd56fe30b70f2f5aa8309352f421cae9f116a901f464216f54a0be50630dcc52815f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## 0.20 2022-02-13
|
|
2
|
+
|
|
3
|
+
- Add support for XML namespaced tags and attributes (#9)
|
|
4
|
+
- Move and refactor HTML/XML common code to Tags module
|
|
5
|
+
|
|
6
|
+
## 0.19 2022-02-05
|
|
7
|
+
|
|
8
|
+
- Rename `Papercraft::Component` to `Papercraft::Template`
|
|
9
|
+
|
|
10
|
+
## 0.18 2022-02-04
|
|
11
|
+
|
|
12
|
+
- Cleanup and update examples
|
|
13
|
+
- Fix behaviour of #emit with block
|
|
14
|
+
- Improve README
|
|
15
|
+
|
|
16
|
+
## 0.17 2022-01-23
|
|
17
|
+
|
|
18
|
+
- Refactor markdown code, add `Papercraft.markdown` method (#8)
|
|
19
|
+
|
|
1
20
|
## 0.16 2022-01-23
|
|
2
21
|
|
|
3
22
|
- Implement JSON templating (#7)
|
data/README.md
CHANGED
|
@@ -24,35 +24,63 @@
|
|
|
24
24
|
|
|
25
25
|
## What is Papercraft?
|
|
26
26
|
|
|
27
|
+
Papercraft is a templating engine for dynamically producing HTML, XML or JSON.
|
|
28
|
+
Papercraft templates are expressed in plain Ruby, leading to easier debugging,
|
|
29
|
+
better protection against HTML/XML injection attacks, and better code reuse.
|
|
30
|
+
|
|
31
|
+
Papercraft templates can be composed in a variety of ways, facilitating the
|
|
32
|
+
usage of layout templates, and enabling a component-oriented approach to
|
|
33
|
+
building complex web interfaces.
|
|
34
|
+
|
|
35
|
+
In Papercraft, dynamic data is passed explicitly to the template as block
|
|
36
|
+
arguments, making the data flow easy to follow and understand. Papercraft also
|
|
37
|
+
lets developers create derivative templates using full or partial parameter
|
|
38
|
+
application.
|
|
39
|
+
|
|
40
|
+
Papercraft includes built-in support for rendering Markdown (using
|
|
41
|
+
[Kramdown](https://github.com/gettalong/kramdown/)), as well as support for
|
|
42
|
+
creating template extensions in order to allow the creation of component
|
|
43
|
+
libraries.
|
|
44
|
+
|
|
27
45
|
```ruby
|
|
28
46
|
require 'papercraft'
|
|
29
47
|
|
|
30
48
|
page = Papercraft.html { |*args|
|
|
31
49
|
html {
|
|
32
|
-
head { }
|
|
50
|
+
head { title 'Title' }
|
|
33
51
|
body { emit_yield *args }
|
|
34
52
|
}
|
|
35
53
|
}
|
|
36
54
|
page.render { p 'foo' }
|
|
37
|
-
#=> "<html><head
|
|
55
|
+
#=> "<html><head><title>Title</title></head><body><p>foo</p></body></html>"
|
|
38
56
|
|
|
39
57
|
hello = page.apply { |name| h1 "Hello, #{name}!" }
|
|
40
58
|
hello.render('world')
|
|
41
|
-
#=> "<html><head
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
59
|
+
#=> "<html><head><title>Title</title></head><body><h1>Hello, world!</h1></body></html>"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Table of content
|
|
63
|
+
|
|
64
|
+
- [Installing papercraft](#installing-papercraft)
|
|
65
|
+
- [Basic usage](#basic-usage)
|
|
66
|
+
- [Adding tags](#adding-tags)
|
|
67
|
+
- [Template parameters](#template-parameters)
|
|
68
|
+
- [Template logic](#template-logic)
|
|
69
|
+
- [Template blocks](#template-blocks)
|
|
70
|
+
- [Plain procs as templates](#plain-procs-as-templates)
|
|
71
|
+
- [Template composition](#template-composition)
|
|
72
|
+
- [Parameter and block application](#parameter-and-block-application)
|
|
73
|
+
- [Higher-order templates](#higher-order-templates)
|
|
74
|
+
- [Layout template composition](#layout-template-composition)
|
|
75
|
+
- [Emitting raw HTML](#emitting-raw-html)
|
|
76
|
+
- [Emitting a string with HTML Encoding](#emitting-a-string-with-html-encoding)
|
|
77
|
+
- [Emitting Markdown](#emitting-markdown)
|
|
78
|
+
- [Working with MIME types](#working-with-mime-types)
|
|
79
|
+
- [Deferred evaluation](#deferred-evaluation)
|
|
80
|
+
- [Papercraft extensions](#papercraft-extensions)
|
|
81
|
+
- [XML templates](#xml-templates)
|
|
82
|
+
- [JSON templates](#json-templates)
|
|
83
|
+
- [API Reference](#api-reference)
|
|
56
84
|
|
|
57
85
|
## Installing Papercraft
|
|
58
86
|
|
|
@@ -68,9 +96,9 @@ Or manually:
|
|
|
68
96
|
$ gem install papercraft
|
|
69
97
|
```
|
|
70
98
|
|
|
71
|
-
##
|
|
99
|
+
## Basic usage
|
|
72
100
|
|
|
73
|
-
To create
|
|
101
|
+
To create an HTML template use `Papercraft.html`:
|
|
74
102
|
|
|
75
103
|
```ruby
|
|
76
104
|
require 'papercraft'
|
|
@@ -80,13 +108,16 @@ html = Papercraft.html {
|
|
|
80
108
|
}
|
|
81
109
|
```
|
|
82
110
|
|
|
111
|
+
(You can also use `Papercraft.xml` and `Papercraft.json` to create XML and JSON
|
|
112
|
+
templates, respectively.)
|
|
113
|
+
|
|
83
114
|
Rendering a template is done using `#render`:
|
|
84
115
|
|
|
85
116
|
```ruby
|
|
86
117
|
html.render #=> "<div id="greeter"><p>Hello!</p></div>"
|
|
87
118
|
```
|
|
88
119
|
|
|
89
|
-
##
|
|
120
|
+
## Adding tags
|
|
90
121
|
|
|
91
122
|
Tags are added using unqualified method calls, and can be nested using blocks:
|
|
92
123
|
|
|
@@ -141,7 +172,7 @@ greeting = Papercraft.html { |name:| h1 "Hello, #{name}!" }
|
|
|
141
172
|
greeting.render(name: 'world') #=> "<h1>Hello, world!</h1>"
|
|
142
173
|
```
|
|
143
174
|
|
|
144
|
-
##
|
|
175
|
+
## Template logic
|
|
145
176
|
|
|
146
177
|
Since Papercraft templates are just a bunch of Ruby, you can easily write your
|
|
147
178
|
view logic right in the template:
|
|
@@ -171,10 +202,10 @@ page = Papercraft.html {
|
|
|
171
202
|
page.render { h1 'hi' }
|
|
172
203
|
```
|
|
173
204
|
|
|
174
|
-
## Plain procs as
|
|
205
|
+
## Plain procs as templates
|
|
175
206
|
|
|
176
207
|
With Papercraft you can write a template as a plain Ruby proc, and later render
|
|
177
|
-
it by passing it as a block to `
|
|
208
|
+
it by passing it as a block to `Papercraft.html`:
|
|
178
209
|
|
|
179
210
|
```ruby
|
|
180
211
|
greeting = proc { |name| h1 "Hello, #{name}!" }
|
|
@@ -188,10 +219,10 @@ greeting = ->(name) { h1 "Hello, #{name}!" }
|
|
|
188
219
|
Papercraft.html(&greeting).render('world')
|
|
189
220
|
```
|
|
190
221
|
|
|
191
|
-
##
|
|
222
|
+
## Template composition
|
|
192
223
|
|
|
193
|
-
Papercraft makes it easy to compose multiple
|
|
194
|
-
document. A Papercraft
|
|
224
|
+
Papercraft makes it easy to compose multiple templates into a whole HTML
|
|
225
|
+
document. A Papercraft template can contain other templates, as the following
|
|
195
226
|
example shows.
|
|
196
227
|
|
|
197
228
|
```ruby
|
|
@@ -219,14 +250,14 @@ page = Papercraft.html { |title, items|
|
|
|
219
250
|
}
|
|
220
251
|
}
|
|
221
252
|
|
|
222
|
-
page.render('Hello from
|
|
253
|
+
page.render('Hello from composed templates', [
|
|
223
254
|
{ id: 1, text: 'foo', checked: false },
|
|
224
255
|
{ id: 2, text: 'bar', checked: true }
|
|
225
256
|
])
|
|
226
257
|
```
|
|
227
258
|
|
|
228
|
-
In addition to using
|
|
229
|
-
non-constant
|
|
259
|
+
In addition to using templates defined as constants, you can also use
|
|
260
|
+
non-constant templates by invoking the `#emit` method:
|
|
230
261
|
|
|
231
262
|
```ruby
|
|
232
263
|
greeting = -> { span "Hello, world" }
|
|
@@ -241,11 +272,11 @@ Papercraft.html {
|
|
|
241
272
|
## Parameter and block application
|
|
242
273
|
|
|
243
274
|
Parameters and blocks can be applied to a template without it being rendered, by
|
|
244
|
-
using `#apply`. This mechanism is what allows
|
|
245
|
-
creation of higher-order
|
|
275
|
+
using `#apply`. This mechanism is what allows template composition and the
|
|
276
|
+
creation of higher-order templates.
|
|
246
277
|
|
|
247
|
-
The `#apply` method returns a new
|
|
248
|
-
or block to the original
|
|
278
|
+
The `#apply` method returns a new template which applies the given parameters and
|
|
279
|
+
or block to the original template:
|
|
249
280
|
|
|
250
281
|
```ruby
|
|
251
282
|
# parameter application
|
|
@@ -258,19 +289,19 @@ div_wrap = Papercraft.html { div { emit_yield } }
|
|
|
258
289
|
wrapped_h1 = div_wrap.apply { h1 'hi' }
|
|
259
290
|
wrapped_h1.render #=> "<div><h1>hi</h1></div>"
|
|
260
291
|
|
|
261
|
-
# wrap a
|
|
292
|
+
# wrap a template
|
|
262
293
|
wrapped_hello_world = div_wrap.apply(&hello_world)
|
|
263
294
|
wrapped_hello_world.render #=> "<div><h1>Hello, world!</h1></div>"
|
|
264
295
|
```
|
|
265
296
|
|
|
266
|
-
## Higher-order
|
|
297
|
+
## Higher-order templates
|
|
267
298
|
|
|
268
|
-
Papercraft also lets you create higher-order
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
markup, enhancing
|
|
299
|
+
Papercraft also lets you create higher-order templates, that is,
|
|
300
|
+
templates that take other templates as parameters, or as blocks. Higher-order
|
|
301
|
+
templates are handy for creating layouts, wrapping templates in arbitrary
|
|
302
|
+
markup, enhancing templates or injecting template parameters.
|
|
272
303
|
|
|
273
|
-
Here is a
|
|
304
|
+
Here is a higher-order template that takes a template as parameter:
|
|
274
305
|
|
|
275
306
|
```ruby
|
|
276
307
|
div_wrap = Papercraft.html { |inner| div { emit inner } }
|
|
@@ -279,7 +310,7 @@ wrapped_greeter = div_wrap.apply(greeter)
|
|
|
279
310
|
wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
|
280
311
|
```
|
|
281
312
|
|
|
282
|
-
The inner
|
|
313
|
+
The inner template can also be passed as a block, as shown above:
|
|
283
314
|
|
|
284
315
|
```ruby
|
|
285
316
|
div_wrap = Papercraft.html { div { emit_yield } }
|
|
@@ -289,7 +320,7 @@ wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
|
|
289
320
|
|
|
290
321
|
## Layout template composition
|
|
291
322
|
|
|
292
|
-
One of the principal uses of higher-order
|
|
323
|
+
One of the principal uses of higher-order templates is the creation of nested
|
|
293
324
|
layouts. Suppose we have a website with a number of different layouts, and we'd
|
|
294
325
|
like to avoid having to repeat the same code in the different layouts. We can do
|
|
295
326
|
this by creating a `default` page template that takes a block, then use `#apply`
|
|
@@ -358,6 +389,14 @@ template = Papercraft.html { |md| div { emit_markdown md, auto_ids: false } }
|
|
|
358
389
|
template.render("# title") #=> "<div><h1>title</h1></div>"
|
|
359
390
|
```
|
|
360
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
|
+
|
|
361
400
|
The default Kramdown options are:
|
|
362
401
|
|
|
363
402
|
```ruby
|
|
@@ -370,17 +409,33 @@ The default Kramdown options are:
|
|
|
370
409
|
```
|
|
371
410
|
|
|
372
411
|
The deafult options can be configured by accessing
|
|
373
|
-
`Papercraft
|
|
412
|
+
`Papercraft.default_kramdown_options`, e.g.:
|
|
413
|
+
|
|
414
|
+
```ruby
|
|
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`:
|
|
374
425
|
|
|
375
426
|
```ruby
|
|
376
|
-
|
|
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
|
|
377
432
|
```
|
|
378
433
|
|
|
379
434
|
## Deferred evaluation
|
|
380
435
|
|
|
381
436
|
Deferred evaluation allows deferring the rendering of parts of a template until
|
|
382
|
-
the last moment, thus allowing an inner
|
|
383
|
-
outer
|
|
437
|
+
the last moment, thus allowing an inner template to manipulate the state of the
|
|
438
|
+
outer template. To in order to defer a part of a template, use `#defer`, and
|
|
384
439
|
include any markup in the provided block. This technique, in in conjunction with
|
|
385
440
|
holding state in instance variables, is an alternative to passing parameters,
|
|
386
441
|
which can be limiting in some situations.
|
|
@@ -389,11 +444,11 @@ A few use cases for deferred evaulation come to mind:
|
|
|
389
444
|
|
|
390
445
|
- Setting the page title.
|
|
391
446
|
- Adding a flash message to a page.
|
|
392
|
-
- Using
|
|
447
|
+
- Using templates that dynamically add static dependencies (JS and CSS) to the
|
|
393
448
|
page.
|
|
394
449
|
|
|
395
450
|
The last use case is particularly interesting. Imagine a `DependencyMananger`
|
|
396
|
-
class that can collect JS and CSS dependencies from the different
|
|
451
|
+
class that can collect JS and CSS dependencies from the different templates
|
|
397
452
|
integrated into the page, and adds them to the page's `<head>` element:
|
|
398
453
|
|
|
399
454
|
```ruby
|
|
@@ -494,11 +549,47 @@ Papercraft.html {
|
|
|
494
549
|
}
|
|
495
550
|
```
|
|
496
551
|
|
|
497
|
-
## JSON templating
|
|
498
552
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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:
|
|
502
593
|
|
|
503
594
|
```ruby
|
|
504
595
|
Papercraft.json {
|
|
@@ -523,7 +614,8 @@ Papercraft.json {
|
|
|
523
614
|
}.render #=> "{\"foo\":{\"bar\":[null,true,123.456]}}"
|
|
524
615
|
```
|
|
525
616
|
|
|
526
|
-
Papercraft uses the [JSON gem](https://rubyapi.org/3.1/o/json) under the hood
|
|
617
|
+
Papercraft uses the [JSON gem](https://rubyapi.org/3.1/o/json) under the hood in
|
|
618
|
+
order to generate actual JSON.
|
|
527
619
|
|
|
528
620
|
## API Reference
|
|
529
621
|
|
data/lib/papercraft/html.rb
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require 'rouge'
|
|
5
|
-
require 'kramdown-parser-gfm'
|
|
3
|
+
require_relative './tags'
|
|
6
4
|
|
|
7
5
|
module Papercraft
|
|
8
6
|
# HTML Markup extensions
|
|
9
7
|
module HTML
|
|
8
|
+
include Tags
|
|
9
|
+
|
|
10
10
|
# Emits the p tag (overrides Object#p)
|
|
11
11
|
#
|
|
12
12
|
# @param text [String] text content of tag
|
|
@@ -83,41 +83,14 @@ module Papercraft
|
|
|
83
83
|
# @param **opts [Hash] Kramdown options
|
|
84
84
|
# @return [void]
|
|
85
85
|
def emit_markdown(markdown, **opts)
|
|
86
|
-
emit
|
|
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
|
|
86
|
+
emit Papercraft.markdown(markdown, **opts)
|
|
111
87
|
end
|
|
112
88
|
|
|
113
89
|
private
|
|
114
90
|
|
|
115
|
-
#
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
# @return [Hash] Merged Kramdown options
|
|
119
|
-
def kramdown_options(opts)
|
|
120
|
-
HTML.kramdown_options.merge(**opts)
|
|
91
|
+
# Escapes the given text using XML entities.
|
|
92
|
+
def escape_text(text)
|
|
93
|
+
EscapeUtils.escape_html(text.to_s)
|
|
121
94
|
end
|
|
122
95
|
end
|
|
123
96
|
end
|
data/lib/papercraft/renderer.rb
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative './html'
|
|
4
|
+
require_relative './xml'
|
|
4
5
|
require_relative './json'
|
|
5
6
|
require_relative './extension_proxy'
|
|
6
7
|
|
|
7
8
|
module Papercraft
|
|
8
9
|
|
|
9
|
-
# A Renderer renders a Papercraft
|
|
10
|
+
# A Renderer renders a Papercraft template into a string
|
|
10
11
|
class Renderer
|
|
11
|
-
|
|
12
|
+
|
|
12
13
|
class << self
|
|
13
14
|
|
|
14
15
|
# Verifies that the given template proc can be called with the given
|
|
@@ -41,9 +42,9 @@ module Papercraft
|
|
|
41
42
|
#
|
|
42
43
|
# Installs the given extensions, passed in the form of a Ruby hash mapping
|
|
43
44
|
# methods to extension modules. The methods will be available to all
|
|
44
|
-
# Papercraft
|
|
45
|
+
# Papercraft templates. Extension methods are executed in the context of
|
|
45
46
|
# the the renderer instance, so they can look just like normal proc
|
|
46
|
-
# components. In cases where method names in the module clash with
|
|
47
|
+
# components. In cases where method names in the module clash with XML
|
|
47
48
|
# tag names, you can use the `#tag` method to emit the relevant tag.
|
|
48
49
|
#
|
|
49
50
|
# module ComponentLibrary
|
|
@@ -111,99 +112,6 @@ module Papercraft
|
|
|
111
112
|
@buffer
|
|
112
113
|
end
|
|
113
114
|
|
|
114
|
-
# The tag method template below is optimized for performance. Do not touch!
|
|
115
|
-
|
|
116
|
-
S_TAG_METHOD_LINE = __LINE__ + 2
|
|
117
|
-
S_TAG_METHOD = <<~EOF
|
|
118
|
-
S_TAG_%<TAG>s_PRE = %<tag_pre>s
|
|
119
|
-
S_TAG_%<TAG>s_CLOSE = %<tag_close>s
|
|
120
|
-
|
|
121
|
-
def %<tag>s(text = nil, **props, &block)
|
|
122
|
-
if text.is_a?(Hash) && props.empty?
|
|
123
|
-
props = text
|
|
124
|
-
text = nil
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
@buffer << S_TAG_%<TAG>s_PRE
|
|
128
|
-
emit_props(props) unless props.empty?
|
|
129
|
-
|
|
130
|
-
if block
|
|
131
|
-
@buffer << S_GT
|
|
132
|
-
instance_eval(&block)
|
|
133
|
-
@buffer << S_TAG_%<TAG>s_CLOSE
|
|
134
|
-
elsif Proc === text
|
|
135
|
-
@buffer << S_GT
|
|
136
|
-
emit(text)
|
|
137
|
-
@buffer << S_TAG_%<TAG>s_CLOSE
|
|
138
|
-
elsif text
|
|
139
|
-
@buffer << S_GT << escape_text(text.to_s) << S_TAG_%<TAG>s_CLOSE
|
|
140
|
-
else
|
|
141
|
-
@buffer << S_SLASH_GT
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
EOF
|
|
145
|
-
|
|
146
|
-
# Emits an HTML tag with the given content, properties and optional block.
|
|
147
|
-
# This method is an alternative to emitting HTML tags using dynamically
|
|
148
|
-
# created methods. This is particularly useful when using extensions that
|
|
149
|
-
# have method names that clash with HTML tags, such as `button` or `a`, or
|
|
150
|
-
# when you need to override the behaviour of a particular HTML tag.
|
|
151
|
-
#
|
|
152
|
-
# The following two method calls have the same effect:
|
|
153
|
-
#
|
|
154
|
-
# button 'text', id: 'button1'
|
|
155
|
-
# tag :button, 'text', id: 'button1'
|
|
156
|
-
#
|
|
157
|
-
# @param sym [Symbol, String] HTML tag
|
|
158
|
-
# @param text [String, nil] tag content
|
|
159
|
-
# @param **props [Hash] tag attributes
|
|
160
|
-
# @param &block [Proc] optional inner HTML
|
|
161
|
-
# @return [void]
|
|
162
|
-
def tag(sym, text = nil, **props, &block)
|
|
163
|
-
if text.is_a?(Hash) && props.empty?
|
|
164
|
-
props = text
|
|
165
|
-
text = nil
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
tag = sym.to_s.tr('_', '-')
|
|
169
|
-
|
|
170
|
-
@buffer << S_LT << tag
|
|
171
|
-
emit_props(props) unless props.empty?
|
|
172
|
-
|
|
173
|
-
if block
|
|
174
|
-
@buffer << S_GT
|
|
175
|
-
instance_eval(&block)
|
|
176
|
-
@buffer << S_LT_SLASH << tag << S_GT
|
|
177
|
-
elsif Proc === text
|
|
178
|
-
@buffer << S_GT
|
|
179
|
-
emit(text)
|
|
180
|
-
@buffer << S_LT_SLASH << tag << S_GT
|
|
181
|
-
elsif text
|
|
182
|
-
@buffer << S_GT << escape_text(text.to_s) << S_LT_SLASH << tag << S_GT
|
|
183
|
-
else
|
|
184
|
-
@buffer << S_SLASH_GT
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
# Catches undefined tag method call and handles it by defining the method.
|
|
189
|
-
#
|
|
190
|
-
# @param sym [Symbol] HTML tag or component identifier
|
|
191
|
-
# @param args [Array] method arguments
|
|
192
|
-
# @param opts [Hash] named method arguments
|
|
193
|
-
# @param &block [Proc] block passed to method
|
|
194
|
-
# @return [void]
|
|
195
|
-
def method_missing(sym, *args, **opts, &block)
|
|
196
|
-
tag = sym.to_s
|
|
197
|
-
code = S_TAG_METHOD % {
|
|
198
|
-
tag: tag,
|
|
199
|
-
TAG: tag.upcase,
|
|
200
|
-
tag_pre: "<#{tag.tr('_', '-')}".inspect,
|
|
201
|
-
tag_close: "</#{tag.tr('_', '-')}>".inspect
|
|
202
|
-
}
|
|
203
|
-
self.class.class_eval(code, __FILE__, S_TAG_METHOD_LINE)
|
|
204
|
-
send(sym, *args, **opts, &block)
|
|
205
|
-
end
|
|
206
|
-
|
|
207
115
|
# Emits the given object into the rendering buffer. If the given object is a
|
|
208
116
|
# proc or a component, `emit` will passes any additional arguments and named
|
|
209
117
|
# arguments to the object when rendering it. If the given object is nil,
|
|
@@ -217,14 +125,15 @@ module Papercraft
|
|
|
217
125
|
#
|
|
218
126
|
# Papercraft.html { emit nil }.render #=> ""
|
|
219
127
|
#
|
|
220
|
-
# @param o [Proc, Papercraft::
|
|
128
|
+
# @param o [Proc, Papercraft::Template, String] emitted object
|
|
221
129
|
# @param *a [Array<any>] arguments to pass to a proc
|
|
222
130
|
# @param **b [Hash] named arguments to pass to a proc
|
|
223
131
|
# @return [void]
|
|
224
|
-
def emit(o, *a, **b)
|
|
132
|
+
def emit(o, *a, **b, &block)
|
|
225
133
|
case o
|
|
226
134
|
when ::Proc
|
|
227
135
|
Renderer.verify_proc_parameters(o, a, b)
|
|
136
|
+
push_emit_yield_block(block) if block
|
|
228
137
|
instance_exec(*a, **b, &o)
|
|
229
138
|
when nil
|
|
230
139
|
else
|
|
@@ -250,10 +159,10 @@ module Papercraft
|
|
|
250
159
|
end
|
|
251
160
|
|
|
252
161
|
# Defers the given block to be evaluated later. Deferred evaluation allows
|
|
253
|
-
# Papercraft
|
|
162
|
+
# Papercraft templates to inject state into sibling components, regardless
|
|
254
163
|
# of the component's order in the container component. For example, a nested
|
|
255
164
|
# component may set an instance variable used by another component. This is
|
|
256
|
-
# an elegant solution to the problem of setting the
|
|
165
|
+
# an elegant solution to the problem of setting the XML page's title, or
|
|
257
166
|
# adding elements to the `<head>` section. Here's how a title can be
|
|
258
167
|
# controlled from a nested component:
|
|
259
168
|
#
|
|
@@ -285,24 +194,6 @@ module Papercraft
|
|
|
285
194
|
@buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
|
|
286
195
|
end
|
|
287
196
|
|
|
288
|
-
S_LT = '<'
|
|
289
|
-
S_GT = '>'
|
|
290
|
-
S_LT_SLASH = '</'
|
|
291
|
-
S_SPACE_LT_SLASH = ' </'
|
|
292
|
-
S_SLASH_GT = '/>'
|
|
293
|
-
S_SPACE = ' '
|
|
294
|
-
S_EQUAL_QUOTE = '="'
|
|
295
|
-
S_QUOTE = '"'
|
|
296
|
-
|
|
297
|
-
# Emits text into the rendering buffer, escaping any special characters to
|
|
298
|
-
# the respective HTML entities.
|
|
299
|
-
#
|
|
300
|
-
# @param data [String] text
|
|
301
|
-
# @return [void]
|
|
302
|
-
def text(data)
|
|
303
|
-
@buffer << escape_text(data)
|
|
304
|
-
end
|
|
305
|
-
|
|
306
197
|
private
|
|
307
198
|
|
|
308
199
|
# Escapes text. This method must be overriden in descendant classes.
|
|
@@ -319,29 +210,6 @@ module Papercraft
|
|
|
319
210
|
(@emit_yield_stack ||= []) << block
|
|
320
211
|
end
|
|
321
212
|
|
|
322
|
-
# Emits tag attributes into the rendering buffer
|
|
323
|
-
# @param props [Hash] tag attributes
|
|
324
|
-
# @return [void]
|
|
325
|
-
def emit_props(props)
|
|
326
|
-
props.each { |k, v|
|
|
327
|
-
case k
|
|
328
|
-
when :src, :href
|
|
329
|
-
@buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
|
|
330
|
-
EscapeUtils.escape_uri(v) << S_QUOTE
|
|
331
|
-
else
|
|
332
|
-
case v
|
|
333
|
-
when true
|
|
334
|
-
@buffer << S_SPACE << k.to_s.tr('_', '-')
|
|
335
|
-
when false, nil
|
|
336
|
-
# emit nothing
|
|
337
|
-
else
|
|
338
|
-
@buffer << S_SPACE << k.to_s.tr('_', '-') <<
|
|
339
|
-
S_EQUAL_QUOTE << v << S_QUOTE
|
|
340
|
-
end
|
|
341
|
-
end
|
|
342
|
-
}
|
|
343
|
-
end
|
|
344
|
-
|
|
345
213
|
# Renders a deferred proc by evaluating it, then adding the rendered result
|
|
346
214
|
# to the buffer.
|
|
347
215
|
#
|
|
@@ -363,23 +231,11 @@ module Papercraft
|
|
|
363
231
|
# Implements an HTML renderer
|
|
364
232
|
class HTMLRenderer < Renderer
|
|
365
233
|
include HTML
|
|
366
|
-
|
|
367
|
-
private
|
|
368
|
-
|
|
369
|
-
# Escapes the given text using HTML entities.
|
|
370
|
-
def escape_text(text)
|
|
371
|
-
EscapeUtils.escape_html(text.to_s)
|
|
372
|
-
end
|
|
373
234
|
end
|
|
374
235
|
|
|
375
236
|
# Implements an XML renderer
|
|
376
237
|
class XMLRenderer < Renderer
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
# Escapes the given text using XML entities.
|
|
380
|
-
def escape_text(text)
|
|
381
|
-
EscapeUtils.escape_xml(text.to_s)
|
|
382
|
-
end
|
|
238
|
+
include XML
|
|
383
239
|
end
|
|
384
240
|
|
|
385
241
|
class JSONRenderer < Renderer
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Papercraft
|
|
4
|
+
# Markup (HTML/XML) extensions
|
|
5
|
+
module Tags
|
|
6
|
+
S_LT = '<'
|
|
7
|
+
S_GT = '>'
|
|
8
|
+
S_LT_SLASH = '</'
|
|
9
|
+
S_SPACE_LT_SLASH = ' </'
|
|
10
|
+
S_SLASH_GT = '/>'
|
|
11
|
+
S_SPACE = ' '
|
|
12
|
+
S_EQUAL_QUOTE = '="'
|
|
13
|
+
S_QUOTE = '"'
|
|
14
|
+
|
|
15
|
+
# The tag method template below is optimized for performance. Do not touch!
|
|
16
|
+
|
|
17
|
+
S_TAG_METHOD_LINE = __LINE__ + 2
|
|
18
|
+
S_TAG_METHOD = <<~EOF
|
|
19
|
+
S_TAG_%<TAG>s_PRE = %<tag_pre>s
|
|
20
|
+
S_TAG_%<TAG>s_CLOSE = %<tag_close>s
|
|
21
|
+
|
|
22
|
+
def %<tag>s(text = nil, **props, &block)
|
|
23
|
+
if text.is_a?(Hash) && props.empty?
|
|
24
|
+
props = text
|
|
25
|
+
text = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@buffer << S_TAG_%<TAG>s_PRE
|
|
29
|
+
emit_props(props) unless props.empty?
|
|
30
|
+
|
|
31
|
+
if block
|
|
32
|
+
@buffer << S_GT
|
|
33
|
+
instance_eval(&block)
|
|
34
|
+
@buffer << S_TAG_%<TAG>s_CLOSE
|
|
35
|
+
elsif Proc === text
|
|
36
|
+
@buffer << S_GT
|
|
37
|
+
emit(text)
|
|
38
|
+
@buffer << S_TAG_%<TAG>s_CLOSE
|
|
39
|
+
elsif text
|
|
40
|
+
@buffer << S_GT << escape_text(text.to_s) << S_TAG_%<TAG>s_CLOSE
|
|
41
|
+
else
|
|
42
|
+
@buffer << S_SLASH_GT
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
EOF
|
|
46
|
+
|
|
47
|
+
# Emits an XML tag with the given content, properties and optional block.
|
|
48
|
+
# This method is an alternative to emitting XML tags using dynamically
|
|
49
|
+
# created methods. This is particularly useful when using extensions that
|
|
50
|
+
# have method names that clash with XML tags, such as `button` or `a`, or
|
|
51
|
+
# when you need to override the behaviour of a particular XML tag.
|
|
52
|
+
#
|
|
53
|
+
# The following two method calls have the same effect:
|
|
54
|
+
#
|
|
55
|
+
# button 'text', id: 'button1'
|
|
56
|
+
# tag :button, 'text', id: 'button1'
|
|
57
|
+
#
|
|
58
|
+
# @param sym [Symbol, String] XML tag
|
|
59
|
+
# @param text [String, nil] tag content
|
|
60
|
+
# @param **props [Hash] tag attributes
|
|
61
|
+
# @param &block [Proc] optional inner XML
|
|
62
|
+
# @return [void]
|
|
63
|
+
def tag(sym, text = nil, **props, &block)
|
|
64
|
+
if text.is_a?(Hash) && props.empty?
|
|
65
|
+
props = text
|
|
66
|
+
text = nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
tag = tag_repr(sym)
|
|
70
|
+
|
|
71
|
+
@buffer << S_LT << tag
|
|
72
|
+
emit_props(props) unless props.empty?
|
|
73
|
+
|
|
74
|
+
if block
|
|
75
|
+
@buffer << S_GT
|
|
76
|
+
instance_eval(&block)
|
|
77
|
+
@buffer << S_LT_SLASH << tag << S_GT
|
|
78
|
+
elsif Proc === text
|
|
79
|
+
@buffer << S_GT
|
|
80
|
+
emit(text)
|
|
81
|
+
@buffer << S_LT_SLASH << tag << S_GT
|
|
82
|
+
elsif text
|
|
83
|
+
@buffer << S_GT << escape_text(text.to_s) << S_LT_SLASH << tag << S_GT
|
|
84
|
+
else
|
|
85
|
+
@buffer << S_SLASH_GT
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Catches undefined tag method call and handles it by defining the method.
|
|
90
|
+
#
|
|
91
|
+
# @param sym [Symbol] XML tag or component identifier
|
|
92
|
+
# @param args [Array] method arguments
|
|
93
|
+
# @param opts [Hash] named method arguments
|
|
94
|
+
# @param &block [Proc] block passed to method
|
|
95
|
+
# @return [void]
|
|
96
|
+
def method_missing(sym, *args, **opts, &block)
|
|
97
|
+
# p method_missing: sym, self: self
|
|
98
|
+
tag = sym.to_s
|
|
99
|
+
repr = tag_repr(tag)
|
|
100
|
+
code = S_TAG_METHOD % {
|
|
101
|
+
tag: tag,
|
|
102
|
+
TAG: tag.upcase,
|
|
103
|
+
tag_pre: "<#{repr}".inspect,
|
|
104
|
+
tag_close: "</#{repr}>".inspect
|
|
105
|
+
}
|
|
106
|
+
self.class.class_eval(code, __FILE__, S_TAG_METHOD_LINE)
|
|
107
|
+
send(sym, *args, **opts, &block)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Emits text into the rendering buffer, escaping any special characters to
|
|
111
|
+
# the respective XML entities.
|
|
112
|
+
#
|
|
113
|
+
# @param data [String] text
|
|
114
|
+
# @return [void]
|
|
115
|
+
def text(data)
|
|
116
|
+
@buffer << escape_text(data)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def tag_repr(tag)
|
|
122
|
+
tag.to_s.tr('_', '-')
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def att_repr(att)
|
|
126
|
+
att.to_s.tr('_', '-')
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Emits tag attributes into the rendering buffer
|
|
130
|
+
# @param props [Hash] tag attributes
|
|
131
|
+
# @return [void]
|
|
132
|
+
def emit_props(props)
|
|
133
|
+
props.each { |k, v|
|
|
134
|
+
case k
|
|
135
|
+
when :src, :href
|
|
136
|
+
@buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
|
|
137
|
+
EscapeUtils.escape_uri(v) << S_QUOTE
|
|
138
|
+
else
|
|
139
|
+
case v
|
|
140
|
+
when true
|
|
141
|
+
@buffer << S_SPACE << att_repr(k)
|
|
142
|
+
when false, nil
|
|
143
|
+
# emit nothing
|
|
144
|
+
else
|
|
145
|
+
@buffer << S_SPACE << att_repr(k) <<
|
|
146
|
+
S_EQUAL_QUOTE << v << S_QUOTE
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
}
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -4,30 +4,30 @@ require_relative './html'
|
|
|
4
4
|
|
|
5
5
|
module Papercraft
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
# include other
|
|
7
|
+
# Template represents a distinct, reusable HTML template. A template can
|
|
8
|
+
# include other templates, and also be nested inside other templates.
|
|
9
9
|
#
|
|
10
|
-
# Since in Papercraft HTML is expressed using blocks (or procs,) the
|
|
10
|
+
# Since in Papercraft HTML is expressed using blocks (or procs,) the Template
|
|
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
|
-
#
|
|
14
|
+
# Templates are usually created using the class methods `html`, `xml` or
|
|
15
15
|
# `json`, for HTML, XML or JSON templates, respectively:
|
|
16
16
|
#
|
|
17
17
|
# greeter = Papercraft.html { |name| h1 "Hello, #{name}!" }
|
|
18
18
|
# greeter.render('world') #=> "<h1>Hello, world!</h1>"
|
|
19
19
|
#
|
|
20
|
-
#
|
|
20
|
+
# Templates can also be created using the normal constructor:
|
|
21
21
|
#
|
|
22
|
-
# greeter = Papercraft::
|
|
22
|
+
# greeter = Papercraft::Template.new(mode: :html) { |name| h1 "Hello, #{name}!" }
|
|
23
23
|
# greeter.render('world') #=> "<h1>Hello, world!</h1>"
|
|
24
24
|
#
|
|
25
|
-
# The different methods for creating
|
|
25
|
+
# The different methods for creating templates can also take a custom MIME
|
|
26
26
|
# type, by passing a `mime_type` named argument:
|
|
27
27
|
#
|
|
28
28
|
# json = Papercraft.json(mime_type: 'application/feed+json') { ... }
|
|
29
29
|
#
|
|
30
|
-
# In the
|
|
30
|
+
# In the template block, HTML elements are created by simply calling
|
|
31
31
|
# unqualified methods:
|
|
32
32
|
#
|
|
33
33
|
# page_layout = Papercraft.html {
|
|
@@ -41,20 +41,20 @@ module Papercraft
|
|
|
41
41
|
# }
|
|
42
42
|
# }
|
|
43
43
|
#
|
|
44
|
-
# Papercraft
|
|
44
|
+
# Papercraft templates can take explicit parameters in order to render
|
|
45
45
|
# dynamic content. This can be in the form of regular or named parameters. The
|
|
46
46
|
# `greeter` template shown above takes a single `name` parameter. Here's how a
|
|
47
|
-
# anchor
|
|
47
|
+
# anchor template could be implemented with named parameters:
|
|
48
48
|
#
|
|
49
49
|
# anchor = Papercraft.html { |uri: , text: | a(text, href: uri) }
|
|
50
50
|
#
|
|
51
|
-
# The above
|
|
51
|
+
# The above template could later be rendered by passing the needed arguments:
|
|
52
52
|
#
|
|
53
53
|
# anchor.render(uri: 'https://example.com', text: 'Example')
|
|
54
54
|
#
|
|
55
|
-
# ##
|
|
55
|
+
# ## Template Composition
|
|
56
56
|
#
|
|
57
|
-
# A
|
|
57
|
+
# A template can be included in another template using the `emit` method:
|
|
58
58
|
#
|
|
59
59
|
# links = Papercraft.html {
|
|
60
60
|
# emit anchor, uri: '/posts', text: 'Posts'
|
|
@@ -62,7 +62,7 @@ module Papercraft
|
|
|
62
62
|
# emit anchor, uri: '/about', text: 'About'
|
|
63
63
|
# }
|
|
64
64
|
#
|
|
65
|
-
# Another way of composing
|
|
65
|
+
# Another way of composing templates is to pass the templates themselves as
|
|
66
66
|
# parameters:
|
|
67
67
|
#
|
|
68
68
|
# links = Papercraft.html { |anchors|
|
|
@@ -74,8 +74,8 @@ module Papercraft
|
|
|
74
74
|
# anchor.apply(uri: '/about', text: 'About')
|
|
75
75
|
# ])
|
|
76
76
|
#
|
|
77
|
-
# The `#apply` method creates a new
|
|
78
|
-
# such that the
|
|
77
|
+
# The `#apply` method creates a new template, applying the given parameters
|
|
78
|
+
# such that the template can be rendered without parameters:
|
|
79
79
|
#
|
|
80
80
|
# links_with_anchors = links.apply([
|
|
81
81
|
# anchor.apply(uri: '/posts', text: 'Posts'),
|
|
@@ -84,7 +84,7 @@ module Papercraft
|
|
|
84
84
|
# ])
|
|
85
85
|
# links_with_anchors.render
|
|
86
86
|
#
|
|
87
|
-
class
|
|
87
|
+
class Template < Proc
|
|
88
88
|
|
|
89
89
|
# Determines the rendering mode: `:html` or `:xml`.
|
|
90
90
|
attr_accessor :mode
|
|
@@ -95,12 +95,12 @@ module Papercraft
|
|
|
95
95
|
json: 'application/json'
|
|
96
96
|
}.freeze
|
|
97
97
|
|
|
98
|
-
# Initializes a
|
|
98
|
+
# Initializes a template with the given block. The rendering mode (HTML or
|
|
99
99
|
# XML) can be passed in the `mode:` parameter. If `mode:` is not specified,
|
|
100
|
-
# the
|
|
100
|
+
# the template defaults to HTML.
|
|
101
101
|
#
|
|
102
102
|
# @param mode [:html, :xml] rendering mode
|
|
103
|
-
# @param mime_type [String, nil] the
|
|
103
|
+
# @param mime_type [String, nil] the template's mime type (nil for default)
|
|
104
104
|
# @param block [Proc] nested HTML block
|
|
105
105
|
def initialize(mode: :html, mime_type: nil, &block)
|
|
106
106
|
@mode = mode
|
|
@@ -124,9 +124,9 @@ module Papercraft
|
|
|
124
124
|
end.to_s
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
-
# Creates a new
|
|
127
|
+
# Creates a new template, applying the given parameters and or block to the
|
|
128
128
|
# current one. Application is one of the principal methods of composing
|
|
129
|
-
#
|
|
129
|
+
# templates, particularly when passing inner templates as blocks:
|
|
130
130
|
#
|
|
131
131
|
# article_wrapper = Papercraft.html {
|
|
132
132
|
# article {
|
|
@@ -141,19 +141,19 @@ module Papercraft
|
|
|
141
141
|
# @param *a [<any>] normal parameters
|
|
142
142
|
# @param **b [Hash] named parameters
|
|
143
143
|
# @param &block [Proc] inner block
|
|
144
|
-
# @return [Papercraft::
|
|
144
|
+
# @return [Papercraft::Template] applied template
|
|
145
145
|
def apply(*a, **b, &block)
|
|
146
146
|
template = self
|
|
147
|
-
|
|
147
|
+
Template.new(mode: @mode, mime_type: @mime_type, &proc do |*x, **y|
|
|
148
148
|
push_emit_yield_block(block) if block
|
|
149
149
|
instance_exec(*a, *x, **b, **y, &template)
|
|
150
150
|
end)
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
# Returns the Renderer class used for rendering the templates, according to
|
|
154
|
-
# the
|
|
154
|
+
# the template's mode.
|
|
155
155
|
#
|
|
156
|
-
# @return [Papercraft::Renderer] Renderer used for rendering the
|
|
156
|
+
# @return [Papercraft::Renderer] Renderer used for rendering the template
|
|
157
157
|
def renderer_class
|
|
158
158
|
case @mode
|
|
159
159
|
when :html
|
data/lib/papercraft/version.rb
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'escape_utils'
|
|
4
|
+
require_relative './tags'
|
|
5
|
+
|
|
6
|
+
module Papercraft
|
|
7
|
+
# XML renderer extensions
|
|
8
|
+
module XML
|
|
9
|
+
include Tags
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def tag_repr(tag)
|
|
14
|
+
tag.to_s.gsub('__', ':').tr('_', '-')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def att_repr(att)
|
|
18
|
+
att.to_s.gsub('__', ':').tr('_', '-')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Escapes the given text using XML entities.
|
|
22
|
+
def escape_text(text)
|
|
23
|
+
EscapeUtils.escape_xml(text.to_s)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/papercraft.rb
CHANGED
|
@@ -1,73 +1,110 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'kramdown'
|
|
4
|
+
require 'rouge'
|
|
5
|
+
require 'kramdown-parser-gfm'
|
|
4
6
|
|
|
5
|
-
require_relative 'papercraft/
|
|
7
|
+
require_relative 'papercraft/template'
|
|
6
8
|
require_relative 'papercraft/renderer'
|
|
7
9
|
require_relative 'papercraft/encoding'
|
|
8
10
|
# require_relative 'papercraft/compiler'
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
|
|
13
|
+
# Papercraft is a composable templating library
|
|
11
14
|
module Papercraft
|
|
12
15
|
# Exception class used to signal templating-related errors
|
|
13
16
|
class Error < RuntimeError; end
|
|
17
|
+
|
|
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 template. `Papercraft.html` can take either a proc
|
|
35
|
+
# argument or a block. In both cases, the proc is converted to a
|
|
36
|
+
# `Papercraft::Template`.
|
|
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::Template] Papercraft template
|
|
43
|
+
def html(o = nil, mime_type: nil, &template)
|
|
44
|
+
return o if o.is_a?(Papercraft::Template)
|
|
45
|
+
template ||= o
|
|
46
|
+
Papercraft::Template.new(mode: :html, mime_type: mime_type, &template)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Creates a new Papercraft template 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::Template`.
|
|
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::Template] Papercraft template
|
|
58
|
+
def xml(o = nil, mime_type: nil, &template)
|
|
59
|
+
return o if o.is_a?(Papercraft::Template)
|
|
60
|
+
template ||= o
|
|
61
|
+
Papercraft::Template.new(mode: :xml, mime_type: mime_type, &template)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Creates a new Papercraft template 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::Template`.
|
|
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::Template] Papercraft template
|
|
73
|
+
def json(o = nil, mime_type: nil, &template)
|
|
74
|
+
return o if o.is_a?(Papercraft::Template)
|
|
75
|
+
template ||= o
|
|
76
|
+
Papercraft::Template.new(mode: :json, mime_type: mime_type, &template)
|
|
77
|
+
end
|
|
14
78
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# @return [Papercraft::Component] Papercraft component
|
|
38
|
-
def self.html(o = nil, mime_type: nil, &template)
|
|
39
|
-
return o if o.is_a?(Papercraft::Component)
|
|
40
|
-
template ||= o
|
|
41
|
-
Papercraft::Component.new(mode: :html, mime_type: mime_type, &template)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Creates a new papercraft component in XML mode. `Papercraft.xml` can take
|
|
45
|
-
# either a proc argument or a block. In both cases, the proc is converted to a
|
|
46
|
-
# `Papercraft::Component`.
|
|
47
|
-
#
|
|
48
|
-
# Papercraft.xml(proc { item 'foo' }).render #=> "<item>foo</item>"
|
|
49
|
-
# Papercraft.xml { item 'foo' }.render #=> "<item>foo</item>"
|
|
50
|
-
#
|
|
51
|
-
# @param template [Proc] template block
|
|
52
|
-
# @return [Papercraft::Component] Papercraft component
|
|
53
|
-
def self.xml(o = nil, mime_type: nil, &template)
|
|
54
|
-
return o if o.is_a?(Papercraft::Component)
|
|
55
|
-
template ||= o
|
|
56
|
-
Papercraft::Component.new(mode: :xml, mime_type: mime_type, &template)
|
|
57
|
-
end
|
|
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
|
|
58
101
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
# @param template [Proc] template block
|
|
67
|
-
# @return [Papercraft::Component] Papercraft component
|
|
68
|
-
def self.json(o = nil, mime_type: nil, &template)
|
|
69
|
-
return o if o.is_a?(Papercraft::Component)
|
|
70
|
-
template ||= o
|
|
71
|
-
Papercraft::Component.new(mode: :json, mime_type: mime_type, &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
|
|
72
109
|
end
|
|
73
110
|
end
|
data/papercraft.png
ADDED
|
Binary file
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: papercraft
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: '0.
|
|
4
|
+
version: '0.20'
|
|
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-
|
|
11
|
+
date: 2022-02-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: escape_utils
|
|
@@ -128,18 +128,22 @@ executables: []
|
|
|
128
128
|
extensions: []
|
|
129
129
|
extra_rdoc_files:
|
|
130
130
|
- README.md
|
|
131
|
+
- papercraft.png
|
|
131
132
|
files:
|
|
132
133
|
- CHANGELOG.md
|
|
133
134
|
- README.md
|
|
134
135
|
- lib/papercraft.rb
|
|
135
136
|
- lib/papercraft/compiler.rb
|
|
136
|
-
- lib/papercraft/component.rb
|
|
137
137
|
- lib/papercraft/encoding.rb
|
|
138
138
|
- lib/papercraft/extension_proxy.rb
|
|
139
139
|
- lib/papercraft/html.rb
|
|
140
140
|
- lib/papercraft/json.rb
|
|
141
141
|
- lib/papercraft/renderer.rb
|
|
142
|
+
- lib/papercraft/tags.rb
|
|
143
|
+
- lib/papercraft/template.rb
|
|
142
144
|
- lib/papercraft/version.rb
|
|
145
|
+
- lib/papercraft/xml.rb
|
|
146
|
+
- papercraft.png
|
|
143
147
|
homepage: http://github.com/digital-fabric/papercraft
|
|
144
148
|
licenses:
|
|
145
149
|
- MIT
|