papercraft 0.8.3 → 0.11
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 +172 -111
- data/lib/papercraft/extension_proxy.rb +30 -0
- data/lib/papercraft/html.rb +27 -3
- data/lib/papercraft/renderer.rb +101 -5
- data/lib/papercraft/version.rb +1 -1
- data/lib/papercraft.rb +8 -0
- metadata +50 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3452d115d85742018d1497b369ea27970f0c144e3dcb1772f4d81e33315dce51
|
4
|
+
data.tar.gz: e7217a9d9bcfd7af0dc92482650bc03392b0406cf4f3d6141c253a2918638545
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f76b9e73aa48f91af932af3308d3dd43239c3d5ca39e305cb1535417b5997483aa884f0e34ed128800b07a3135ebc1d18ea81d0df64e6170726a66c86969288
|
7
|
+
data.tar.gz: dbe744ca25996c3d263ff609ca26f42076784fdbceabd2dd1604dd1016e2d840830230f4205a673b9e5ba14eb8e480627612971b71ca6631e0a6e473d59ccb73
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
## 0.11 2022-01-04
|
2
|
+
|
3
|
+
- Add deferred evaluation
|
4
|
+
|
5
|
+
## 0.10.1 2021-12-25
|
6
|
+
|
7
|
+
- Fix tag rendering with empty text in Ruby 3.0
|
8
|
+
|
9
|
+
## 0.10 2021-12-25
|
10
|
+
|
11
|
+
- Add support for extensions
|
12
|
+
|
13
|
+
## 0.9 2021-12-23
|
14
|
+
|
15
|
+
- Add support for emitting Markdown
|
16
|
+
- Add support for passing proc as argument to `#H` and `#X`
|
17
|
+
- Deprecate `Encoding` module
|
18
|
+
|
1
19
|
## 0.8.1 2021-12-22
|
2
20
|
|
3
21
|
- Fix gemspec
|
data/README.md
CHANGED
@@ -1,30 +1,56 @@
|
|
1
|
-
|
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>
|
2
22
|
|
3
|
-
|
4
|
-
[TUTORIAL](#getting-started) |
|
5
|
-
[EXAMPLES](examples) |
|
6
|
-
[REFERENCE](#api-reference)
|
23
|
+
# Papercraft - Composable HTML templating for Ruby
|
7
24
|
|
8
25
|
## What is Papercraft?
|
9
26
|
|
27
|
+
```ruby
|
28
|
+
require 'papercraft'
|
29
|
+
|
30
|
+
page = H { |*args|
|
31
|
+
html {
|
32
|
+
head { }
|
33
|
+
body { emit_yield *args }
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
hello = H.apply { |name| h1 "Hello, #{name}!" }
|
38
|
+
hello.render('world')
|
39
|
+
#=> "<html><head/><body><h1>Hello, world!</h1></body></html>"
|
40
|
+
```
|
41
|
+
|
10
42
|
Papercraft is an HTML templating engine for Ruby that offers the following
|
11
43
|
features:
|
12
44
|
|
13
|
-
- HTML templating using plain Ruby syntax
|
45
|
+
- HTML and XML templating using plain Ruby syntax
|
14
46
|
- Minimal boilerplate
|
15
47
|
- Mix logic and tags freely
|
16
|
-
-
|
17
|
-
- Automatic HTML escaping
|
48
|
+
- Automatic HTML and XML escaping
|
18
49
|
- Composable components
|
50
|
+
- Explicit parameter passing to nested components
|
19
51
|
- Higher order components
|
20
|
-
- Built-in support for rendering Markdown
|
21
|
-
|
22
|
-
> **Note** Papercraft is a new library and as such may be missing features and
|
23
|
-
> contain bugs. Also, its API may change unexpectedly. Your issue reports and
|
24
|
-
> code contributions are most welcome!
|
25
|
-
|
26
|
-
With Papercraft you can structure your templates as nested HTML components, in a
|
27
|
-
somewhat similar fashion to React.
|
52
|
+
- Built-in support for rendering [Markdown](#emitting-markdown)
|
53
|
+
- Support for namespaced extensions
|
28
54
|
|
29
55
|
## Installing Papercraft
|
30
56
|
|
@@ -42,42 +68,25 @@ $ gem install papercraft
|
|
42
68
|
|
43
69
|
## Getting started
|
44
70
|
|
45
|
-
To
|
71
|
+
To create a template use the global method `Kernel#H`:
|
46
72
|
|
47
73
|
```ruby
|
48
74
|
require 'papercraft'
|
49
|
-
```
|
50
|
-
|
51
|
-
To create a template use `Papercraft.new` or the global method `Kernel#H`:
|
52
75
|
|
53
|
-
```ruby
|
54
|
-
# can also use Papercraft.new
|
55
76
|
html = H {
|
56
|
-
div { p '
|
77
|
+
div(id: 'greeter') { p 'Hello!' }
|
57
78
|
}
|
58
79
|
```
|
59
80
|
|
60
|
-
|
61
|
-
|
62
|
-
To render a Papercraft template use the `#render` method:
|
81
|
+
Rendering a template is done using `#render`:
|
63
82
|
|
64
83
|
```ruby
|
65
|
-
|
66
|
-
```
|
67
|
-
|
68
|
-
The render method accepts an arbitrary context variable:
|
69
|
-
|
70
|
-
```ruby
|
71
|
-
html = H {
|
72
|
-
h1 context[:title]
|
73
|
-
}
|
74
|
-
|
75
|
-
html.render(title: 'My title') #=> "<h1>My title</h1>"
|
84
|
+
html.render #=> "<div id="greeter"><p>Hello!</p></div>"
|
76
85
|
```
|
77
86
|
|
78
87
|
## All about tags
|
79
88
|
|
80
|
-
Tags are added using unqualified method calls, and
|
89
|
+
Tags are added using unqualified method calls, and can be nested using blocks:
|
81
90
|
|
82
91
|
```ruby
|
83
92
|
H {
|
@@ -114,8 +123,9 @@ H { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</
|
|
114
123
|
|
115
124
|
## Template parameters
|
116
125
|
|
117
|
-
|
118
|
-
template on
|
126
|
+
In Papercraft, parameters are always passed explicitly. This means that template
|
127
|
+
parameters are specified as block parameters, and are passed to the template on
|
128
|
+
rendering:
|
119
129
|
|
120
130
|
```ruby
|
121
131
|
greeting = H { |name| h1 "Hello, #{name}!" }
|
@@ -328,110 +338,161 @@ H { str 'hi&lo' }.render #=> "hi&lo"
|
|
328
338
|
|
329
339
|
## Emitting Markdown
|
330
340
|
|
331
|
-
|
341
|
+
Markdown is rendered using the
|
342
|
+
[Kramdown](https://kramdown.gettalong.org/index.html) gem. To emit Markdown, use
|
343
|
+
`#emit_markdown`:
|
332
344
|
|
333
345
|
```ruby
|
334
346
|
template = H { |md| div { emit_markdown md } }
|
335
|
-
template.render("Here's some *Markdown*") #=> "<div>Here's some <em>Markdown</em
|
347
|
+
template.render("Here's some *Markdown*") #=> "<div><p>Here's some <em>Markdown</em><p>\n</div>"
|
336
348
|
```
|
337
349
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
together pieces of HTML. Feel free to explore the API!
|
350
|
+
[Kramdown
|
351
|
+
options](https://kramdown.gettalong.org/options.html#available-options) can be
|
352
|
+
specified by adding them to the `#emit_markdown` call:
|
342
353
|
|
343
|
-
|
354
|
+
```ruby
|
355
|
+
template = H { |md| div { emit_markdown md, auto_ids: false } }
|
356
|
+
template.render("# title") #=> "<div><h1>title</h1></div>"
|
357
|
+
```
|
344
358
|
|
345
|
-
|
346
|
-
takes an item component as an argument. The `List` component can be reused for
|
347
|
-
rendering any kind of unordered list, and with any kind of item component:
|
359
|
+
The default Kramdown options are:
|
348
360
|
|
349
361
|
```ruby
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
li { emit item_component }
|
356
|
-
}
|
357
|
-
}
|
358
|
-
}
|
359
|
-
}
|
362
|
+
{
|
363
|
+
entity_output: :numeric,
|
364
|
+
syntax_highlighter: :rouge,
|
365
|
+
input: 'GFM',
|
366
|
+
hard_wrap: false
|
360
367
|
}
|
361
|
-
|
362
|
-
TodoItem = H {
|
363
|
-
span item.text, class: item.completed ? 'completed' : 'pending'
|
364
|
-
}
|
365
|
-
|
366
|
-
def todo_list(items)
|
367
|
-
H {
|
368
|
-
div { List(items, TodoItem) }
|
369
|
-
}
|
370
|
-
end
|
371
368
|
```
|
372
369
|
|
373
|
-
|
374
|
-
|
375
|
-
#### `Papercraft#initialize(**context, &block)` a.k.a. `Kernel#H`
|
370
|
+
The deafult options can be configured by accessing
|
371
|
+
`Papercraft::HTML.kramdown_options`:
|
376
372
|
|
377
|
-
|
378
|
-
|
373
|
+
```ruby
|
374
|
+
Papercraft::HTML.kramdown_options[:auto_ids] = false
|
375
|
+
```
|
379
376
|
|
380
|
-
|
381
|
-
code, and an optional [local context](#local-context) in the form of a hash.
|
382
|
-
The `Kernel#H` method serves as a shortcut for creating Papercraft instances.
|
377
|
+
## Deferred evaluation
|
383
378
|
|
384
|
-
|
379
|
+
Deferred evaluation allows deferring the rendering of parts of a template until
|
380
|
+
the last moment, thus allowing an inner component to manipulate the state of the
|
381
|
+
outer component. To in order to defer a part of a template, use `#defer`, and
|
382
|
+
include any markup in the provided block. This technique, in in conjunction with
|
383
|
+
holding state in instance variables, is an alternative to passing parameters,
|
384
|
+
which can be limiting in some situations.
|
385
385
|
|
386
|
-
|
386
|
+
A few use cases for deferred evaulation come to mind:
|
387
387
|
|
388
|
-
|
389
|
-
|
388
|
+
- Setting the page title.
|
389
|
+
- Adding a flash message to a page.
|
390
|
+
- Using components that dynamically add static dependencies (JS and CSS) to the
|
391
|
+
page.
|
390
392
|
|
391
|
-
|
393
|
+
The last use case is particularly interesting. Imagine a `DependencyMananger`
|
394
|
+
class that can collect JS and CSS dependencies from the different components
|
395
|
+
integrated into the page, and adds them to the page's `<head>` element:
|
392
396
|
|
393
|
-
|
397
|
+
```ruby
|
398
|
+
default_layout = H { |**args|
|
399
|
+
@dependencies = DependencyMananger.new
|
400
|
+
head {
|
401
|
+
defer { emit @dependencies.head_markup }
|
402
|
+
}
|
403
|
+
body { emit_yield **args }
|
404
|
+
}
|
394
405
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
- `block`: inner HTML block
|
406
|
+
button = proc { |text, onclick|
|
407
|
+
@dependencies.js '/static/js/button.js'
|
408
|
+
@dependencies.css '/static/css/button.css'
|
399
409
|
|
400
|
-
|
401
|
-
|
410
|
+
button text, onclick: onclick
|
411
|
+
}
|
402
412
|
|
403
|
-
|
413
|
+
heading = proc { |text|
|
414
|
+
@dependencies.js '/static/js/heading.js'
|
415
|
+
@dependencies.css '/static/css/heading.css'
|
404
416
|
|
405
|
-
|
417
|
+
h1 text
|
418
|
+
}
|
406
419
|
|
407
|
-
|
408
|
-
|
409
|
-
- `block`: inner HTML block
|
420
|
+
page = default_layout.apply {
|
421
|
+
emit heading, "What's your favorite cheese?"
|
410
422
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
423
|
+
emit button, 'Beaufort', 'eat_beaufort()'
|
424
|
+
emit button, 'Mont d''or', 'eat_montdor()'
|
425
|
+
emit button, 'Époisses', 'eat_epoisses()'
|
426
|
+
}
|
427
|
+
```
|
415
428
|
|
416
|
-
|
429
|
+
## Papercraft extensions
|
430
|
+
|
431
|
+
Papercraft extensions are modules that contain one or more methods that can be
|
432
|
+
used to render complex HTML components. Extension modules can be used by
|
433
|
+
installing them as a namespaced extension using `Papercraft.extension`.
|
434
|
+
Extensions are particularly useful when you work with CSS frameworks such as
|
435
|
+
[Bootstrap](https://getbootstrap.com/), [Tailwind](https://tailwindui.com/) or
|
436
|
+
[Primer](https://primer.style/).
|
437
|
+
|
438
|
+
For example, to create a Bootstrap card component, the following HTML markup is
|
439
|
+
needed (example taken from the [Bootstrap
|
440
|
+
docs](https://getbootstrap.com/docs/5.1/components/card/#titles-text-and-links)):
|
441
|
+
|
442
|
+
```html
|
443
|
+
<div class="card" style="width: 18rem;">
|
444
|
+
<div class="card-body">
|
445
|
+
<h5 class="card-title">Card title</h5>
|
446
|
+
<h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
|
447
|
+
<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>
|
448
|
+
<a href="#" class="card-link">Card link</a>
|
449
|
+
<a href="#" class="card-link">Another link</a>
|
450
|
+
</div>
|
451
|
+
</div>
|
452
|
+
```
|
417
453
|
|
418
|
-
|
454
|
+
With Papercraft, we could create a `Bootstrap` extension with a `#card` method
|
455
|
+
and other associated methods:
|
419
456
|
|
420
|
-
|
457
|
+
```ruby
|
458
|
+
module BootstrapComponents
|
459
|
+
...
|
421
460
|
|
422
|
-
|
461
|
+
def card(**props)
|
462
|
+
div(class: 'card', **props) {
|
463
|
+
div(class: 'card-body') {
|
464
|
+
emit_yield
|
465
|
+
}
|
466
|
+
}
|
467
|
+
end
|
423
468
|
|
424
|
-
|
425
|
-
|
469
|
+
def card_title(title)
|
470
|
+
h5 title, class: 'card-title'
|
471
|
+
end
|
426
472
|
|
427
|
-
|
473
|
+
...
|
474
|
+
end
|
428
475
|
|
429
|
-
|
476
|
+
Papercraft.extension(bootstrap: BootstrapComponents)
|
477
|
+
```
|
430
478
|
|
431
|
-
|
479
|
+
The call to `Papercraft.extension` lets us access the different methods of
|
480
|
+
`BootstrapComponents` by calling `#bootstrap` inside a template. With this,
|
481
|
+
we'll be able to express the above markup as follows:
|
432
482
|
|
433
|
-
|
483
|
+
```ruby
|
484
|
+
H {
|
485
|
+
bootstrap.card(style: 'width: 18rem') {
|
486
|
+
bootstrap.card_title 'Card title'
|
487
|
+
bootstrap.card_subtitle 'Card subtitle'
|
488
|
+
bootstrap.card_text 'Some quick example text to build on the card title and make up the bulk of the card''s content.'
|
489
|
+
bootstrap.card_link '#', 'Card link'
|
490
|
+
bootstrap.card_link '#', 'Another link'
|
491
|
+
}
|
492
|
+
}
|
493
|
+
```
|
434
494
|
|
435
|
-
|
495
|
+
## API Reference
|
436
496
|
|
437
|
-
|
497
|
+
The API reference for this library can be found
|
498
|
+
[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
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'kramdown'
|
4
|
+
require 'rouge'
|
5
|
+
require 'kramdown-parser-gfm'
|
4
6
|
|
5
|
-
module Papercraft
|
6
|
-
|
7
|
+
module Papercraft
|
7
8
|
# HTML Markup extensions
|
8
9
|
module HTML
|
9
10
|
# Emits the p tag (overrides Object#p)
|
@@ -42,5 +43,28 @@ module Papercraft
|
|
42
43
|
end
|
43
44
|
link(**attributes)
|
44
45
|
end
|
46
|
+
|
47
|
+
def emit_markdown(markdown, **opts)
|
48
|
+
emit Kramdown::Document.new(markdown, **kramdown_options(opts)).to_html
|
49
|
+
end
|
50
|
+
|
51
|
+
def kramdown_options(opts)
|
52
|
+
HTML.kramdown_options.merge(**opts)
|
53
|
+
end
|
54
|
+
|
55
|
+
class << self
|
56
|
+
def kramdown_options
|
57
|
+
@kramdown_options ||= {
|
58
|
+
entity_output: :numeric,
|
59
|
+
syntax_highlighter: :rouge,
|
60
|
+
input: 'GFM',
|
61
|
+
hard_wrap: false
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def kramdown_options=(opts)
|
66
|
+
@kramdown_options = opts
|
67
|
+
end
|
68
|
+
end
|
45
69
|
end
|
46
70
|
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,39 @@ 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
|
+
# Installs the given extensions, mapping a method name to the extension
|
38
|
+
# module.
|
39
|
+
# @param map [Hash] hash mapping methods to extension modules
|
40
|
+
# @return [void]
|
41
|
+
def extension(map)
|
42
|
+
map.each do |sym, mod|
|
43
|
+
define_extension_method(sym, mod)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Defines a method returning an extension proxy for the given module
|
50
|
+
# @param sym [Symbol] method name
|
51
|
+
# @param mod [Module] extension module
|
52
|
+
# @return [void]
|
53
|
+
def define_extension_method(sym, mod)
|
54
|
+
define_method(sym) do
|
55
|
+
(@extension_proxies ||= {})[mod] ||= ExtensionProxy.new(self, mod)
|
56
|
+
end
|
57
|
+
end
|
35
58
|
end
|
36
59
|
|
60
|
+
INITIAL_BUFFER_CAPACITY = 8192
|
61
|
+
|
37
62
|
# Initializes the renderer and evaulates the given template in the
|
38
63
|
# renderer's scope.
|
39
64
|
#
|
40
65
|
# @param &template [Proc] template block
|
41
66
|
def initialize(&template)
|
42
|
-
@buffer =
|
67
|
+
@buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
|
43
68
|
instance_eval(&template)
|
44
69
|
end
|
45
70
|
|
@@ -47,6 +72,20 @@ module Papercraft
|
|
47
72
|
#
|
48
73
|
# @return [String]
|
49
74
|
def to_s
|
75
|
+
if @parts
|
76
|
+
last = @buffer
|
77
|
+
@buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
|
78
|
+
parts = @parts
|
79
|
+
@parts = nil
|
80
|
+
parts.each do |p|
|
81
|
+
if Proc === p
|
82
|
+
render_deferred_proc(&p)
|
83
|
+
else
|
84
|
+
@buffer << p
|
85
|
+
end
|
86
|
+
end
|
87
|
+
@buffer << last unless last.empty?
|
88
|
+
end
|
50
89
|
@buffer
|
51
90
|
end
|
52
91
|
|
@@ -56,6 +95,11 @@ module Papercraft
|
|
56
95
|
S_TAG_%<TAG>s_CLOSE = '</%<tag>s>'.tr('_', '-')
|
57
96
|
|
58
97
|
def %<tag>s(text = nil, **props, &block)
|
98
|
+
if text.is_a?(Hash) && props.empty?
|
99
|
+
props = text
|
100
|
+
text = nil
|
101
|
+
end
|
102
|
+
|
59
103
|
@buffer << S_TAG_%<TAG>s_PRE
|
60
104
|
emit_props(props) unless props.empty?
|
61
105
|
|
@@ -83,9 +127,6 @@ module Papercraft
|
|
83
127
|
# @param &block [Proc] block passed to method
|
84
128
|
# @return [void]
|
85
129
|
def method_missing(sym, *args, **opts, &block)
|
86
|
-
value = @local && @local[sym]
|
87
|
-
return value if value
|
88
|
-
|
89
130
|
tag = sym.to_s
|
90
131
|
code = S_TAG_METHOD % { tag: tag, TAG: tag.upcase }
|
91
132
|
self.class.class_eval(code, __FILE__, S_TAG_METHOD_LINE)
|
@@ -135,6 +176,42 @@ module Papercraft
|
|
135
176
|
|
136
177
|
instance_exec(*a, **b, &@inner_block)
|
137
178
|
end
|
179
|
+
|
180
|
+
# Defers the given block to be evaluated later. Deferred evaluation allows
|
181
|
+
# Papercraft components to inject state into sibling components, regardless
|
182
|
+
# of the component's order in the container component. For example, a nested
|
183
|
+
# component may set an instance variable used by another component. This is
|
184
|
+
# an elegant solution to the problem of setting the HTML page's title, or
|
185
|
+
# adding elements to the `<head>` section. Here's how a title can be
|
186
|
+
# controlled from a nested component:
|
187
|
+
#
|
188
|
+
# layout = H {
|
189
|
+
# html {
|
190
|
+
# head {
|
191
|
+
# defer { title @title }
|
192
|
+
# }
|
193
|
+
# body {
|
194
|
+
# emit_yield
|
195
|
+
# }
|
196
|
+
# }
|
197
|
+
# }
|
198
|
+
#
|
199
|
+
# html.render {
|
200
|
+
# @title = 'My super page'
|
201
|
+
# h1 'content'
|
202
|
+
# }
|
203
|
+
#
|
204
|
+
# @param &block [Proc] Deferred block to be emitted
|
205
|
+
# @return [void]
|
206
|
+
def defer(&block)
|
207
|
+
if !@parts
|
208
|
+
@parts = [@buffer, block]
|
209
|
+
else
|
210
|
+
@parts << @buffer unless @buffer.empty?
|
211
|
+
@parts << block
|
212
|
+
end
|
213
|
+
@buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
|
214
|
+
end
|
138
215
|
|
139
216
|
S_LT = '<'
|
140
217
|
S_GT = '>'
|
@@ -192,6 +269,23 @@ module Papercraft
|
|
192
269
|
end
|
193
270
|
}
|
194
271
|
end
|
272
|
+
|
273
|
+
# Renders a deferred proc by evaluating it, then adding the rendered result
|
274
|
+
# to the buffer.
|
275
|
+
#
|
276
|
+
# @param &block [Proc] deferred proc
|
277
|
+
# @return [void]
|
278
|
+
def render_deferred_proc(&block)
|
279
|
+
old_buffer = @buffer
|
280
|
+
|
281
|
+
@buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
|
282
|
+
@parts = nil
|
283
|
+
|
284
|
+
instance_eval(&block)
|
285
|
+
|
286
|
+
old_buffer << to_s
|
287
|
+
@buffer = old_buffer
|
288
|
+
end
|
195
289
|
end
|
196
290
|
|
197
291
|
# Implements an HTML renderer
|
@@ -200,6 +294,7 @@ module Papercraft
|
|
200
294
|
|
201
295
|
private
|
202
296
|
|
297
|
+
# Escapes the given text using HTML entities.
|
203
298
|
def escape_text(text)
|
204
299
|
EscapeUtils.escape_html(text.to_s)
|
205
300
|
end
|
@@ -209,6 +304,7 @@ module Papercraft
|
|
209
304
|
class XMLRenderer < Renderer
|
210
305
|
private
|
211
306
|
|
307
|
+
# Escapes the given text using XML entities.
|
212
308
|
def escape_text(text)
|
213
309
|
EscapeUtils.escape_xml(text.to_s)
|
214
310
|
end
|
data/lib/papercraft/version.rb
CHANGED
data/lib/papercraft.rb
CHANGED
@@ -11,6 +11,14 @@ 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
|
+
def self.extension(map)
|
20
|
+
Renderer.extension(map)
|
21
|
+
end
|
14
22
|
end
|
15
23
|
|
16
24
|
# 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.11'
|
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-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: escape_utils
|
@@ -24,6 +24,48 @@ dependencies:
|
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.2.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: kramdown
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.3.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.3.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rouge
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.26.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.26.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: kramdown-parser-gfm
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.1.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.1.0
|
27
69
|
- !ruby/object:Gem::Dependency
|
28
70
|
name: minitest
|
29
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,7 +122,7 @@ dependencies:
|
|
80
122
|
- - '='
|
81
123
|
- !ruby/object:Gem::Version
|
82
124
|
version: 2.0.9
|
83
|
-
description:
|
125
|
+
description:
|
84
126
|
email: sharon@noteflakes.com
|
85
127
|
executables: []
|
86
128
|
extensions: []
|
@@ -93,6 +135,7 @@ files:
|
|
93
135
|
- lib/papercraft/compiler.rb
|
94
136
|
- lib/papercraft/component.rb
|
95
137
|
- lib/papercraft/encoding.rb
|
138
|
+
- lib/papercraft/extension_proxy.rb
|
96
139
|
- lib/papercraft/html.rb
|
97
140
|
- lib/papercraft/renderer.rb
|
98
141
|
- lib/papercraft/version.rb
|
@@ -104,7 +147,7 @@ metadata:
|
|
104
147
|
documentation_uri: https://www.rubydoc.info/gems/papercraft
|
105
148
|
homepage_uri: https://github.com/digital-fabric/papercraft
|
106
149
|
changelog_uri: https://github.com/digital-fabric/papercraft/blob/master/CHANGELOG.md
|
107
|
-
post_install_message:
|
150
|
+
post_install_message:
|
108
151
|
rdoc_options:
|
109
152
|
- "--title"
|
110
153
|
- Papercraft
|
@@ -123,8 +166,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
166
|
- !ruby/object:Gem::Version
|
124
167
|
version: '0'
|
125
168
|
requirements: []
|
126
|
-
rubygems_version: 3.
|
127
|
-
signing_key:
|
169
|
+
rubygems_version: 3.3.3
|
170
|
+
signing_key:
|
128
171
|
specification_version: 4
|
129
172
|
summary: 'Papercraft: component-based HTML templating for Ruby'
|
130
173
|
test_files: []
|