papercraft 0.16 → 0.20
Sign up to get free protection for your applications and to get access to all the features.
- 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
|