p2 2.0 → 2.1
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 +34 -27
- data/lib/p2/compiler.rb +95 -32
- data/lib/p2/proc_ext.rb +4 -2
- data/lib/p2/version.rb +1 -1
- data/lib/p2.rb +67 -77
- metadata +4 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c34fa05073968d2f8aed0d766ec9b59275fdb061ce2a328ad18038ce0f986068
|
4
|
+
data.tar.gz: c0bab8233ffc293432b33a531e554ebc379ec7d796c2f4046e105b185e0c5f5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb711aac468494ad590779e45a97fa9593d9a7e0e172389ae14125ba1576a4dda90bf5f796c56592d8a1b939cf00d2a69cd91386f5ef583453b2045bee333f5d
|
7
|
+
data.tar.gz: 8a1ee9990feb82379a01bd1971b00c844d3b0f3f9f0cc6531596619f47abf0e56c629d04e34a54f12594e7a73718e88854ab6056b30ad8019ab9a63d133e2101
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
# 2.1 2025-08-08
|
2
|
+
|
3
|
+
- Optimize output code: directly invoke component templates instead of calling
|
4
|
+
`P2.render_emit_call`. P2 is now
|
5
|
+
- Optimize output code: use separate pushes to buffer instead of interpolated
|
6
|
+
strings.
|
7
|
+
- Streamline API: `emit proc` => `render`, `emit str` => `raw`, `emit_markdown`
|
8
|
+
=> `markdown`
|
9
|
+
- Optimize output code: add `frozen_string_literal` to top of compiled code
|
10
|
+
- Add more benchmarks (#1)
|
11
|
+
- Optimize output code: use ERB::Escape.html_escape instead of CGI.escape_html
|
12
|
+
(#2)
|
13
|
+
- Fix source map calculation
|
14
|
+
|
15
|
+
## 2.0.1 2025-08-07
|
16
|
+
|
17
|
+
- Fix source map calculation
|
18
|
+
|
1
19
|
## 2.0 2025-08-07
|
2
20
|
|
3
21
|
- Passes all HTML, compilation tests from Papercraft
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
P2
|
5
5
|
</h1>
|
6
6
|
|
7
|
-
<h4 align="center">
|
7
|
+
<h4 align="center">Functional HTML templating for Ruby</h4>
|
8
8
|
|
9
9
|
<p align="center">
|
10
10
|
<a href="http://rubygems.org/gems/p2">
|
@@ -24,9 +24,9 @@
|
|
24
24
|
|
25
25
|
## What is P2?
|
26
26
|
|
27
|
-
P2 is a templating engine for dynamically producing
|
28
|
-
|
29
|
-
|
27
|
+
P2 is a templating engine for dynamically producing HTML. P2 templates are
|
28
|
+
expressed as Ruby procs, leading to easier debugging, better protection against
|
29
|
+
HTML injection attacks, and better code reuse.
|
30
30
|
|
31
31
|
P2 templates can be composed in a variety of ways, facilitating the usage of
|
32
32
|
layout templates, and enabling a component-oriented approach to building complex
|
@@ -37,11 +37,8 @@ making the data flow easy to follow and understand. P2 also lets developers
|
|
37
37
|
create derivative templates using full or partial parameter application.
|
38
38
|
|
39
39
|
P2 includes built-in support for rendering Markdown (using
|
40
|
-
[Kramdown](https://github.com/gettalong/kramdown/)).
|
41
|
-
|
42
|
-
P2 automatically escapes all text emitted in templates according to the template
|
43
|
-
type. For more information see the section on [escaping
|
44
|
-
content](#escaping-content).
|
40
|
+
[Kramdown](https://github.com/gettalong/kramdown/)). P2 also automatically
|
41
|
+
escapes all text emitted in templates.
|
45
42
|
|
46
43
|
```ruby
|
47
44
|
require 'p2'
|
@@ -64,6 +61,16 @@ hello.render(name: 'world')
|
|
64
61
|
#=> "<html><head><title>Title</title></head><body><h1>Hello, world!</h1></body></html>"
|
65
62
|
```
|
66
63
|
|
64
|
+
P2 features:
|
65
|
+
|
66
|
+
- Express HTML using plain Ruby procs.
|
67
|
+
- Automatic compilation for super-fast execution (up to 2X faster than ERB templates).
|
68
|
+
- Deferred rendering using `defer`.
|
69
|
+
- Template composition (for uses such as layouts).
|
70
|
+
- Automatic conversion of backtraces for exceptions occurring while rendering,
|
71
|
+
in order to point to the correct spot in the original template code.
|
72
|
+
|
73
|
+
|
67
74
|
## Table of Content
|
68
75
|
|
69
76
|
- [Installing P2](#installing-p2)
|
@@ -79,7 +86,7 @@ hello.render(name: 'world')
|
|
79
86
|
- [Higher-Order Templates](#higher-order-templates)
|
80
87
|
- [Layout Template Composition](#layout-template-composition)
|
81
88
|
- [Emitting Raw HTML](#emitting-raw-html)
|
82
|
-
- [Emitting a String with HTML
|
89
|
+
- [Emitting a String with HTML Escaping](#emitting-a-string-with-html-escaping)
|
83
90
|
- [Emitting Markdown](#emitting-markdown)
|
84
91
|
- [Deferred Evaluation](#deferred-evaluation)
|
85
92
|
- [API Reference](#api-reference)
|
@@ -291,14 +298,14 @@ page.render('Hello from composed templates', [
|
|
291
298
|
```
|
292
299
|
|
293
300
|
In addition to using templates defined as constants, you can also use
|
294
|
-
non-constant templates by invoking the `#
|
301
|
+
non-constant templates by invoking the `#render` method:
|
295
302
|
|
296
303
|
```ruby
|
297
304
|
greeting = -> { span "Hello, world" }
|
298
305
|
|
299
306
|
-> {
|
300
307
|
div {
|
301
|
-
|
308
|
+
render greeting
|
302
309
|
}
|
303
310
|
}
|
304
311
|
```
|
@@ -338,7 +345,7 @@ templates or injecting template parameters.
|
|
338
345
|
Here is a higher-order template that takes a template as parameter:
|
339
346
|
|
340
347
|
```ruby
|
341
|
-
div_wrap = -> { |inner| div {
|
348
|
+
div_wrap = -> { |inner| div { render inner } }
|
342
349
|
greeter = -> { h1 'hi' }
|
343
350
|
wrapped_greeter = div_wrap.apply(greeter)
|
344
351
|
wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
|
@@ -375,7 +382,7 @@ default_layout = -> { |**params|
|
|
375
382
|
article_layout = default_layout.apply { |title:, body:|
|
376
383
|
article {
|
377
384
|
h1 title
|
378
|
-
|
385
|
+
markdown body
|
379
386
|
}
|
380
387
|
}
|
381
388
|
|
@@ -387,16 +394,16 @@ article_layout.render(
|
|
387
394
|
|
388
395
|
## Emitting Raw HTML
|
389
396
|
|
390
|
-
Raw HTML can be emitted using `#
|
397
|
+
Raw HTML can be emitted using `#raw`:
|
391
398
|
|
392
399
|
```ruby
|
393
|
-
wrapped = -> { |html| div {
|
400
|
+
wrapped = -> { |html| div { raw html } }
|
394
401
|
wrapped.render("<h1>hi</h1>") #=> "<div><h1>hi</h1></div>"
|
395
402
|
```
|
396
403
|
|
397
|
-
## Emitting a String with HTML
|
404
|
+
## Emitting a String with HTML Escaping
|
398
405
|
|
399
|
-
To emit a string with proper HTML
|
406
|
+
To emit a string with proper HTML escaping, without wrapping it in an HTML
|
400
407
|
element, use `#text`:
|
401
408
|
|
402
409
|
```ruby
|
@@ -407,19 +414,19 @@ element, use `#text`:
|
|
407
414
|
|
408
415
|
Markdown is rendered using the
|
409
416
|
[Kramdown](https://kramdown.gettalong.org/index.html) gem. To emit Markdown, use
|
410
|
-
`#
|
417
|
+
`#markdown`:
|
411
418
|
|
412
419
|
```ruby
|
413
|
-
template = -> { |md| div {
|
420
|
+
template = -> { |md| div { markdown md } }
|
414
421
|
template.render("Here's some *Markdown*") #=> "<div><p>Here's some <em>Markdown</em><p>\n</div>"
|
415
422
|
```
|
416
423
|
|
417
424
|
[Kramdown
|
418
425
|
options](https://kramdown.gettalong.org/options.html#available-options) can be
|
419
|
-
specified by adding them to the `#
|
426
|
+
specified by adding them to the `#markdown` call:
|
420
427
|
|
421
428
|
```ruby
|
422
|
-
template = -> { |md| div {
|
429
|
+
template = -> { |md| div { markdown md, auto_ids: false } }
|
423
430
|
template.render("# title") #=> "<div><h1>title</h1></div>"
|
424
431
|
```
|
425
432
|
|
@@ -471,7 +478,7 @@ integrated into the page, and adds them to the page's `<head>` element:
|
|
471
478
|
default_layout = -> { |**args|
|
472
479
|
@dependencies = DependencyMananger.new
|
473
480
|
head {
|
474
|
-
defer {
|
481
|
+
defer { render @dependencies.head_markup }
|
475
482
|
}
|
476
483
|
body { emit_yield **args }
|
477
484
|
}
|
@@ -491,11 +498,11 @@ heading = proc { |text|
|
|
491
498
|
}
|
492
499
|
|
493
500
|
page = default_layout.apply {
|
494
|
-
|
501
|
+
render heading, "What's your favorite cheese?"
|
495
502
|
|
496
|
-
|
497
|
-
|
498
|
-
|
503
|
+
render button, 'Beaufort', 'eat_beaufort()'
|
504
|
+
render button, 'Mont d''or', 'eat_montdor()'
|
505
|
+
render button, 'Époisses', 'eat_epoisses()'
|
499
506
|
}
|
500
507
|
```
|
501
508
|
|
data/lib/p2/compiler.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'cgi'
|
4
4
|
require 'sirop'
|
5
|
-
require '
|
5
|
+
require 'erb/escape'
|
6
6
|
|
7
7
|
module P2
|
8
8
|
class TagNode
|
@@ -48,7 +48,7 @@ module P2
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
class
|
51
|
+
class RenderNode
|
52
52
|
attr_reader :call_node, :location, :block
|
53
53
|
|
54
54
|
include Prism::DSL
|
@@ -107,7 +107,7 @@ module P2
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def accept(visitor)
|
110
|
-
visitor.
|
110
|
+
visitor.visit_render_node(self)
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
@@ -124,6 +124,19 @@ module P2
|
|
124
124
|
end
|
125
125
|
end
|
126
126
|
|
127
|
+
class RawNode
|
128
|
+
attr_reader :call_node, :location
|
129
|
+
|
130
|
+
def initialize(call_node, _compiler)
|
131
|
+
@call_node = call_node
|
132
|
+
@location = call_node.location
|
133
|
+
end
|
134
|
+
|
135
|
+
def accept(visitor)
|
136
|
+
visitor.visit_raw_node(self)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
127
140
|
class DeferNode
|
128
141
|
attr_reader :call_node, :location, :block
|
129
142
|
|
@@ -172,13 +185,15 @@ module P2
|
|
172
185
|
)
|
173
186
|
when :raise
|
174
187
|
super(node)
|
175
|
-
when :
|
176
|
-
|
188
|
+
when :render
|
189
|
+
RenderNode.new(node, self)
|
190
|
+
when :raw
|
191
|
+
RawNode.new(node, self)
|
177
192
|
when :text
|
178
193
|
TextNode.new(node, self)
|
179
194
|
when :defer
|
180
195
|
DeferNode.new(node, self)
|
181
|
-
when :html5, :
|
196
|
+
when :html5, :markdown
|
182
197
|
CustomTagNode.new(node, self)
|
183
198
|
else
|
184
199
|
TagNode.new(node, self)
|
@@ -207,6 +222,10 @@ module P2
|
|
207
222
|
|
208
223
|
def self.compile(proc, wrap: true)
|
209
224
|
source_map, code = compile_to_code(proc, wrap:)
|
225
|
+
if ENV['DEBUG'] == '1'
|
226
|
+
puts '*' * 40
|
227
|
+
puts code
|
228
|
+
end
|
210
229
|
eval(code, proc.binding, source_map[:compiled_fn])
|
211
230
|
end
|
212
231
|
|
@@ -221,12 +240,12 @@ module P2
|
|
221
240
|
end
|
222
241
|
|
223
242
|
def with_source_map(orig_proc, orig_ast)
|
224
|
-
|
243
|
+
compiled_fn = "::(#{orig_proc.source_location.join(':')})"
|
225
244
|
@source_map = {
|
226
245
|
source_fn: orig_proc.source_location.first,
|
227
|
-
compiled_fn:
|
246
|
+
compiled_fn: compiled_fn
|
228
247
|
}
|
229
|
-
@source_map_line_ofs =
|
248
|
+
@source_map_line_ofs = 2
|
230
249
|
self
|
231
250
|
end
|
232
251
|
|
@@ -235,11 +254,12 @@ module P2
|
|
235
254
|
@binding = binding
|
236
255
|
visit(ast)
|
237
256
|
flush_html_parts!(semicolon_prefix: true)
|
257
|
+
update_source_map
|
238
258
|
|
239
259
|
source_code = @buffer
|
240
260
|
@buffer = +''
|
241
261
|
if wrap
|
242
|
-
emit("(#{@source_map.inspect}).then { |src_map| ->(__buffer__")
|
262
|
+
emit("# frozen_string_literal: true\n(#{@source_map.inspect}).then { |src_map| ->(__buffer__")
|
243
263
|
|
244
264
|
params = orig_ast.parameters
|
245
265
|
params = params&.parameters
|
@@ -291,13 +311,13 @@ module P2
|
|
291
311
|
|
292
312
|
if node.inner_text
|
293
313
|
if is_static_node?(node.inner_text)
|
294
|
-
emit_html(node.location,
|
314
|
+
emit_html(node.location, ERB::Escape.html_escape(format_literal(node.inner_text)))
|
295
315
|
else
|
296
316
|
convert_to_s = !is_string_type_node?(node.inner_text)
|
297
317
|
if convert_to_s
|
298
|
-
emit_html(node.location, "
|
318
|
+
emit_html(node.location, interpolated("ERB::Escape.html_escape((#{format_code(node.inner_text)}).to_s)"))
|
299
319
|
else
|
300
|
-
emit_html(node.location, "
|
320
|
+
emit_html(node.location, interpolated("ERB::Escape.html_escape(#{format_code(node.inner_text)})"))
|
301
321
|
end
|
302
322
|
end
|
303
323
|
end
|
@@ -319,34 +339,50 @@ module P2
|
|
319
339
|
emit(');')
|
320
340
|
end
|
321
341
|
|
322
|
-
def
|
342
|
+
def visit_render_node(node)
|
343
|
+
args = node.call_node.arguments.arguments
|
344
|
+
first_arg = args.first
|
345
|
+
|
346
|
+
block_embed = node.block && "&(->(__buffer__) #{format_code(node.block)}.compiled!)"
|
347
|
+
block_embed = ", #{block_embed}" if block_embed && node.call_node.arguments
|
348
|
+
|
349
|
+
flush_html_parts!
|
350
|
+
adjust_whitespace(node.location)
|
351
|
+
|
352
|
+
if args.length == 1
|
353
|
+
emit("; #{format_code(first_arg)}.compiled_proc.(__buffer__#{block_embed})")
|
354
|
+
else
|
355
|
+
args_code = format_code_comma_separated_nodes(args[1..])
|
356
|
+
emit("; #{format_code(first_arg)}.compiled_proc.(__buffer__, #{args_code}#{block_embed})")
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def visit_text_node(node)
|
361
|
+
return if !node.call_node.arguments
|
362
|
+
|
323
363
|
args = node.call_node.arguments.arguments
|
324
364
|
first_arg = args.first
|
325
365
|
if args.length == 1
|
326
366
|
if is_static_node?(first_arg)
|
327
|
-
emit_html(node.location, format_literal(first_arg))
|
328
|
-
elsif first_arg.is_a?(Prism::LambdaNode)
|
329
|
-
visit(first_arg.body)
|
367
|
+
emit_html(node.location, ERB::Escape.html_escape(format_literal(first_arg)))
|
330
368
|
else
|
331
|
-
emit_html(node.location, "
|
369
|
+
emit_html(node.location, interpolated("ERB::Escape.html_escape(#{format_code(first_arg)}.to_s)"))
|
332
370
|
end
|
333
371
|
else
|
334
|
-
|
335
|
-
block_embed = ", #{block_embed}" if block_embed && node.call_node.arguments
|
336
|
-
emit_html(node.location, "#\{P2.render_emit_call(#{format_code(node.call_node.arguments)}#{block_embed})}")
|
372
|
+
raise "Don't know how to compile #{node}"
|
337
373
|
end
|
338
374
|
end
|
339
375
|
|
340
|
-
def
|
376
|
+
def visit_raw_node(node)
|
341
377
|
return if !node.call_node.arguments
|
342
378
|
|
343
379
|
args = node.call_node.arguments.arguments
|
344
380
|
first_arg = args.first
|
345
381
|
if args.length == 1
|
346
382
|
if is_static_node?(first_arg)
|
347
|
-
emit_html(node.location,
|
383
|
+
emit_html(node.location, format_literal(first_arg))
|
348
384
|
else
|
349
|
-
emit_html(node.location, "
|
385
|
+
emit_html(node.location, interpolated("(#{format_code(first_arg)}).to_s"))
|
350
386
|
end
|
351
387
|
else
|
352
388
|
raise "Don't know how to compile #{node}"
|
@@ -381,11 +417,11 @@ module P2
|
|
381
417
|
emit_html(node.location, '<!DOCTYPE html><html>')
|
382
418
|
visit(node.block.body) if node.block
|
383
419
|
emit_html(node.block.closing_loc, '</html>')
|
384
|
-
when :
|
420
|
+
when :markdown
|
385
421
|
args = node.call_node.arguments
|
386
422
|
return if !args
|
387
423
|
|
388
|
-
emit_html(node.location, "
|
424
|
+
emit_html(node.location, interpolated("P2.markdown(#{format_code(args)})"))
|
389
425
|
end
|
390
426
|
end
|
391
427
|
|
@@ -403,10 +439,20 @@ module P2
|
|
403
439
|
|
404
440
|
private
|
405
441
|
|
406
|
-
def
|
442
|
+
def interpolated(str)
|
443
|
+
"#\{#{str}}"
|
444
|
+
end
|
445
|
+
|
446
|
+
def format_code(node, klass = self.class)
|
407
447
|
klass.new(minimize_whitespace: true).to_source(node)
|
408
448
|
end
|
409
449
|
|
450
|
+
def format_code_comma_separated_nodes(list)
|
451
|
+
compiler = self.class.new(minimize_whitespace: true)
|
452
|
+
compiler.visit_comma_separated_nodes(list)
|
453
|
+
compiler.buffer
|
454
|
+
end
|
455
|
+
|
410
456
|
VOID_TAGS = %w(area base br col embed hr img input link meta param source track wbr)
|
411
457
|
|
412
458
|
def is_void_element?(tag)
|
@@ -487,7 +533,7 @@ module P2
|
|
487
533
|
!is_static_node?(it.key) || !is_static_node?(it.value)
|
488
534
|
end
|
489
535
|
|
490
|
-
return "
|
536
|
+
return interpolated("P2.format_html_attrs(#{format_code(node)})") if dynamic_attributes
|
491
537
|
|
492
538
|
parts = elements.map do
|
493
539
|
key = it.key
|
@@ -521,11 +567,20 @@ module P2
|
|
521
567
|
return if @pending_html_parts.empty?
|
522
568
|
|
523
569
|
adjust_whitespace(@html_loc_start)
|
524
|
-
|
525
|
-
|
570
|
+
|
571
|
+
code = +''
|
572
|
+
part = +''
|
573
|
+
|
574
|
+
@pending_html_parts.each do
|
575
|
+
if (m = it.match(/^#\{(.+)\}$/m))
|
576
|
+
emit_buffer_push(code, part, quotes: true) if !part.empty?
|
577
|
+
emit_buffer_push(code, m[1])
|
578
|
+
else
|
579
|
+
part << it
|
580
|
+
end
|
526
581
|
end
|
582
|
+
emit_buffer_push(code, part, quotes: true) if !part.empty?
|
527
583
|
|
528
|
-
str = @pending_html_parts.join
|
529
584
|
@pending_html_parts.clear
|
530
585
|
|
531
586
|
@last_loc = @html_loc_end
|
@@ -535,7 +590,15 @@ module P2
|
|
535
590
|
@html_loc_start = nil
|
536
591
|
@html_loc_end = nil
|
537
592
|
|
538
|
-
emit
|
593
|
+
emit code
|
594
|
+
end
|
595
|
+
|
596
|
+
def emit_buffer_push(buf, part, quotes: false)
|
597
|
+
return if part.empty?
|
598
|
+
|
599
|
+
q = quotes ? '"' : ''
|
600
|
+
buf << "; __buffer__ << #{q}#{part}#{q}"
|
601
|
+
part.clear
|
539
602
|
end
|
540
603
|
|
541
604
|
def emit_postlude
|
data/lib/p2/proc_ext.rb
CHANGED
@@ -5,13 +5,15 @@ require_relative './compiler'
|
|
5
5
|
# Extensions to the Proc class
|
6
6
|
class ::Proc
|
7
7
|
def compiled_code
|
8
|
-
P2::TemplateCompiler.compile_to_code(self)
|
8
|
+
P2::TemplateCompiler.compile_to_code(self).last
|
9
9
|
end
|
10
10
|
|
11
11
|
def compiled?
|
12
12
|
@is_compiled
|
13
13
|
end
|
14
14
|
|
15
|
+
# marks the proc as compiled, i.e. can render directly and takes a string
|
16
|
+
# buffer as first argument
|
15
17
|
def compiled!
|
16
18
|
@is_compiled = true
|
17
19
|
self
|
@@ -22,7 +24,7 @@ class ::Proc
|
|
22
24
|
end
|
23
25
|
|
24
26
|
def compile
|
25
|
-
P2.compile(self).compiled!
|
27
|
+
P2::TemplateCompiler.compile(self).compiled!
|
26
28
|
rescue Sirop::Error
|
27
29
|
uncompiled_renderer
|
28
30
|
end
|
data/lib/p2/version.rb
CHANGED
data/lib/p2.rb
CHANGED
@@ -8,94 +8,84 @@ module P2
|
|
8
8
|
# Exception class used to signal templating-related errors
|
9
9
|
class Error < RuntimeError; end
|
10
10
|
|
11
|
-
|
12
|
-
def compile(proc)
|
13
|
-
P2::TemplateCompiler.compile(proc)
|
14
|
-
end
|
11
|
+
extend self
|
15
12
|
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
def format_tag(tag)
|
14
|
+
tag.to_s.gsub('_', '-')
|
15
|
+
end
|
19
16
|
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
def format_html_attr_key(tag)
|
18
|
+
tag.to_s.tr('_', '-')
|
19
|
+
end
|
23
20
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
21
|
+
def format_html_attrs(attrs)
|
22
|
+
attrs.each_with_object(+'') do |(k, v), html|
|
23
|
+
case v
|
24
|
+
when nil, false
|
25
|
+
when true
|
26
|
+
html << ' ' if !html.empty?
|
27
|
+
html << format_html_attr_key(k)
|
28
|
+
else
|
29
|
+
html << ' ' if !html.empty?
|
30
|
+
v = v.join(' ') if v.is_a?(Array)
|
31
|
+
html << "#{format_html_attr_key(k)}=\"#{v}\""
|
36
32
|
end
|
37
33
|
end
|
34
|
+
end
|
38
35
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
36
|
+
def translate_backtrace(e, source_map)
|
37
|
+
re = compute_source_map_re(source_map)
|
38
|
+
source_fn = source_map[:source_fn]
|
39
|
+
backtrace = e.backtrace.map {
|
40
|
+
if (m = it.match(re))
|
41
|
+
line = m[2].to_i
|
42
|
+
source_line = source_map[line] || "?(#{line})"
|
43
|
+
it.sub(m[1], "#{source_fn}:#{source_line}")
|
45
44
|
else
|
46
|
-
|
45
|
+
it
|
47
46
|
end
|
48
|
-
|
47
|
+
}
|
48
|
+
e.set_backtrace(backtrace)
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
if (m = it.match(re))
|
55
|
-
line = m[2].to_i
|
56
|
-
source_line = source_map[line] || "?(#{line})"
|
57
|
-
it.sub(m[1], "#{source_fn}:#{source_line}")
|
58
|
-
else
|
59
|
-
it
|
60
|
-
end
|
61
|
-
}
|
62
|
-
e.set_backtrace(backtrace)
|
63
|
-
end
|
51
|
+
def compute_source_map_re(source_map)
|
52
|
+
escaped = source_map[:compiled_fn].gsub(/[\(\)]/) { "\\#{it[0]}" }
|
53
|
+
/^(#{escaped}\:(\d+))/
|
54
|
+
end
|
64
55
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
56
|
+
# Renders Markdown into HTML. The `opts` argument will be merged with the
|
57
|
+
# default Kramdown options in order to change the rendering behaviour.
|
58
|
+
#
|
59
|
+
# @param markdown [String] Markdown
|
60
|
+
# @param opts [Hash] Kramdown option overrides
|
61
|
+
# @return [String] HTML
|
62
|
+
def markdown(markdown, **opts)
|
63
|
+
# require relevant deps on use
|
64
|
+
require 'kramdown'
|
65
|
+
require 'rouge'
|
66
|
+
require 'kramdown-parser-gfm'
|
67
|
+
|
68
|
+
opts = default_kramdown_options.merge(opts)
|
69
|
+
Kramdown::Document.new(markdown, **opts).to_html
|
70
|
+
end
|
80
71
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
72
|
+
# Returns the default Kramdown options used for rendering Markdown.
|
73
|
+
#
|
74
|
+
# @return [Hash] Kramdown options
|
75
|
+
def default_kramdown_options
|
76
|
+
@default_kramdown_options ||= {
|
77
|
+
entity_output: :numeric,
|
78
|
+
syntax_highlighter: :rouge,
|
79
|
+
input: 'GFM',
|
80
|
+
hard_wrap: false
|
81
|
+
}
|
82
|
+
end
|
92
83
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
84
|
+
# Sets the default Kramdown options used for rendering Markdown.
|
85
|
+
#
|
86
|
+
# @param opts [Hash] Kramdown options
|
87
|
+
# @return [void]
|
88
|
+
def default_kramdown_options=(opts)
|
89
|
+
@default_kramdown_options = opts
|
100
90
|
end
|
101
91
|
end
|
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.1'
|
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.
|
18
|
+
version: 0.8.3
|
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.
|
25
|
+
version: 0.8.3
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: kramdown
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -111,9 +111,8 @@ homepage: http://github.com/digital-fabric/p2
|
|
111
111
|
licenses:
|
112
112
|
- MIT
|
113
113
|
metadata:
|
114
|
-
source_code_uri: https://github.com/digital-fabric/p2
|
115
|
-
documentation_uri: https://www.rubydoc.info/gems/p2
|
116
114
|
homepage_uri: https://github.com/digital-fabric/p2
|
115
|
+
documentation_uri: https://www.rubydoc.info/gems/p2
|
117
116
|
changelog_uri: https://github.com/digital-fabric/p2/blob/master/CHANGELOG.md
|
118
117
|
rdoc_options:
|
119
118
|
- "--title"
|