papercraft 0.9 → 0.12
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 +18 -0
- data/README.md +148 -34
- data/lib/papercraft/extension_proxy.rb +30 -0
- data/lib/papercraft/html.rb +57 -4
- data/lib/papercraft/renderer.rb +174 -8
- data/lib/papercraft/version.rb +1 -1
- data/lib/papercraft.rb +14 -0
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be072094c35a6f89327b9c88b90264ad3db61cb43311d0e3baca865f403fdbc5
|
4
|
+
data.tar.gz: 7e565933dd83c48c05bac8c648cbe9b0007bc43f8fc5bd862af46dcd00dfb328
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b8da36fb3c7298e8121b9c612cb81752609e1e3d1f01a94e4bf939864b29de718884989203077c5c1392926a54205c921adbcaa1bf3aa75d84694b7b4db4dcc
|
7
|
+
data.tar.gz: 778163311cb86ee425c4617ff54b10e8853d9b92404ef29abac848a4252818386b2e23058c355678c286a5bae3db9d4a7bfb182acb0a4aacf00a042431cc196a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
## 0.12 2022-01-06
|
2
|
+
|
3
|
+
- Improve documentation
|
4
|
+
- Add `Renderer#tag` method
|
5
|
+
- Add `HTML#style`, `HTML#script` methods
|
6
|
+
|
7
|
+
## 0.11 2022-01-04
|
8
|
+
|
9
|
+
- Add deferred evaluation
|
10
|
+
|
11
|
+
## 0.10.1 2021-12-25
|
12
|
+
|
13
|
+
- Fix tag rendering with empty text in Ruby 3.0
|
14
|
+
|
15
|
+
## 0.10 2021-12-25
|
16
|
+
|
17
|
+
- Add support for extensions
|
18
|
+
|
1
19
|
## 0.9 2021-12-23
|
2
20
|
|
3
21
|
- Add support for emitting Markdown
|
data/README.md
CHANGED
@@ -1,8 +1,24 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
<h1 align="center">
|
2
|
+
Papercraft
|
3
|
+
</h1>
|
4
|
+
|
5
|
+
<h4 align="center">Composable HTML templating for Ruby</h4>
|
6
|
+
|
7
|
+
<p align="center">
|
8
|
+
<a href="http://rubygems.org/gems/papercraft">
|
9
|
+
<img src="https://badge.fury.io/rb/papercraft.svg" alt="Ruby gem">
|
10
|
+
</a>
|
11
|
+
<a href="https://github.com/digital-fabric/papercraft/actions?query=workflow%3ATests">
|
12
|
+
<img src="https://github.com/digital-fabric/papercraft/workflows/Tests/badge.svg" alt="Tests">
|
13
|
+
</a>
|
14
|
+
<a href="https://github.com/digital-fabric/papercraft/blob/master/LICENSE">
|
15
|
+
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
|
16
|
+
</a>
|
17
|
+
</p>
|
18
|
+
|
19
|
+
<p align="center">
|
20
|
+
<a href="https://www.rubydoc.info/gems/papercraft">API reference</a>
|
21
|
+
</p>
|
6
22
|
|
7
23
|
## What is Papercraft?
|
8
24
|
|
@@ -32,9 +48,7 @@ features:
|
|
32
48
|
- Explicit parameter passing to nested components
|
33
49
|
- Higher order components
|
34
50
|
- Built-in support for rendering [Markdown](#emitting-markdown)
|
35
|
-
|
36
|
-
With Papercraft you can structure your templates as nested HTML components, in a
|
37
|
-
somewhat similar fashion to React.
|
51
|
+
- Support for namespaced extensions
|
38
52
|
|
39
53
|
## Installing Papercraft
|
40
54
|
|
@@ -52,43 +66,25 @@ $ gem install papercraft
|
|
52
66
|
|
53
67
|
## Getting started
|
54
68
|
|
55
|
-
To
|
69
|
+
To create a template use the global method `Kernel#H`:
|
56
70
|
|
57
71
|
```ruby
|
58
72
|
require 'papercraft'
|
59
|
-
```
|
60
|
-
|
61
|
-
To create a template use `Papercraft::Component.new` or the global method
|
62
|
-
`Kernel#H`:
|
63
73
|
|
64
|
-
```ruby
|
65
|
-
# can also use Papercraft.new
|
66
74
|
html = H {
|
67
|
-
div { p '
|
75
|
+
div(id: 'greeter') { p 'Hello!' }
|
68
76
|
}
|
69
77
|
```
|
70
78
|
|
71
|
-
|
72
|
-
|
73
|
-
To render a Papercraft template use the `#render` method:
|
74
|
-
|
75
|
-
```ruby
|
76
|
-
H { span 'best span' }.render #=> "<span>best span</span>"
|
77
|
-
```
|
78
|
-
|
79
|
-
The render method accepts an arbitrary context variable:
|
79
|
+
Rendering a template is done using `#render`:
|
80
80
|
|
81
81
|
```ruby
|
82
|
-
html
|
83
|
-
h1 context[:title]
|
84
|
-
}
|
85
|
-
|
86
|
-
html.render(title: 'My title') #=> "<h1>My title</h1>"
|
82
|
+
html.render #=> "<div id="greeter"><p>Hello!</p></div>"
|
87
83
|
```
|
88
84
|
|
89
85
|
## All about tags
|
90
86
|
|
91
|
-
Tags are added using unqualified method calls, and
|
87
|
+
Tags are added using unqualified method calls, and can be nested using blocks:
|
92
88
|
|
93
89
|
```ruby
|
94
90
|
H {
|
@@ -370,13 +366,131 @@ The default Kramdown options are:
|
|
370
366
|
```
|
371
367
|
|
372
368
|
The deafult options can be configured by accessing
|
373
|
-
`Papercraft::HTML.kramdown_options
|
369
|
+
`Papercraft::HTML.kramdown_options`, e.g.:
|
374
370
|
|
375
371
|
```ruby
|
376
372
|
Papercraft::HTML.kramdown_options[:auto_ids] = false
|
377
373
|
```
|
378
374
|
|
379
|
-
##
|
375
|
+
## Deferred evaluation
|
376
|
+
|
377
|
+
Deferred evaluation allows deferring the rendering of parts of a template until
|
378
|
+
the last moment, thus allowing an inner component to manipulate the state of the
|
379
|
+
outer component. To in order to defer a part of a template, use `#defer`, and
|
380
|
+
include any markup in the provided block. This technique, in in conjunction with
|
381
|
+
holding state in instance variables, is an alternative to passing parameters,
|
382
|
+
which can be limiting in some situations.
|
383
|
+
|
384
|
+
A few use cases for deferred evaulation come to mind:
|
385
|
+
|
386
|
+
- Setting the page title.
|
387
|
+
- Adding a flash message to a page.
|
388
|
+
- Using components that dynamically add static dependencies (JS and CSS) to the
|
389
|
+
page.
|
390
|
+
|
391
|
+
The last use case is particularly interesting. Imagine a `DependencyMananger`
|
392
|
+
class that can collect JS and CSS dependencies from the different components
|
393
|
+
integrated into the page, and adds them to the page's `<head>` element:
|
394
|
+
|
395
|
+
```ruby
|
396
|
+
default_layout = H { |**args|
|
397
|
+
@dependencies = DependencyMananger.new
|
398
|
+
head {
|
399
|
+
defer { emit @dependencies.head_markup }
|
400
|
+
}
|
401
|
+
body { emit_yield **args }
|
402
|
+
}
|
403
|
+
|
404
|
+
button = proc { |text, onclick|
|
405
|
+
@dependencies.js '/static/js/button.js'
|
406
|
+
@dependencies.css '/static/css/button.css'
|
407
|
+
|
408
|
+
button text, onclick: onclick
|
409
|
+
}
|
410
|
+
|
411
|
+
heading = proc { |text|
|
412
|
+
@dependencies.js '/static/js/heading.js'
|
413
|
+
@dependencies.css '/static/css/heading.css'
|
414
|
+
|
415
|
+
h1 text
|
416
|
+
}
|
417
|
+
|
418
|
+
page = default_layout.apply {
|
419
|
+
emit heading, "What's your favorite cheese?"
|
420
|
+
|
421
|
+
emit button, 'Beaufort', 'eat_beaufort()'
|
422
|
+
emit button, 'Mont d''or', 'eat_montdor()'
|
423
|
+
emit button, 'Époisses', 'eat_epoisses()'
|
424
|
+
}
|
425
|
+
```
|
426
|
+
|
427
|
+
## Papercraft extensions
|
428
|
+
|
429
|
+
Papercraft extensions are modules that contain one or more methods that can be
|
430
|
+
used to render complex HTML components. Extension modules can be used by
|
431
|
+
installing them as a namespaced extension using `Papercraft::extension`.
|
432
|
+
Extensions are particularly useful when you work with CSS frameworks such as
|
433
|
+
[Bootstrap](https://getbootstrap.com/), [Tailwind](https://tailwindui.com/) or
|
434
|
+
[Primer](https://primer.style/).
|
435
|
+
|
436
|
+
For example, to create a Bootstrap card component, the following HTML markup is
|
437
|
+
needed (example taken from the [Bootstrap
|
438
|
+
docs](https://getbootstrap.com/docs/5.1/components/card/#titles-text-and-links)):
|
439
|
+
|
440
|
+
```html
|
441
|
+
<div class="card" style="width: 18rem;">
|
442
|
+
<div class="card-body">
|
443
|
+
<h5 class="card-title">Card title</h5>
|
444
|
+
<h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
|
445
|
+
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
|
446
|
+
<a href="#" class="card-link">Card link</a>
|
447
|
+
<a href="#" class="card-link">Another link</a>
|
448
|
+
</div>
|
449
|
+
</div>
|
450
|
+
```
|
451
|
+
|
452
|
+
With Papercraft, we could create a `Bootstrap` extension with a `#card` method
|
453
|
+
and other associated methods:
|
454
|
+
|
455
|
+
```ruby
|
456
|
+
module BootstrapComponents
|
457
|
+
...
|
458
|
+
|
459
|
+
def card(**props)
|
460
|
+
div(class: 'card', **props) {
|
461
|
+
div(class: 'card-body') {
|
462
|
+
emit_yield
|
463
|
+
}
|
464
|
+
}
|
465
|
+
end
|
466
|
+
|
467
|
+
def card_title(title)
|
468
|
+
h5 title, class: 'card-title'
|
469
|
+
end
|
470
|
+
|
471
|
+
...
|
472
|
+
end
|
473
|
+
|
474
|
+
Papercraft.extension(bootstrap: BootstrapComponents)
|
475
|
+
```
|
476
|
+
|
477
|
+
The call to `Papercraft::extension` lets us access the different methods of
|
478
|
+
`BootstrapComponents` by calling `#bootstrap` inside a template. With this,
|
479
|
+
we'll be able to express the above markup as follows:
|
480
|
+
|
481
|
+
```ruby
|
482
|
+
H {
|
483
|
+
bootstrap.card(style: 'width: 18rem') {
|
484
|
+
bootstrap.card_title 'Card title'
|
485
|
+
bootstrap.card_subtitle 'Card subtitle'
|
486
|
+
bootstrap.card_text 'Some quick example text to build on the card title and make up the bulk of the card''s content.'
|
487
|
+
bootstrap.card_link '#', 'Card link'
|
488
|
+
bootstrap.card_link '#', 'Another link'
|
489
|
+
}
|
490
|
+
}
|
491
|
+
```
|
492
|
+
|
493
|
+
## API Reference
|
380
494
|
|
381
|
-
The
|
495
|
+
The API reference for this library can be found
|
382
496
|
[here](https://www.rubydoc.info/gems/papercraft).
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Papercraft
|
4
|
+
|
5
|
+
# An ExtensionProxy proxies method calls to a renderer. Extension proxies are
|
6
|
+
# used to provide an namespaced interface to Papercraft extensions. When an
|
7
|
+
# extension is installed using `Papercraft.extension`, a corresponding method
|
8
|
+
# is defined on the `Papercraft::Renderer` class that creates an extension
|
9
|
+
# proxy that gives access to the different extension methods.
|
10
|
+
class ExtensionProxy
|
11
|
+
|
12
|
+
# Initializes a new ExtensionProxy.
|
13
|
+
# @param renderer [Papercraft::Renderer] renderer to proxy to
|
14
|
+
# @param mod [Module] extension module
|
15
|
+
# @return [void]
|
16
|
+
def initialize(renderer, mod)
|
17
|
+
@renderer = renderer
|
18
|
+
extend(mod)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Proxies missing methods to the renderer
|
22
|
+
# @param sym [Symbol] method name
|
23
|
+
# @param *args [Array] arguments
|
24
|
+
# @param &block [Proc] block
|
25
|
+
# @return void
|
26
|
+
def method_missing(sym, *args, &block)
|
27
|
+
@renderer.send(sym, *args, &block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/papercraft/html.rb
CHANGED
@@ -44,15 +44,53 @@ module Papercraft
|
|
44
44
|
link(**attributes)
|
45
45
|
end
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
# Emits an inline CSS style element.
|
48
|
+
#
|
49
|
+
# @param css [String] CSS code
|
50
|
+
# @param **props [Hash] optional element attributes
|
51
|
+
# @return [void]
|
52
|
+
def style(css, **props, &block)
|
53
|
+
@buffer << '<style'
|
54
|
+
emit_props(props) unless props.empty?
|
55
|
+
|
56
|
+
@buffer << '>' << css << '</style>'
|
49
57
|
end
|
58
|
+
|
59
|
+
# Emits an inline JS script element.
|
60
|
+
#
|
61
|
+
# @param js [String, nil] Javascript code
|
62
|
+
# @param **props [Hash] optional element attributes
|
63
|
+
# @return [void]
|
64
|
+
def script(js = nil, **props, &block)
|
65
|
+
@buffer << '<script'
|
66
|
+
emit_props(props) unless props.empty?
|
50
67
|
|
51
|
-
|
52
|
-
|
68
|
+
if js
|
69
|
+
@buffer << '>' << js << '</script>'
|
70
|
+
else
|
71
|
+
@buffer << '></script>'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Converts and emits the given markdown. Papercraft uses
|
76
|
+
# [Kramdown](https://github.com/gettalong/kramdown/) to do the Markdown to
|
77
|
+
# HTML conversion. Optional Kramdown settings can be provided in order to
|
78
|
+
# control the conversion. Those are merged with the default Kramdown
|
79
|
+
# settings, which can be controlled using
|
80
|
+
# `Papercraft::HTML.kramdown_options`.
|
81
|
+
#
|
82
|
+
# @param markdown [String] Markdown content
|
83
|
+
# @param **opts [Hash] Kramdown options
|
84
|
+
# @return [void]
|
85
|
+
def emit_markdown(markdown, **opts)
|
86
|
+
emit Kramdown::Document.new(markdown, **kramdown_options(opts)).to_html
|
53
87
|
end
|
54
88
|
|
55
89
|
class << self
|
90
|
+
# Returns the default Kramdown options used for converting Markdown to
|
91
|
+
# HTML.
|
92
|
+
#
|
93
|
+
# @return [Hash] Default Kramdown options
|
56
94
|
def kramdown_options
|
57
95
|
@kramdown_options ||= {
|
58
96
|
entity_output: :numeric,
|
@@ -62,9 +100,24 @@ module Papercraft
|
|
62
100
|
}
|
63
101
|
end
|
64
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
|
65
108
|
def kramdown_options=(opts)
|
66
109
|
@kramdown_options = opts
|
67
110
|
end
|
68
111
|
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# Returns the default Kramdown options, merged with the given overrides.
|
116
|
+
#
|
117
|
+
# @param opts [Hash] Kramdown option overrides
|
118
|
+
# @return [Hash] Merged Kramdown options
|
119
|
+
def kramdown_options(opts)
|
120
|
+
HTML.kramdown_options.merge(**opts)
|
121
|
+
end
|
69
122
|
end
|
70
123
|
end
|
data/lib/papercraft/renderer.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative './html'
|
4
|
+
require_relative './extension_proxy'
|
4
5
|
|
5
6
|
module Papercraft
|
6
7
|
|
@@ -31,15 +32,60 @@ module Papercraft
|
|
31
32
|
if param_count > args.size
|
32
33
|
raise Papercraft::Error, "Missing template parameters"
|
33
34
|
end
|
34
|
-
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# call_seq:
|
38
|
+
# Papercraft::Renderer.extension(name => mod, ...)
|
39
|
+
# Papercraft.extension(name => mod, ...)
|
40
|
+
#
|
41
|
+
# Installs the given extensions, passed in the form of a Ruby hash mapping
|
42
|
+
# methods to extension modules. The methods will be available to all
|
43
|
+
# Papercraft components. Extension methods are executed in the context of
|
44
|
+
# the the renderer instance, so they can look just like normal proc
|
45
|
+
# components. In cases where method names in the module clash with HTML
|
46
|
+
# tag names, you can use the `#tag` method to emit the relevant tag.
|
47
|
+
#
|
48
|
+
# module ComponentLibrary
|
49
|
+
# def card(title, content)
|
50
|
+
# div(class: 'card') {
|
51
|
+
# h3 title
|
52
|
+
# div(class: 'card-content') { emit_markdown content }
|
53
|
+
# }
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# Papercraft.extension(components: ComponentLibrary)
|
58
|
+
# H { components.card('Foo', '**Bar**') }
|
59
|
+
#
|
60
|
+
# @param map [Hash] hash mapping methods to extension modules
|
61
|
+
# @return [void]
|
62
|
+
def extension(map)
|
63
|
+
map.each do |sym, mod|
|
64
|
+
define_extension_method(sym, mod)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Defines a method returning an extension proxy for the given module
|
71
|
+
# @param sym [Symbol] method name
|
72
|
+
# @param mod [Module] extension module
|
73
|
+
# @return [void]
|
74
|
+
def define_extension_method(sym, mod)
|
75
|
+
define_method(sym) do
|
76
|
+
(@extension_proxies ||= {})[mod] ||= ExtensionProxy.new(self, mod)
|
77
|
+
end
|
78
|
+
end
|
35
79
|
end
|
36
80
|
|
81
|
+
INITIAL_BUFFER_CAPACITY = 8192
|
82
|
+
|
37
83
|
# Initializes the renderer and evaulates the given template in the
|
38
84
|
# renderer's scope.
|
39
85
|
#
|
40
86
|
# @param &template [Proc] template block
|
41
87
|
def initialize(&template)
|
42
|
-
@buffer =
|
88
|
+
@buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
|
43
89
|
instance_eval(&template)
|
44
90
|
end
|
45
91
|
|
@@ -47,15 +93,36 @@ module Papercraft
|
|
47
93
|
#
|
48
94
|
# @return [String]
|
49
95
|
def to_s
|
96
|
+
if @parts
|
97
|
+
last = @buffer
|
98
|
+
@buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
|
99
|
+
parts = @parts
|
100
|
+
@parts = nil
|
101
|
+
parts.each do |p|
|
102
|
+
if Proc === p
|
103
|
+
render_deferred_proc(&p)
|
104
|
+
else
|
105
|
+
@buffer << p
|
106
|
+
end
|
107
|
+
end
|
108
|
+
@buffer << last unless last.empty?
|
109
|
+
end
|
50
110
|
@buffer
|
51
111
|
end
|
52
112
|
|
113
|
+
# The tag method template below is optimized for performance. Do not touch!
|
114
|
+
|
53
115
|
S_TAG_METHOD_LINE = __LINE__ + 1
|
54
116
|
S_TAG_METHOD = <<~EOF
|
55
|
-
S_TAG_%<TAG>s_PRE =
|
56
|
-
S_TAG_%<TAG>s_CLOSE =
|
117
|
+
S_TAG_%<TAG>s_PRE = %<tag_pre>s
|
118
|
+
S_TAG_%<TAG>s_CLOSE = %<tag_close>s
|
57
119
|
|
58
120
|
def %<tag>s(text = nil, **props, &block)
|
121
|
+
if text.is_a?(Hash) && props.empty?
|
122
|
+
props = text
|
123
|
+
text = nil
|
124
|
+
end
|
125
|
+
|
59
126
|
@buffer << S_TAG_%<TAG>s_PRE
|
60
127
|
emit_props(props) unless props.empty?
|
61
128
|
|
@@ -75,6 +142,48 @@ module Papercraft
|
|
75
142
|
end
|
76
143
|
EOF
|
77
144
|
|
145
|
+
# Emits an HTML tag with the given content, properties and optional block.
|
146
|
+
# This method is an alternative to emitting HTML tags using dynamically
|
147
|
+
# created methods. This is particularly useful when using extensions that
|
148
|
+
# have method names that clash with HTML tags, such as `button` or `a`, or
|
149
|
+
# when you need to override the behaviour of a particular HTML tag.
|
150
|
+
#
|
151
|
+
# The following two method calls have the same effect:
|
152
|
+
#
|
153
|
+
# button 'text', id: 'button1'
|
154
|
+
# tag :button, 'text', id: 'button1'
|
155
|
+
#
|
156
|
+
# @param sym [Symbol, String] HTML tag
|
157
|
+
# @param text [String, nil] tag content
|
158
|
+
# @param **props [Hash] tag attributes
|
159
|
+
# @param &block [Proc] optional inner HTML
|
160
|
+
# @return [void]
|
161
|
+
def tag(sym, text = nil, **props, &block)
|
162
|
+
if text.is_a?(Hash) && props.empty?
|
163
|
+
props = text
|
164
|
+
text = nil
|
165
|
+
end
|
166
|
+
|
167
|
+
tag = sym.to_s.tr('_', '-')
|
168
|
+
|
169
|
+
@buffer << S_LT << tag
|
170
|
+
emit_props(props) unless props.empty?
|
171
|
+
|
172
|
+
if block
|
173
|
+
@buffer << S_GT
|
174
|
+
instance_eval(&block)
|
175
|
+
@buffer << S_LT_SLASH << tag << S_GT
|
176
|
+
elsif Proc === text
|
177
|
+
@buffer << S_GT
|
178
|
+
emit(text)
|
179
|
+
@buffer << S_LT_SLASH << tag << S_GT
|
180
|
+
elsif text
|
181
|
+
@buffer << S_GT << escape_text(text.to_s) << S_LT_SLASH << tag << S_GT
|
182
|
+
else
|
183
|
+
@buffer << S_SLASH_GT
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
78
187
|
# Catches undefined tag method call and handles it by defining the method.
|
79
188
|
#
|
80
189
|
# @param sym [Symbol] HTML tag or component identifier
|
@@ -83,11 +192,13 @@ module Papercraft
|
|
83
192
|
# @param &block [Proc] block passed to method
|
84
193
|
# @return [void]
|
85
194
|
def method_missing(sym, *args, **opts, &block)
|
86
|
-
value = @local && @local[sym]
|
87
|
-
return value if value
|
88
|
-
|
89
195
|
tag = sym.to_s
|
90
|
-
code = S_TAG_METHOD % {
|
196
|
+
code = S_TAG_METHOD % {
|
197
|
+
tag: tag,
|
198
|
+
TAG: tag.upcase,
|
199
|
+
tag_pre: "<#{tag.tr('_', '-')}".inspect,
|
200
|
+
tag_close: "</#{tag.tr('_', '-')}>".inspect
|
201
|
+
}
|
91
202
|
self.class.class_eval(code, __FILE__, S_TAG_METHOD_LINE)
|
92
203
|
send(sym, *args, **opts, &block)
|
93
204
|
end
|
@@ -135,6 +246,42 @@ module Papercraft
|
|
135
246
|
|
136
247
|
instance_exec(*a, **b, &@inner_block)
|
137
248
|
end
|
249
|
+
|
250
|
+
# Defers the given block to be evaluated later. Deferred evaluation allows
|
251
|
+
# Papercraft components to inject state into sibling components, regardless
|
252
|
+
# of the component's order in the container component. For example, a nested
|
253
|
+
# component may set an instance variable used by another component. This is
|
254
|
+
# an elegant solution to the problem of setting the HTML page's title, or
|
255
|
+
# adding elements to the `<head>` section. Here's how a title can be
|
256
|
+
# controlled from a nested component:
|
257
|
+
#
|
258
|
+
# layout = H {
|
259
|
+
# html {
|
260
|
+
# head {
|
261
|
+
# defer { title @title }
|
262
|
+
# }
|
263
|
+
# body {
|
264
|
+
# emit_yield
|
265
|
+
# }
|
266
|
+
# }
|
267
|
+
# }
|
268
|
+
#
|
269
|
+
# html.render {
|
270
|
+
# @title = 'My super page'
|
271
|
+
# h1 'content'
|
272
|
+
# }
|
273
|
+
#
|
274
|
+
# @param &block [Proc] Deferred block to be emitted
|
275
|
+
# @return [void]
|
276
|
+
def defer(&block)
|
277
|
+
if !@parts
|
278
|
+
@parts = [@buffer, block]
|
279
|
+
else
|
280
|
+
@parts << @buffer unless @buffer.empty?
|
281
|
+
@parts << block
|
282
|
+
end
|
283
|
+
@buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
|
284
|
+
end
|
138
285
|
|
139
286
|
S_LT = '<'
|
140
287
|
S_GT = '>'
|
@@ -192,6 +339,23 @@ module Papercraft
|
|
192
339
|
end
|
193
340
|
}
|
194
341
|
end
|
342
|
+
|
343
|
+
# Renders a deferred proc by evaluating it, then adding the rendered result
|
344
|
+
# to the buffer.
|
345
|
+
#
|
346
|
+
# @param &block [Proc] deferred proc
|
347
|
+
# @return [void]
|
348
|
+
def render_deferred_proc(&block)
|
349
|
+
old_buffer = @buffer
|
350
|
+
|
351
|
+
@buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
|
352
|
+
@parts = nil
|
353
|
+
|
354
|
+
instance_eval(&block)
|
355
|
+
|
356
|
+
old_buffer << to_s
|
357
|
+
@buffer = old_buffer
|
358
|
+
end
|
195
359
|
end
|
196
360
|
|
197
361
|
# Implements an HTML renderer
|
@@ -200,6 +364,7 @@ module Papercraft
|
|
200
364
|
|
201
365
|
private
|
202
366
|
|
367
|
+
# Escapes the given text using HTML entities.
|
203
368
|
def escape_text(text)
|
204
369
|
EscapeUtils.escape_html(text.to_s)
|
205
370
|
end
|
@@ -209,6 +374,7 @@ module Papercraft
|
|
209
374
|
class XMLRenderer < Renderer
|
210
375
|
private
|
211
376
|
|
377
|
+
# Escapes the given text using XML entities.
|
212
378
|
def escape_text(text)
|
213
379
|
EscapeUtils.escape_xml(text.to_s)
|
214
380
|
end
|
data/lib/papercraft/version.rb
CHANGED
data/lib/papercraft.rb
CHANGED
@@ -11,6 +11,20 @@ require_relative 'papercraft/encoding'
|
|
11
11
|
module Papercraft
|
12
12
|
# Exception class used to signal templating-related errors
|
13
13
|
class Error < RuntimeError; end
|
14
|
+
|
15
|
+
# Installs one or more extensions. Extensions enhance templating capabilities
|
16
|
+
# by adding namespaced methods to emplates. An extension is implemented as a
|
17
|
+
# Ruby module containing one or more methods. Each method in the extension
|
18
|
+
# module can be used to render a specific HTML element or a set of elements.
|
19
|
+
#
|
20
|
+
# This is a convenience method. For more information on using Papercraft
|
21
|
+
# extensions, see `Papercraft::Renderer::extension`
|
22
|
+
#
|
23
|
+
# @param map [Hash] hash mapping methods to extension modules
|
24
|
+
# @return [void]
|
25
|
+
def self.extension(map)
|
26
|
+
Renderer.extension(map)
|
27
|
+
end
|
14
28
|
end
|
15
29
|
|
16
30
|
# Kernel extensions
|
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.12'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: escape_utils
|
@@ -122,7 +122,7 @@ dependencies:
|
|
122
122
|
- - '='
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: 2.0.9
|
125
|
-
description:
|
125
|
+
description:
|
126
126
|
email: sharon@noteflakes.com
|
127
127
|
executables: []
|
128
128
|
extensions: []
|
@@ -135,6 +135,7 @@ files:
|
|
135
135
|
- lib/papercraft/compiler.rb
|
136
136
|
- lib/papercraft/component.rb
|
137
137
|
- lib/papercraft/encoding.rb
|
138
|
+
- lib/papercraft/extension_proxy.rb
|
138
139
|
- lib/papercraft/html.rb
|
139
140
|
- lib/papercraft/renderer.rb
|
140
141
|
- lib/papercraft/version.rb
|
@@ -146,7 +147,7 @@ metadata:
|
|
146
147
|
documentation_uri: https://www.rubydoc.info/gems/papercraft
|
147
148
|
homepage_uri: https://github.com/digital-fabric/papercraft
|
148
149
|
changelog_uri: https://github.com/digital-fabric/papercraft/blob/master/CHANGELOG.md
|
149
|
-
post_install_message:
|
150
|
+
post_install_message:
|
150
151
|
rdoc_options:
|
151
152
|
- "--title"
|
152
153
|
- Papercraft
|
@@ -165,8 +166,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
166
|
- !ruby/object:Gem::Version
|
166
167
|
version: '0'
|
167
168
|
requirements: []
|
168
|
-
rubygems_version: 3.
|
169
|
-
signing_key:
|
169
|
+
rubygems_version: 3.3.3
|
170
|
+
signing_key:
|
170
171
|
specification_version: 4
|
171
172
|
summary: 'Papercraft: component-based HTML templating for Ruby'
|
172
173
|
test_files: []
|