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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c67f363b3f2f756ab72d89bc9d54f3e7c717a0134b080d522cd7d87923ee21b2
4
- data.tar.gz: 4d8ed90dae13469d324cebef15735386c12161edba87fac791aa8cbdf60f3ba8
3
+ metadata.gz: a84949b86e99acbe131d37cb624dbf60010fe720ce27081d5e5652b33f2de004
4
+ data.tar.gz: c03bef8c358bae1536c04739c2fa67bd3ce6e879fd036f2e7352c6291677b888
5
5
  SHA512:
6
- metadata.gz: f5af31de4e3673acf332c748dbfec372b998958f3807fa410951ec7bf28e33e0fcda2adb92f9455486bc57b1cf40147d43451d31f7ea452e4ff5036accca607d
7
- data.tar.gz: 164dbf6b2e03df22a85a6eb2267d34cd2e9dd4c51a1761bb469cd57e1c3167916438cffb8d66e6a8e62d225568a57f5d053e3da8cf478f56d3118fa459a4cb49
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 { emit_yield **props }
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 complex
49
- web interfaces.
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
- - [Basic Usage](#basic-usage)
90
- - [Adding Tags](#adding-tags)
91
- - [Tag and Attribute Formatting](#tag-and-attribute-formatting)
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
- ## Basic Usage
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 `#render`:
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
- ## Expressing HTML Using Ruby
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
- ## Tag and Attribute Formatting
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
- ## Escaping Content
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. For HTML templates, P2 uses
205
- [escape_utils](https://github.com/brianmario/escape_utils), specifically:
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
- - HTML: `escape_utils.escape_html`
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
- In order to emit raw HTML, you can use the `#emit` method as [described
210
- below](#emitting-raw-html).
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 = -> { |name| h1 "Hello, #{name}!" }
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 = -> { |name:| h1 "Hello, #{name}!" }
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
- -> { |user = nil|
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 `emit_yield`:
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 { emit_yield }
375
+ body { render_yield }
253
376
  }
254
377
  }
255
378
 
@@ -280,7 +403,7 @@ ItemList = ->(items) {
280
403
  }
281
404
  }
282
405
 
283
- page = -> { |title, items|
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 { emit_yield } }
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 { emit_yield } }
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
- emit_yield(**params)
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 { emit_yield **args }
603
+ body { render_yield **args }
481
604
  }
482
605
 
483
606
  button = proc { |text, onclick|
@@ -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 BlockInvocationNode
155
- attr_reader :call_node, :location, :block
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.visit_block_invocation_node(self)
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
- match_emit_yield(node) ||
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 match_emit_yield(node)
58
+ def match_extension(node)
55
59
  return if node.receiver
56
- return if node.name != :emit_yield
57
-
58
- yield_node(
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 @yield_used
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 yield node.
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::YieldNode] node
349
+ # @param node [P2::RenderYieldNode] node
298
350
  # @return [void]
299
- def visit_yield_node(node)
351
+ def visit_render_yield_node(node)
300
352
  flush_html_parts!
301
353
  adjust_whitespace(node.location)
302
- @yield_used = true
303
- emit("; (__block__ ? __block__.compiled_proc.(__buffer__")
304
- if node.arguments
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(") : raise(LocalJumpError, 'no block given (yield/emit_yield)'))")
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 = loc
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
- code = +''
528
- part = +''
529
-
530
- @pending_html_parts.each do
531
- if (m = it.match(/^#\{(.+)\}$/m))
532
- emit_html_buffer_push(code, part, quotes: true) if !part.empty?
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
- part << it
609
+ concatenated << part
536
610
  end
611
+ last_loc = loc
537
612
  end
538
- emit_html_buffer_push(code, part, quotes: true) if !part.empty?
613
+ emit_html_buffer_push(concatenated, quotes: true) if !concatenated.empty?
539
614
 
540
615
  @pending_html_parts.clear
541
616
 
542
- @last_loc = @html_loc_end
543
- @last_loc_start = loc_start(@html_loc_end)
544
- @last_loc_end = loc_end(@html_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(buf, part, quotes: false)
631
+ def emit_html_buffer_push(part, quotes: false)
559
632
  return if part.empty?
560
633
 
561
634
  q = quotes ? '"' : ''
562
- buf << "; __buffer__ << #{q}#{part}#{q}"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module P2
4
- VERSION = '2.6'
4
+ VERSION = '2.8'
5
5
  end
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
- source_line = source_map[line] || "?(#{line})"
71
- entry.sub(m[1], "#{source_map[:source_fn]}:#{source_line}")
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.6'
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.8.3
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.8.3
25
+ version: '0.9'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: kramdown
28
28
  requirement: !ruby/object:Gem::Requirement