p2 2.6 → 2.8
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 +12 -0
- data/README.md +153 -30
- data/lib/p2/compiler/nodes.rb +61 -5
- data/lib/p2/compiler/tag_translator.rb +9 -8
- data/lib/p2/compiler.rb +108 -35
- data/lib/p2/proc_ext.rb +11 -1
- data/lib/p2/version.rb +1 -1
- data/lib/p2.rb +14 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a84949b86e99acbe131d37cb624dbf60010fe720ce27081d5e5652b33f2de004
|
4
|
+
data.tar.gz: c03bef8c358bae1536c04739c2fa67bd3ce6e879fd036f2e7352c6291677b888
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2993a42368846048e02ca7cb01d2ba6f7be013870a5f39597478442d16659b730356443b1b77ee8bb36c6b9a3942bde7d8ef19e66ad087d2a69d9b686d5cc706
|
7
|
+
data.tar.gz: 7739be72dd3bdf195188339509a21e24bccbae6ac8efb140a16780445f71b60fea12ab0a87831f117422b620102c277391f33db944b94b7945088f16cf099d73
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
# 2.8 2025-08-17
|
2
|
+
|
3
|
+
- Add render_children builtin
|
4
|
+
- Rename emit_yield to render_yield
|
5
|
+
- Add `Proc#render_cached` for caching render result
|
6
|
+
|
7
|
+
# 2.7 2025-08-17
|
8
|
+
|
9
|
+
- Improve source maps and whitespace in compiled code
|
10
|
+
- Minor improvements to emit_yield generated code
|
11
|
+
- Add support for extensions
|
12
|
+
|
1
13
|
# 2.6 2025-08-16
|
2
14
|
|
3
15
|
- Add support for block invocation.
|
data/README.md
CHANGED
@@ -30,7 +30,7 @@ require 'p2'
|
|
30
30
|
page = ->(**props) {
|
31
31
|
html {
|
32
32
|
head { title 'My Title' }
|
33
|
-
body {
|
33
|
+
body { render_yield **props }
|
34
34
|
}
|
35
35
|
}
|
36
36
|
page.render {
|
@@ -45,8 +45,8 @@ templates are expressed as Ruby procs, leading to easier debugging, better
|
|
45
45
|
protection against HTML injection attacks, and better code reuse.
|
46
46
|
|
47
47
|
P2 templates can be composed in a variety of ways, facilitating the usage of
|
48
|
-
layout templates, and enabling a component-oriented approach to building
|
49
|
-
|
48
|
+
layout templates, and enabling a component-oriented approach to building web
|
49
|
+
interfaces of arbitrary complexity.
|
50
50
|
|
51
51
|
In P2, dynamic data is passed explicitly to the template as block/lambda
|
52
52
|
arguments, making the data flow easy to follow and understand. P2 also lets
|
@@ -77,19 +77,21 @@ hello.render(name: 'world')
|
|
77
77
|
P2 features:
|
78
78
|
|
79
79
|
- Express HTML using plain Ruby procs.
|
80
|
-
- Deferred rendering using `defer`.
|
81
|
-
- Template composition (for uses such as layouts).
|
82
|
-
- Markdown rendering using [Kramdown](https://github.com/gettalong/kramdown/).
|
83
80
|
- Automatic compilation for super-fast execution (about as
|
84
81
|
[fast](https://github.com/digital-fabric/p2/blob/master/examples/perf.rb) as
|
85
82
|
compiled ERB/ERubi).
|
83
|
+
- Deferred rendering using `defer`.
|
84
|
+
- Simple and easy template composition (for uses such as layouts, or modular
|
85
|
+
templates).
|
86
|
+
- Markdown rendering using [Kramdown](https://github.com/gettalong/kramdown/).
|
87
|
+
- Support for extensions.
|
88
|
+
- Simple caching API for caching the rendering result.
|
86
89
|
|
87
90
|
## Table of Content
|
88
91
|
|
89
|
-
- [
|
90
|
-
- [
|
91
|
-
- [
|
92
|
-
- [Escaping Content](#escaping-content)
|
92
|
+
- [Getting Started](#getting-started)
|
93
|
+
- [Basic Markup](#markup)
|
94
|
+
- [Builtin Methods](#builtin-methods)
|
93
95
|
- [Template Parameters](#template-parameters)
|
94
96
|
- [Template Logic](#template-logic)
|
95
97
|
- [Template Blocks](#template-blocks)
|
@@ -103,7 +105,7 @@ P2 features:
|
|
103
105
|
- [Deferred Evaluation](#deferred-evaluation)
|
104
106
|
- [API Reference](#api-reference)
|
105
107
|
|
106
|
-
##
|
108
|
+
## Getting Started
|
107
109
|
|
108
110
|
In P2, an HTML template is expressed as a proc:
|
109
111
|
|
@@ -113,7 +115,7 @@ html = -> {
|
|
113
115
|
}
|
114
116
|
```
|
115
117
|
|
116
|
-
Rendering a template is done using
|
118
|
+
Rendering a template is done using `Proc#render`:
|
117
119
|
|
118
120
|
```ruby
|
119
121
|
require 'p2'
|
@@ -121,7 +123,7 @@ require 'p2'
|
|
121
123
|
html.render #=> "<div id="greeter"><p>Hello!</p></div>"
|
122
124
|
```
|
123
125
|
|
124
|
-
##
|
126
|
+
## Markup
|
125
127
|
|
126
128
|
Tags are added using unqualified method calls, and can be nested using blocks:
|
127
129
|
|
@@ -172,7 +174,7 @@ An attribute value given as an array will be joined by space characters:
|
|
172
174
|
-> { div class: [:foo, :bar] }.render #=> "<div class=\"foo bar\"></div>"
|
173
175
|
```
|
174
176
|
|
175
|
-
|
177
|
+
### Tag and Attribute Formatting
|
176
178
|
|
177
179
|
P2 does not make any assumption about what tags and attributes you can use. You
|
178
180
|
can mix upper and lower case letters, and you can include arbitrary characters
|
@@ -198,16 +200,137 @@ normally used for tags:
|
|
198
200
|
}.render #=> '<cra_zy__:!tag>foo</cra_zy__:!tag>'
|
199
201
|
```
|
200
202
|
|
201
|
-
|
203
|
+
### Escaping Content
|
202
204
|
|
203
205
|
P2 automatically escapes all text content emitted in a template. The specific
|
204
|
-
escaping algorithm depends on the template type.
|
205
|
-
[
|
206
|
+
escaping algorithm depends on the template type. To emit raw HTML, use the
|
207
|
+
`#raw` method as [described below](#builtin-methods).
|
208
|
+
|
209
|
+
## Builtin Methods
|
210
|
+
|
211
|
+
In addition to normal tags, P2 provides the following method calls for templates:
|
212
|
+
|
213
|
+
### `#text` - emit escaped text
|
214
|
+
|
215
|
+
`#text` is used for emitting text that will be escaped. This method can be used
|
216
|
+
to emit text not directly inside an enclosing tag:
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
-> {
|
220
|
+
p {
|
221
|
+
text 'The time is: '
|
222
|
+
span(Time.now, id: 'clock')
|
223
|
+
}
|
224
|
+
}.render #=> <p>The time is: <span id="clock">XX:XX:XX</span></p>
|
225
|
+
```
|
226
|
+
|
227
|
+
### `#raw` - emit raw HTML
|
228
|
+
|
229
|
+
`#raw` is used for emitting raw HTML, i.e. without escaping. You can use this to
|
230
|
+
emit an HTML snippet:
|
231
|
+
|
232
|
+
```ruby
|
233
|
+
TITLE_HTML = '<h1>hi</h1>'
|
234
|
+
-> {
|
235
|
+
div {
|
236
|
+
raw TITLE_HTML
|
237
|
+
}
|
238
|
+
}.render #=> <div><h1>hi</h1></div>
|
239
|
+
```
|
240
|
+
|
241
|
+
### `#render_yield` - emit given block
|
242
|
+
|
243
|
+
`#render_yield` is used to emit a given block. If no block is given, a
|
244
|
+
`LocalJumpError` exception is raised:
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
Card = ->(**props) {
|
248
|
+
card { render_yield(**props) }
|
249
|
+
}
|
250
|
+
|
251
|
+
Card.render(foo: 'bar') { |foo|
|
252
|
+
h1 foo
|
253
|
+
} #=> <card><h1>bar</h1></card>
|
254
|
+
```
|
255
|
+
|
256
|
+
`render_yield` can be called with or without arguments, which are passed to the
|
257
|
+
given block.
|
258
|
+
|
259
|
+
### `#render_children` - emit given block
|
260
|
+
|
261
|
+
`#render_children` is used to emit a given block, but does not raise an
|
262
|
+
exception if no block is given.
|
263
|
+
|
264
|
+
### `#defer` - emit deferred HTML
|
265
|
+
|
266
|
+
`#defer` is used to emit HTML in a deferred fashion - the deferred part will be
|
267
|
+
evaluated only after processing the entire template:
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
Layout = -> {
|
271
|
+
head {
|
272
|
+
defer {
|
273
|
+
title @title
|
274
|
+
}
|
275
|
+
}
|
276
|
+
body {
|
277
|
+
render_yield
|
278
|
+
}
|
279
|
+
}
|
280
|
+
|
281
|
+
Layout.render {
|
282
|
+
@title = 'Foobar'
|
283
|
+
h1 'hi'
|
284
|
+
} #=> <head><title>Foobar</title></head><body><h1>hi</h1></body>
|
285
|
+
```
|
286
|
+
|
287
|
+
### `#render` - render the given template inline
|
288
|
+
|
289
|
+
`#render` is used to emit the given template. This can be used to compose
|
290
|
+
templates:
|
206
291
|
|
207
|
-
|
292
|
+
```ruby
|
293
|
+
partial = -> { p 'foo' }
|
294
|
+
-> {
|
295
|
+
div {
|
296
|
+
render partial
|
297
|
+
}
|
298
|
+
}.render #=> <div><p>foo</p></div>
|
299
|
+
```
|
300
|
+
|
301
|
+
Any argument following the given template is passed to the template for
|
302
|
+
rendering:
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
large_button = ->(title) { button(title, class: 'large') }
|
208
306
|
|
209
|
-
|
210
|
-
|
307
|
+
-> {
|
308
|
+
render large_button, 'foo'
|
309
|
+
}.render #=> <button class="large">foo</button>
|
310
|
+
```
|
311
|
+
|
312
|
+
### `#html5` - emit an HTML5 document type declaration and html tag
|
313
|
+
|
314
|
+
```ruby
|
315
|
+
-> {
|
316
|
+
html5 {
|
317
|
+
p 'hi'
|
318
|
+
}
|
319
|
+
} #=> <!DOCTYPE html><html><p>hi</p></html>
|
320
|
+
```
|
321
|
+
|
322
|
+
### `#markdown` emit markdown content
|
323
|
+
|
324
|
+
`#markdown` is used for rendering markdown content. The call converts the given
|
325
|
+
markdown to HTML and emits it into the rendered HTML:
|
326
|
+
|
327
|
+
```ruby
|
328
|
+
-> {
|
329
|
+
div {
|
330
|
+
markdown 'This is *markdown*'
|
331
|
+
}
|
332
|
+
}.render #=> <p>This is <em>markdown</em></p>
|
333
|
+
```
|
211
334
|
|
212
335
|
## Template Parameters
|
213
336
|
|
@@ -216,14 +339,14 @@ parameters are specified as block parameters, and are passed to the template on
|
|
216
339
|
rendering:
|
217
340
|
|
218
341
|
```ruby
|
219
|
-
greeting = -> {
|
342
|
+
greeting = ->(name) { h1 "Hello, #{name}!" }
|
220
343
|
greeting.render('world') #=> "<h1>Hello, world!</h1>"
|
221
344
|
```
|
222
345
|
|
223
346
|
Templates can also accept named parameters:
|
224
347
|
|
225
348
|
```ruby
|
226
|
-
greeting = -> {
|
349
|
+
greeting = ->(name:) { h1 "Hello, #{name}!" }
|
227
350
|
greeting.render(name: 'world') #=> "<h1>Hello, world!</h1>"
|
228
351
|
```
|
229
352
|
|
@@ -233,7 +356,7 @@ Since P2 templates are just a bunch of Ruby, you can easily embed your view
|
|
233
356
|
logic right in the template:
|
234
357
|
|
235
358
|
```ruby
|
236
|
-
->
|
359
|
+
->(user = nil) {
|
237
360
|
if user
|
238
361
|
span "Hello, #{user.name}!"
|
239
362
|
else
|
@@ -244,12 +367,12 @@ logic right in the template:
|
|
244
367
|
|
245
368
|
## Template Blocks
|
246
369
|
|
247
|
-
Templates can also accept and render blocks by using `
|
370
|
+
Templates can also accept and render blocks by using `render_yield`:
|
248
371
|
|
249
372
|
```ruby
|
250
373
|
page = -> {
|
251
374
|
html {
|
252
|
-
body {
|
375
|
+
body { render_yield }
|
253
376
|
}
|
254
377
|
}
|
255
378
|
|
@@ -280,7 +403,7 @@ ItemList = ->(items) {
|
|
280
403
|
}
|
281
404
|
}
|
282
405
|
|
283
|
-
page = ->
|
406
|
+
page = ->(title, items) {
|
284
407
|
html5 {
|
285
408
|
head { Title(title) }
|
286
409
|
body { ItemList(items) }
|
@@ -322,7 +445,7 @@ hello_world = hello.apply('world')
|
|
322
445
|
hello_world.render #=> "<h1>Hello, world!</h1>"
|
323
446
|
|
324
447
|
# block application
|
325
|
-
div_wrap = -> { div {
|
448
|
+
div_wrap = -> { div { render_yield } }
|
326
449
|
wrapped_h1 = div_wrap.apply { h1 'hi' }
|
327
450
|
wrapped_h1.render #=> "<div><h1>hi</h1></div>"
|
328
451
|
|
@@ -350,7 +473,7 @@ wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
|
350
473
|
The inner template can also be passed as a block, as shown above:
|
351
474
|
|
352
475
|
```ruby
|
353
|
-
div_wrap = -> { div {
|
476
|
+
div_wrap = -> { div { render_yield } }
|
354
477
|
wrapped_greeter = div_wrap.apply { h1 'hi' }
|
355
478
|
wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
356
479
|
```
|
@@ -370,7 +493,7 @@ default_layout = -> { |**params|
|
|
370
493
|
title: params[:title]
|
371
494
|
}
|
372
495
|
body {
|
373
|
-
|
496
|
+
render_yield(**params)
|
374
497
|
}
|
375
498
|
}
|
376
499
|
}
|
@@ -477,7 +600,7 @@ default_layout = -> { |**args|
|
|
477
600
|
head {
|
478
601
|
defer { render deps.head_markup }
|
479
602
|
}
|
480
|
-
body {
|
603
|
+
body { render_yield **args }
|
481
604
|
}
|
482
605
|
|
483
606
|
button = proc { |text, onclick|
|
data/lib/p2/compiler/nodes.rb
CHANGED
@@ -1,8 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
class Prism::InspectVisitor
|
4
|
+
def visit_tag_node(node)
|
5
|
+
commands << [inspect_node("TagNode", node), indent]
|
6
|
+
# flags = [("newline" if node.newline?), ("static_literal" if node.static_literal?), ].compact
|
7
|
+
# commands << ["├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n", indent]
|
8
|
+
# commands << ["├── left:\n", indent]
|
9
|
+
# commands << [node.left, "#{indent}│ "]
|
10
|
+
# commands << ["├── right:\n", indent]
|
11
|
+
# commands << [node.right, "#{indent}│ "]
|
12
|
+
# commands << ["└── operator_loc: #{inspect_location(node.operator_loc)}\n", indent]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
3
16
|
module P2
|
4
17
|
# Represents a tag call
|
5
|
-
class TagNode
|
18
|
+
class TagNode < Prism::Node
|
6
19
|
attr_reader :call_node, :location, :tag, :tag_location, :inner_text, :attributes, :block
|
7
20
|
|
8
21
|
def initialize(call_node, translator)
|
@@ -149,19 +162,62 @@ module P2
|
|
149
162
|
visitor.visit_builtin_node(self)
|
150
163
|
end
|
151
164
|
end
|
165
|
+
|
166
|
+
class ExtensionTagNode
|
167
|
+
attr_reader :tag, :call_node, :location, :block
|
168
|
+
|
169
|
+
def initialize(call_node, translator)
|
170
|
+
@call_node = call_node
|
171
|
+
@tag = call_node.name
|
172
|
+
@location = call_node.location
|
173
|
+
@block = call_node.block && translator.visit(call_node.block)
|
174
|
+
end
|
175
|
+
|
176
|
+
def accept(visitor)
|
177
|
+
visitor.visit_extension_tag_node(self)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class BlockInvocationNode
|
182
|
+
attr_reader :call_node, :location, :block
|
183
|
+
|
184
|
+
def initialize(call_node, translator)
|
185
|
+
@call_node = call_node
|
186
|
+
@tag = call_node.name
|
187
|
+
@location = call_node.location
|
188
|
+
@block = call_node.block && translator.visit(call_node.block)
|
189
|
+
end
|
190
|
+
|
191
|
+
def accept(visitor)
|
192
|
+
visitor.visit_block_invocation_node(self)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class RenderYieldNode
|
198
|
+
attr_reader :call_node, :location
|
199
|
+
|
200
|
+
def initialize(call_node, translator)
|
201
|
+
@call_node = call_node
|
202
|
+
@tag = call_node.name
|
203
|
+
@location = call_node.location
|
204
|
+
end
|
205
|
+
|
206
|
+
def accept(visitor)
|
207
|
+
visitor.visit_render_yield_node(self)
|
208
|
+
end
|
152
209
|
end
|
153
210
|
|
154
|
-
class
|
155
|
-
attr_reader :call_node, :location
|
211
|
+
class RenderChildrenNode
|
212
|
+
attr_reader :call_node, :location
|
156
213
|
|
157
214
|
def initialize(call_node, translator)
|
158
215
|
@call_node = call_node
|
159
216
|
@tag = call_node.name
|
160
217
|
@location = call_node.location
|
161
|
-
@block = call_node.block && translator.visit(call_node.block)
|
162
218
|
end
|
163
219
|
|
164
220
|
def accept(visitor)
|
165
|
-
visitor.
|
221
|
+
visitor.visit_render_children_node(self)
|
166
222
|
end
|
167
223
|
end
|
@@ -23,7 +23,7 @@ module P2
|
|
23
23
|
return super(node) if dont_translate
|
24
24
|
|
25
25
|
match_builtin(node) ||
|
26
|
-
|
26
|
+
match_extension(node) ||
|
27
27
|
match_const_tag(node) ||
|
28
28
|
match_block_call(node) ||
|
29
29
|
match_tag(node) ||
|
@@ -34,6 +34,10 @@ module P2
|
|
34
34
|
return if node.receiver
|
35
35
|
|
36
36
|
case node.name
|
37
|
+
when :render_yield
|
38
|
+
RenderYieldNode.new(node, self)
|
39
|
+
when :render_children
|
40
|
+
RenderChildrenNode.new(node, self)
|
37
41
|
when :raise
|
38
42
|
visit_call_node(node, dont_translate: true)
|
39
43
|
when :render
|
@@ -51,14 +55,11 @@ module P2
|
|
51
55
|
end
|
52
56
|
end
|
53
57
|
|
54
|
-
def
|
58
|
+
def match_extension(node)
|
55
59
|
return if node.receiver
|
56
|
-
return if node.name
|
57
|
-
|
58
|
-
|
59
|
-
location: node.location,
|
60
|
-
arguments: node.arguments
|
61
|
-
)
|
60
|
+
return if !P2::Extensions[node.name]
|
61
|
+
|
62
|
+
ExtensionTagNode.new(node, self)
|
62
63
|
end
|
63
64
|
|
64
65
|
def match_const_tag(node)
|
data/lib/p2/compiler.rb
CHANGED
@@ -53,6 +53,8 @@ module P2
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def self.store_source_map(source_map)
|
56
|
+
return if !source_map
|
57
|
+
|
56
58
|
fn = source_map[:compiled_fn]
|
57
59
|
source_map_store[fn] = source_map
|
58
60
|
end
|
@@ -67,9 +69,6 @@ module P2
|
|
67
69
|
def initialize(**)
|
68
70
|
super(**)
|
69
71
|
@pending_html_parts = []
|
70
|
-
@html_loc_start = nil
|
71
|
-
@html_loc_end = nil
|
72
|
-
@yield_used = nil
|
73
72
|
end
|
74
73
|
|
75
74
|
# Initializes a source map.
|
@@ -79,6 +78,8 @@ module P2
|
|
79
78
|
# @return [self]
|
80
79
|
def with_source_map(orig_proc, orig_ast)
|
81
80
|
fn = Compiler.source_location_to_fn(orig_proc.source_location)
|
81
|
+
@orig_proc = orig_proc
|
82
|
+
@orig_proc_fn = orig_proc.source_location.first
|
82
83
|
@source_map = {
|
83
84
|
source_fn: orig_proc.source_location.first,
|
84
85
|
compiled_fn: fn
|
@@ -87,6 +88,15 @@ module P2
|
|
87
88
|
self
|
88
89
|
end
|
89
90
|
|
91
|
+
def update_source_map(str = nil)
|
92
|
+
return if !@source_map
|
93
|
+
|
94
|
+
buffer_cur_line = @buffer.count("\n") + 1
|
95
|
+
orig_source_cur_line = @last_loc_start ? @last_loc_start.first : '?'
|
96
|
+
@source_map[buffer_cur_line + @source_map_line_ofs] ||=
|
97
|
+
"#{@orig_proc_fn}:#{orig_source_cur_line}"
|
98
|
+
end
|
99
|
+
|
90
100
|
# Formats the source code for a compiled template proc.
|
91
101
|
#
|
92
102
|
# @param ast [Prism::Node] translated AST
|
@@ -104,7 +114,7 @@ module P2
|
|
104
114
|
source_code = @buffer
|
105
115
|
@buffer = +''
|
106
116
|
if wrap
|
107
|
-
@source_map[2] = loc_start(orig_ast.location).first
|
117
|
+
@source_map[2] = "#{@orig_proc_fn}:#{loc_start(orig_ast.location).first}"
|
108
118
|
emit("# frozen_string_literal: true\n->(__buffer__")
|
109
119
|
|
110
120
|
params = orig_ast.parameters
|
@@ -114,11 +124,12 @@ module P2
|
|
114
124
|
emit(format_code(params))
|
115
125
|
end
|
116
126
|
|
117
|
-
if @
|
127
|
+
if @render_yield_used || @render_children_used
|
118
128
|
emit(', &__block__')
|
119
129
|
end
|
120
130
|
|
121
131
|
emit(") {\n")
|
132
|
+
|
122
133
|
end
|
123
134
|
@buffer << source_code
|
124
135
|
emit_defer_postlude if @defer_mode
|
@@ -126,8 +137,6 @@ module P2
|
|
126
137
|
emit('; __buffer__')
|
127
138
|
adjust_whitespace(orig_ast.closing_loc)
|
128
139
|
emit('}')
|
129
|
-
# emit(";") if @buffer !~ /\n\s*$/m
|
130
|
-
# emit("rescue Exception => e; P2.translate_backtrace(e, src_map); raise e; end }")
|
131
140
|
end
|
132
141
|
update_source_map
|
133
142
|
Compiler.store_source_map(@source_map)
|
@@ -141,7 +150,7 @@ module P2
|
|
141
150
|
def visit_tag_node(node)
|
142
151
|
tag = node.tag
|
143
152
|
|
144
|
-
adjust_whitespace(node.location)
|
153
|
+
# adjust_whitespace(node.location)
|
145
154
|
is_void = is_void_element?(tag)
|
146
155
|
emit_html(node.tag_location, format_html_tag_open(tag, node.attributes))
|
147
156
|
return if is_void
|
@@ -292,20 +301,81 @@ module P2
|
|
292
301
|
end
|
293
302
|
end
|
294
303
|
|
295
|
-
# Visits a
|
304
|
+
# Visits a extension tag node.
|
305
|
+
#
|
306
|
+
# @param node [P2::ExtensionTagNode] node
|
307
|
+
# @return [void]
|
308
|
+
def visit_extension_tag_node(node)
|
309
|
+
flush_html_parts!
|
310
|
+
adjust_whitespace(node.location)
|
311
|
+
emit("; P2::Extensions[#{node.tag.inspect}].compiled_proc.(__buffer__")
|
312
|
+
if node.call_node.arguments
|
313
|
+
emit(', ')
|
314
|
+
visit(node.call_node.arguments)
|
315
|
+
end
|
316
|
+
if node.block
|
317
|
+
block_body = format_inline_block(node.block.body)
|
318
|
+
block_params = []
|
319
|
+
|
320
|
+
if node.block.parameters.is_a?(Prism::ItParametersNode)
|
321
|
+
raise P2::Error, "Blocks passed to extensions cannot use it parameter"
|
322
|
+
end
|
323
|
+
|
324
|
+
if (params = node.block.parameters&.parameters)
|
325
|
+
params.requireds.each do
|
326
|
+
block_params << format_code(it) if !it.is_a?(Prism::ItParametersNode)
|
327
|
+
end
|
328
|
+
params.optionals.each do
|
329
|
+
block_params << format_code(it) if !it.is_a?(Prism::ItParametersNode)
|
330
|
+
end
|
331
|
+
block_params << format_code(params.rest) if params.rest
|
332
|
+
params.posts.each do
|
333
|
+
block_params << format_code(it) if !it.is_a?(Prism::ItParametersNode)
|
334
|
+
end
|
335
|
+
params.keywords.each do
|
336
|
+
block_params << format_code(it) if !it.is_a?(Prism::ItParametersNode)
|
337
|
+
end
|
338
|
+
block_params << format_code(params.keyword_rest) if params.keyword_rest
|
339
|
+
end
|
340
|
+
block_params = block_params.empty? ? '' : ", #{block_params.join(', ')}"
|
341
|
+
|
342
|
+
emit(", &(proc { |__buffer__#{block_params}| #{block_body} }).compiled!")
|
343
|
+
end
|
344
|
+
emit(")")
|
345
|
+
end
|
346
|
+
|
347
|
+
# Visits a render_yield node.
|
296
348
|
#
|
297
|
-
# @param node [P2::
|
349
|
+
# @param node [P2::RenderYieldNode] node
|
298
350
|
# @return [void]
|
299
|
-
def
|
351
|
+
def visit_render_yield_node(node)
|
300
352
|
flush_html_parts!
|
301
353
|
adjust_whitespace(node.location)
|
302
|
-
|
303
|
-
|
304
|
-
|
354
|
+
guard = @render_yield_used ?
|
355
|
+
'' : "; raise(LocalJumpError, 'no block given (render_yield)') if !__block__"
|
356
|
+
@render_yield_used = true
|
357
|
+
emit("#{guard}; __block__.compiled_proc.(__buffer__")
|
358
|
+
if node.call_node.arguments
|
305
359
|
emit(', ')
|
306
|
-
visit(node.arguments)
|
360
|
+
visit(node.call_node.arguments)
|
307
361
|
end
|
308
|
-
emit(")
|
362
|
+
emit(")")
|
363
|
+
end
|
364
|
+
|
365
|
+
# Visits a render_children node.
|
366
|
+
#
|
367
|
+
# @param node [P2::RenderChildrenNode] node
|
368
|
+
# @return [void]
|
369
|
+
def visit_render_children_node(node)
|
370
|
+
flush_html_parts!
|
371
|
+
adjust_whitespace(node.location)
|
372
|
+
@render_children_used = true
|
373
|
+
emit("; __block__&.compiled_proc&.(__buffer__")
|
374
|
+
if node.call_node.arguments
|
375
|
+
emit(', ')
|
376
|
+
visit(node.call_node.arguments)
|
377
|
+
end
|
378
|
+
emit(")")
|
309
379
|
end
|
310
380
|
|
311
381
|
def visit_block_invocation_node(node)
|
@@ -356,6 +426,10 @@ module P2
|
|
356
426
|
Compiler.new(minimize_whitespace: true).to_source(node)
|
357
427
|
end
|
358
428
|
|
429
|
+
def format_inline_block(node)
|
430
|
+
Compiler.new(minimize_whitespace: true).format_compiled_template(node, node, wrap: false, binding: @binding)
|
431
|
+
end
|
432
|
+
|
359
433
|
# Formats a comma separated list of AST nodes. Used for formatting partial
|
360
434
|
# argument lists.
|
361
435
|
#
|
@@ -512,8 +586,8 @@ module P2
|
|
512
586
|
# @return [void]
|
513
587
|
def emit_html(loc, str)
|
514
588
|
@html_loc_start ||= loc
|
515
|
-
@html_loc_end
|
516
|
-
@pending_html_parts << str
|
589
|
+
@html_loc_end ||= loc
|
590
|
+
@pending_html_parts << [loc, str]
|
517
591
|
end
|
518
592
|
|
519
593
|
# Flushes pending HTML parts to the source code buffer.
|
@@ -522,31 +596,30 @@ module P2
|
|
522
596
|
def flush_html_parts!(semicolon_prefix: true)
|
523
597
|
return if @pending_html_parts.empty?
|
524
598
|
|
525
|
-
adjust_whitespace(@html_loc_start)
|
599
|
+
adjust_whitespace(@html_loc_start, advance_to_end: false)
|
600
|
+
concatenated = +''
|
526
601
|
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
emit_html_buffer_push(
|
533
|
-
emit_html_buffer_push(code, m[1])
|
602
|
+
last_loc = @html_loc_start
|
603
|
+
@pending_html_parts.each do |(loc, part)|
|
604
|
+
if (m = part.match(/^#\{(.+)\}$/m))
|
605
|
+
emit_html_buffer_push(concatenated, quotes: true) if !concatenated.empty?
|
606
|
+
adjust_whitespace(loc, advance_to_end: false)
|
607
|
+
emit_html_buffer_push(m[1])
|
534
608
|
else
|
535
|
-
|
609
|
+
concatenated << part
|
536
610
|
end
|
611
|
+
last_loc = loc
|
537
612
|
end
|
538
|
-
emit_html_buffer_push(
|
613
|
+
emit_html_buffer_push(concatenated, quotes: true) if !concatenated.empty?
|
539
614
|
|
540
615
|
@pending_html_parts.clear
|
541
616
|
|
542
|
-
@last_loc =
|
543
|
-
@last_loc_start = loc_start(@
|
544
|
-
@last_loc_end =
|
617
|
+
@last_loc = last_loc
|
618
|
+
@last_loc_start = loc_start(@last_loc)
|
619
|
+
@last_loc_end = @last_loc_start
|
545
620
|
|
546
621
|
@html_loc_start = nil
|
547
622
|
@html_loc_end = nil
|
548
|
-
|
549
|
-
emit code
|
550
623
|
end
|
551
624
|
|
552
625
|
# Emits HTML buffer push code to the given source code buffer.
|
@@ -555,11 +628,11 @@ module P2
|
|
555
628
|
# @param part [String] HTML part
|
556
629
|
# @param quotes [bool] whether to wrap emitted HTML in double quotes
|
557
630
|
# @return [void]
|
558
|
-
def emit_html_buffer_push(
|
631
|
+
def emit_html_buffer_push(part, quotes: false)
|
559
632
|
return if part.empty?
|
560
633
|
|
561
634
|
q = quotes ? '"' : ''
|
562
|
-
|
635
|
+
emit("; __buffer__ << #{q}#{part}#{q}")
|
563
636
|
part.clear
|
564
637
|
end
|
565
638
|
|
data/lib/p2/proc_ext.rb
CHANGED
@@ -50,7 +50,10 @@ class ::Proc
|
|
50
50
|
# @return [Proc] compiled proc
|
51
51
|
def compile
|
52
52
|
P2::Compiler.compile(self).compiled!
|
53
|
-
rescue Sirop::Error
|
53
|
+
rescue Sirop::Error => e
|
54
|
+
puts '!' * 40
|
55
|
+
p self
|
56
|
+
p e
|
54
57
|
raise P2::Error, "Dynamically defined procs cannot be compiled"
|
55
58
|
end
|
56
59
|
|
@@ -87,4 +90,11 @@ class ::Proc
|
|
87
90
|
compiled.(__buffer__, *a, *x, **b, **y, &c_proc)
|
88
91
|
}.compiled!
|
89
92
|
end
|
93
|
+
|
94
|
+
# Caches and returns
|
95
|
+
def render_cached(*args, **kargs)
|
96
|
+
@render_cache ||= {}
|
97
|
+
key = args.empty? && kargs.empty? ? nil : [args, kargs]
|
98
|
+
@render_cache[key] ||= render(*args, **kargs)
|
99
|
+
end
|
90
100
|
end
|
data/lib/p2/version.rb
CHANGED
data/lib/p2.rb
CHANGED
@@ -12,6 +12,18 @@ module P2
|
|
12
12
|
|
13
13
|
extend self
|
14
14
|
|
15
|
+
# Registry of P2 exgtensions
|
16
|
+
Extensions = {}
|
17
|
+
|
18
|
+
# Registers extensions to the P2 syntax.
|
19
|
+
#
|
20
|
+
# @param spec [Hash] hash mapping symbols to procs
|
21
|
+
# @return [self]
|
22
|
+
def extension(spec)
|
23
|
+
Extensions.merge!(spec)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
15
27
|
# Formats the given string, converting underscores to dashes.
|
16
28
|
#
|
17
29
|
# @param tag [String, Symbol] input string
|
@@ -67,8 +79,8 @@ module P2
|
|
67
79
|
source_map = cache[fn] ||= Compiler.source_map_store[fn]
|
68
80
|
return entry if !source_map
|
69
81
|
|
70
|
-
|
71
|
-
entry.sub(m[1],
|
82
|
+
ref = source_map[line] || "?(#{line})"
|
83
|
+
entry.sub(m[1], ref)
|
72
84
|
end
|
73
85
|
|
74
86
|
def make_argument_error(err, backtrace)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: p2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '2.
|
4
|
+
version: '2.8'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
@@ -15,14 +15,14 @@ dependencies:
|
|
15
15
|
requirements:
|
16
16
|
- - "~>"
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: 0.
|
18
|
+
version: '0.9'
|
19
19
|
type: :runtime
|
20
20
|
prerelease: false
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
22
22
|
requirements:
|
23
23
|
- - "~>"
|
24
24
|
- !ruby/object:Gem::Version
|
25
|
-
version: 0.
|
25
|
+
version: '0.9'
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: kramdown
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|