papercraft 1.3 → 2.13
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 +93 -0
- data/README.md +246 -617
- data/lib/papercraft/compiler/nodes.rb +223 -0
- data/lib/papercraft/compiler/tag_translator.rb +93 -0
- data/lib/papercraft/compiler.rb +657 -201
- data/lib/papercraft/proc_ext.rb +118 -0
- data/lib/papercraft/template.rb +16 -195
- data/lib/papercraft/version.rb +1 -1
- data/lib/papercraft.rb +113 -89
- metadata +11 -64
- data/lib/papercraft/compiler_old.rb +0 -701
- data/lib/papercraft/extension_proxy.rb +0 -41
- data/lib/papercraft/extensions/soap.rb +0 -42
- data/lib/papercraft/html.rb +0 -173
- data/lib/papercraft/json.rb +0 -128
- data/lib/papercraft/renderer.rb +0 -190
- data/lib/papercraft/tags.rb +0 -408
- data/lib/papercraft/xml.rb +0 -47
- data/lib/tilt/papercraft.rb +0 -25
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './compiler'
|
4
|
+
|
5
|
+
# Extensions to the Proc class.
|
6
|
+
class ::Proc
|
7
|
+
# Returns the compiled form code for the proc.
|
8
|
+
#
|
9
|
+
# @return [String] compiled proc code
|
10
|
+
def compiled_code
|
11
|
+
Papercraft::Compiler.compile_to_code(self).last
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the source map for the compiled proc.
|
15
|
+
#
|
16
|
+
# @return [Array<String>] source map
|
17
|
+
def source_map
|
18
|
+
loc = source_location
|
19
|
+
fn = compiled? ? loc.first : Papercraft::Compiler.source_location_to_fn(loc)
|
20
|
+
Papercraft::Compiler.source_map_store[fn]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the AST for the proc.
|
24
|
+
#
|
25
|
+
# @return [Prism::Node] AST root
|
26
|
+
def ast
|
27
|
+
Sirop.to_ast(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns true if proc is marked as compiled.
|
31
|
+
#
|
32
|
+
# @return [bool] is the proc marked as compiled
|
33
|
+
def compiled?
|
34
|
+
@is_compiled
|
35
|
+
end
|
36
|
+
|
37
|
+
# Marks the proc as compiled, i.e. can render directly and takes a string
|
38
|
+
# buffer as first argument.
|
39
|
+
#
|
40
|
+
# @return [self]
|
41
|
+
def compiled!
|
42
|
+
@is_compiled = true
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the compiled proc for the given proc. If marked as compiled, returns
|
47
|
+
# self.
|
48
|
+
#
|
49
|
+
# @param mode [Symbol] compilation mode (:html, :xml)
|
50
|
+
# @return [Proc] compiled proc or self
|
51
|
+
def compiled_proc(mode: :html)
|
52
|
+
@compiled_proc ||= @is_compiled ? self : compile(mode:)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Compiles the proc into the compiled form.
|
56
|
+
#
|
57
|
+
# @param mode [Symbol] compilation mode (:html, :xml)
|
58
|
+
# @return [Proc] compiled proc
|
59
|
+
def compile(mode: :html)
|
60
|
+
Papercraft::Compiler.compile(self, mode:).compiled!
|
61
|
+
rescue Sirop::Error
|
62
|
+
raise Papercraft::Error, "Dynamically defined procs cannot be compiled"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Renders the proc to HTML with the given arguments.
|
66
|
+
#
|
67
|
+
# @return [String] HTML string
|
68
|
+
def render(*a, **b, &c)
|
69
|
+
compiled_proc.(+'', *a, **b, &c)
|
70
|
+
rescue Exception => e
|
71
|
+
e.is_a?(Papercraft::Error) ? raise : raise(Papercraft.translate_backtrace(e))
|
72
|
+
end
|
73
|
+
|
74
|
+
# Renders the proc to XML with the given arguments.
|
75
|
+
#
|
76
|
+
# @return [String] XML string
|
77
|
+
def render_xml(*a, **b, &c)
|
78
|
+
compiled_proc(mode: :xml).(+'', *a, **b, &c)
|
79
|
+
rescue Exception => e
|
80
|
+
e.is_a?(Papercraft::Error) ? raise : raise(Papercraft.translate_backtrace(e))
|
81
|
+
end
|
82
|
+
|
83
|
+
# Renders the proc to HTML with the given arguments into the given buffer.
|
84
|
+
#
|
85
|
+
# @param buf [String] buffer
|
86
|
+
# @return [String] HTML string
|
87
|
+
def render_to_buffer(buf, *a, **b, &c)
|
88
|
+
compiled_proc.(buf, *a, **b, &c)
|
89
|
+
rescue Exception => e
|
90
|
+
raise Papercraft.translate_backtrace(e)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns a proc that applies the given arguments to the original proc.
|
94
|
+
#
|
95
|
+
# @return [Proc] applied proc
|
96
|
+
def apply(*a, **b, &c)
|
97
|
+
compiled = compiled_proc
|
98
|
+
c_compiled = c&.compiled_proc
|
99
|
+
|
100
|
+
->(__buffer__, *x, **y, &z) {
|
101
|
+
c_proc = c_compiled && ->(__buffer__, *d, **e) {
|
102
|
+
c_compiled.(__buffer__, *a, *d, **b, **e, &z)
|
103
|
+
}.compiled!
|
104
|
+
|
105
|
+
compiled.(__buffer__, *a, *x, **b, **y, &c_proc)
|
106
|
+
}.compiled!
|
107
|
+
end
|
108
|
+
|
109
|
+
# Caches and returns the rendered HTML for the template with the given
|
110
|
+
# arguments.
|
111
|
+
#
|
112
|
+
# @return [String] HTML string
|
113
|
+
def render_cached(*args, **kargs, &block)
|
114
|
+
@render_cache ||= {}
|
115
|
+
key = args.empty? && kargs.empty? && !block ? nil : [args, kargs, block&.source_location]
|
116
|
+
@render_cache[key] ||= render(*args, **kargs, &block)
|
117
|
+
end
|
118
|
+
end
|
data/lib/papercraft/template.rb
CHANGED
@@ -1,207 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative './html'
|
4
|
-
|
5
3
|
module Papercraft
|
6
|
-
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# `json`, for HTML, XML or JSON templates, respectively:
|
16
|
-
#
|
17
|
-
# greeter = Papercraft.html { |name| h1 "Hello, #{name}!" }
|
18
|
-
# greeter.render('world') #=> "<h1>Hello, world!</h1>"
|
19
|
-
#
|
20
|
-
# Templates can also be created using the normal constructor:
|
21
|
-
#
|
22
|
-
# greeter = Papercraft::Template.new(mode: :html) { |name| h1 "Hello, #{name}!" }
|
23
|
-
# greeter.render('world') #=> "<h1>Hello, world!</h1>"
|
24
|
-
#
|
25
|
-
# The different methods for creating templates can also take a custom MIME
|
26
|
-
# type, by passing a `mime_type` named argument:
|
27
|
-
#
|
28
|
-
# json = Papercraft.json(mime_type: 'application/feed+json') { ... }
|
29
|
-
#
|
30
|
-
# In the template block, HTML elements are created by simply calling
|
31
|
-
# unqualified methods:
|
32
|
-
#
|
33
|
-
# page_layout = Papercraft.html {
|
34
|
-
# html5 {
|
35
|
-
# head {
|
36
|
-
# title 'foo'
|
37
|
-
# }
|
38
|
-
# body {
|
39
|
-
# h1 "Hello, world!"
|
40
|
-
# }
|
41
|
-
# }
|
42
|
-
# }
|
43
|
-
#
|
44
|
-
# Papercraft templates can take explicit parameters in order to render
|
45
|
-
# dynamic content. This can be in the form of regular or named parameters. The
|
46
|
-
# `greeter` template shown above takes a single `name` parameter. Here's how a
|
47
|
-
# anchor template could be implemented with named parameters:
|
48
|
-
#
|
49
|
-
# anchor = Papercraft.html { |uri: , text: | a(text, href: uri) }
|
50
|
-
#
|
51
|
-
# The above template could later be rendered by passing the needed arguments:
|
52
|
-
#
|
53
|
-
# anchor.render(uri: 'https://example.com', text: 'Example')
|
54
|
-
#
|
55
|
-
# ## Template Composition
|
56
|
-
#
|
57
|
-
# A template can be included in another template using the `emit` method:
|
58
|
-
#
|
59
|
-
# links = Papercraft.html {
|
60
|
-
# emit anchor, uri: '/posts', text: 'Posts'
|
61
|
-
# emit anchor, uri: '/archive', text: 'Archive'
|
62
|
-
# emit anchor, uri: '/about', text: 'About'
|
63
|
-
# }
|
64
|
-
#
|
65
|
-
# Another way of composing templates is to pass the templates themselves as
|
66
|
-
# parameters:
|
67
|
-
#
|
68
|
-
# links = Papercraft.html { |anchors|
|
69
|
-
# anchors.each { |a| emit a }
|
70
|
-
# }
|
71
|
-
# links.render([
|
72
|
-
# anchor.apply(uri: '/posts', text: 'Posts'),
|
73
|
-
# anchor.apply(uri: '/archive', text: 'Archive'),
|
74
|
-
# anchor.apply(uri: '/about', text: 'About')
|
75
|
-
# ])
|
76
|
-
#
|
77
|
-
# The `#apply` method creates a new template, applying the given parameters
|
78
|
-
# such that the template can be rendered without parameters:
|
79
|
-
#
|
80
|
-
# links_with_anchors = links.apply([
|
81
|
-
# anchor.apply(uri: '/posts', text: 'Posts'),
|
82
|
-
# anchor.apply(uri: '/archive', text: 'Archive'),
|
83
|
-
# anchor.apply(uri: '/about', text: 'About')
|
84
|
-
# ])
|
85
|
-
# links_with_anchors.render
|
86
|
-
#
|
87
|
-
class Template < Proc
|
88
|
-
|
89
|
-
# Determines the rendering mode: `:html` or `:xml`.
|
90
|
-
attr_accessor :mode
|
91
|
-
|
92
|
-
STOCK_MIME_TYPE = {
|
93
|
-
html: 'text/html',
|
94
|
-
xml: 'application/xml',
|
95
|
-
json: 'application/json'
|
96
|
-
}.freeze
|
97
|
-
|
98
|
-
# Initializes a template with the given block. The rendering mode (HTML or
|
99
|
-
# XML) can be passed in the `mode:` parameter. If `mode:` is not specified,
|
100
|
-
# the template defaults to HTML.
|
101
|
-
#
|
102
|
-
# @param mode [:html, :xml] rendering mode
|
103
|
-
# @param mime_type [String, nil] the template's mime type (nil for default)
|
104
|
-
# @param block [Proc] nested HTML block
|
105
|
-
def initialize(mode: :html, mime_type: nil, &block)
|
4
|
+
# Template wrapper class. This class can be used to distinguish between Papercraft
|
5
|
+
# templates and other kinds of procs.
|
6
|
+
class Template
|
7
|
+
attr_reader :proc, :mode
|
8
|
+
|
9
|
+
# @param proc [Proc] template proc
|
10
|
+
# @param mode [Symbol] mode (:html, :xml)
|
11
|
+
def initialize(proc, mode: :html)
|
12
|
+
@proc = proc
|
106
13
|
@mode = mode
|
107
|
-
@mime_type = mime_type || STOCK_MIME_TYPE[mode]
|
108
|
-
super(&block)
|
109
14
|
end
|
110
15
|
|
111
|
-
|
112
|
-
|
113
|
-
# Renders the template with the given parameters and or block, and returns
|
114
|
-
# the string result.
|
115
|
-
#
|
116
|
-
# @param *params [any] unnamed parameters
|
117
|
-
# @param **named_params [any] named parameters
|
118
|
-
# @return [String] rendered string
|
119
|
-
def render(*a, **b, &block)
|
120
|
-
template = self
|
121
|
-
Renderer.verify_proc_parameters(template, a, b)
|
122
|
-
renderer_class.new do
|
123
|
-
push_emit_yield_block(block) if block
|
124
|
-
instance_exec(*a, **b, &template)
|
125
|
-
end.to_s
|
16
|
+
def render(*, **, &)
|
17
|
+
(mode == :xml) ? @proc.render_xml(*, **, &) : @proc.render(*, **, &)
|
126
18
|
end
|
127
19
|
|
128
|
-
|
129
|
-
|
130
|
-
# {https://htmx.org/essays/template-fragments/ HTMX template fragments}.
|
131
|
-
#
|
132
|
-
# form = Papercraft.html { |action|
|
133
|
-
# h1 'Hello'
|
134
|
-
# fragment(:buttons) {
|
135
|
-
# button action
|
136
|
-
# button 'Cancel'
|
137
|
-
# }
|
138
|
-
# }
|
139
|
-
# form.render_fragment(:buttons, 'foo') #=> "<button>foo</button><button>Cancel</buttons>"
|
140
|
-
#
|
141
|
-
# @param name [Symbol, String] fragment name
|
142
|
-
# @param *params [any] unnamed parameters
|
143
|
-
# @param **named_params [any] named parameters
|
144
|
-
# @return [String] rendered string
|
145
|
-
def render_fragment(name, *a, **b, &block)
|
146
|
-
template = self
|
147
|
-
Renderer.verify_proc_parameters(template, a, b)
|
148
|
-
renderer_class.new(name) do
|
149
|
-
push_emit_yield_block(block) if block
|
150
|
-
instance_exec(*a, **b, &template)
|
151
|
-
end.to_s
|
20
|
+
def apply(*, **, &)
|
21
|
+
Template.new(@proc.apply(*, **, &), mode: @mode)
|
152
22
|
end
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
# templates, particularly when passing inner templates as blocks:
|
157
|
-
#
|
158
|
-
# article_wrapper = Papercraft.html {
|
159
|
-
# article {
|
160
|
-
# emit_yield
|
161
|
-
# }
|
162
|
-
# }
|
163
|
-
# wrapped_article = article_wrapper.apply {
|
164
|
-
# h1 'Article title'
|
165
|
-
# }
|
166
|
-
# wrapped_article.render #=> "<article><h1>Article title</h1></article>"
|
167
|
-
#
|
168
|
-
# @param *a [<any>] normal parameters
|
169
|
-
# @param **b [Hash] named parameters
|
170
|
-
# @return [Papercraft::Template] applied template
|
171
|
-
def apply(*a, **b, &block)
|
172
|
-
template = self
|
173
|
-
Template.new(mode: @mode, mime_type: @mime_type, &proc do |*x, **y|
|
174
|
-
push_emit_yield_block(block) if block
|
175
|
-
instance_exec(*a, *x, **b, **y, &template)
|
176
|
-
end)
|
177
|
-
end
|
178
|
-
|
179
|
-
# Returns the Renderer class used for rendering the templates, according to
|
180
|
-
# the template's mode.
|
181
|
-
#
|
182
|
-
# @return [Papercraft::Renderer] Renderer used for rendering the template
|
183
|
-
def renderer_class
|
184
|
-
case @mode
|
185
|
-
when :html
|
186
|
-
HTMLRenderer
|
187
|
-
when :xml
|
188
|
-
XMLRenderer
|
189
|
-
when :json
|
190
|
-
JSONRenderer
|
191
|
-
else
|
192
|
-
raise "Invalid mode #{@mode.inspect}"
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
# Returns the template's associated MIME type.
|
197
|
-
#
|
198
|
-
# @return [String] MIME type
|
199
|
-
def mime_type
|
200
|
-
@mime_type
|
201
|
-
end
|
202
|
-
|
203
|
-
def compile(*args)
|
204
|
-
Papercraft::Compiler.new.compile(self, *args)
|
23
|
+
|
24
|
+
def compiled_proc
|
25
|
+
@proc.compiled_proc(mode: @mode)
|
205
26
|
end
|
206
27
|
end
|
207
28
|
end
|
data/lib/papercraft/version.rb
CHANGED
data/lib/papercraft.rb
CHANGED
@@ -1,109 +1,133 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'kramdown'
|
4
|
-
require 'rouge'
|
5
|
-
require 'kramdown-parser-gfm'
|
6
|
-
|
7
3
|
require_relative 'papercraft/template'
|
8
|
-
require_relative 'papercraft/renderer'
|
9
4
|
require_relative 'papercraft/compiler'
|
5
|
+
require_relative 'papercraft/proc_ext'
|
10
6
|
|
11
|
-
|
12
|
-
#
|
7
|
+
# Papercraft is a functional templating library. In Papercraft, templates are expressed as plain
|
8
|
+
# Ruby procs.
|
13
9
|
module Papercraft
|
14
10
|
# Exception class used to signal templating-related errors
|
15
11
|
class Error < RuntimeError; end
|
16
12
|
|
17
|
-
|
18
|
-
|
19
|
-
# Installs one or more extensions. Extensions enhance templating capabilities
|
20
|
-
# by adding namespaced methods to emplates. An extension is implemented as a
|
21
|
-
# Ruby module containing one or more methods. Each method in the extension
|
22
|
-
# module can be used to render a specific HTML element or a set of elements.
|
23
|
-
#
|
24
|
-
# This is a convenience method. For more information on using Papercraft
|
25
|
-
# extensions, see `Papercraft::Renderer::extension`
|
26
|
-
#
|
27
|
-
# @param map [Hash] hash mapping methods to extension modules
|
28
|
-
# @return [void]
|
29
|
-
def extension(map)
|
30
|
-
Renderer.extension(map)
|
31
|
-
end
|
13
|
+
extend self
|
32
14
|
|
33
|
-
|
34
|
-
|
35
|
-
# `Papercraft::Template`.
|
36
|
-
#
|
37
|
-
# Papercraft.html(proc { h1 'hi' }).render #=> "<h1>hi</h1>"
|
38
|
-
# Papercraft.html { h1 'hi' }.render #=> "<h1>hi</h1>"
|
39
|
-
#
|
40
|
-
# @param template [Proc] template block
|
41
|
-
# @return [Papercraft::Template] Papercraft template
|
42
|
-
def html(o = nil, mime_type: nil, &template)
|
43
|
-
return o if o.is_a?(Papercraft::Template)
|
44
|
-
template ||= o
|
45
|
-
Papercraft::Template.new(mode: :html, mime_type: mime_type, &template)
|
46
|
-
end
|
15
|
+
# Registry of Papercraft exgtensions
|
16
|
+
Extensions = {}
|
47
17
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
# @return [Papercraft::Template] Papercraft template
|
57
|
-
def xml(o = nil, mime_type: nil, &template)
|
58
|
-
return o if o.is_a?(Papercraft::Template)
|
59
|
-
template ||= o
|
60
|
-
Papercraft::Template.new(mode: :xml, mime_type: mime_type, &template)
|
61
|
-
end
|
18
|
+
# Registers extensions to the Papercraft 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
|
62
26
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
# @param template [Proc] template block
|
71
|
-
# @return [Papercraft::Template] Papercraft template
|
72
|
-
def json(o = nil, mime_type: nil, &template)
|
73
|
-
return o if o.is_a?(Papercraft::Template)
|
74
|
-
template ||= o
|
75
|
-
Papercraft::Template.new(mode: :json, mime_type: mime_type, &template)
|
76
|
-
end
|
27
|
+
# Formats the given string, converting underscores to dashes.
|
28
|
+
#
|
29
|
+
# @param tag [String, Symbol] input string
|
30
|
+
# @return [String] output string
|
31
|
+
def underscores_to_dashes(tag)
|
32
|
+
tag.to_s.gsub('_', '-')
|
33
|
+
end
|
77
34
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
35
|
+
# Formats the given hash as tag attributes.
|
36
|
+
#
|
37
|
+
# @param attrs [Hash] input hash
|
38
|
+
# @return [String] formatted attributes
|
39
|
+
def format_tag_attrs(attrs)
|
40
|
+
attrs.each_with_object(+'') do |(k, v), html|
|
41
|
+
case v
|
42
|
+
when nil, false
|
43
|
+
when true
|
44
|
+
html << ' ' if !html.empty?
|
45
|
+
html << underscores_to_dashes(k)
|
46
|
+
else
|
47
|
+
html << ' ' if !html.empty?
|
48
|
+
v = v.join(' ') if v.is_a?(Array)
|
49
|
+
html << "#{underscores_to_dashes(k)}=\"#{v}\""
|
50
|
+
end
|
87
51
|
end
|
52
|
+
end
|
88
53
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
54
|
+
# Translates entries in exception's backtrace to point to original source code.
|
55
|
+
#
|
56
|
+
# @param err [Exception] raised exception
|
57
|
+
# @return [Exception] raised exception
|
58
|
+
def translate_backtrace(err)
|
59
|
+
cache = {}
|
60
|
+
is_argument_error = err.is_a?(ArgumentError) && err.backtrace[0] =~ /^\:\:/
|
61
|
+
backtrace = err.backtrace.map { |e| compute_backtrace_entry(e, cache) }
|
62
|
+
|
63
|
+
return make_argument_error(err, backtrace) if is_argument_error
|
64
|
+
|
65
|
+
err.set_backtrace(backtrace)
|
66
|
+
err
|
67
|
+
end
|
68
|
+
|
69
|
+
# Computes a backtrace entry with caching.
|
70
|
+
#
|
71
|
+
# @param entry [String] backtrace entry
|
72
|
+
# @param cache [Hash] cache store mapping compiled filename to source_map
|
73
|
+
def compute_backtrace_entry(entry, cache)
|
74
|
+
m = entry.match(/^((\:\:\(.+\:.+\))\:(\d+))/)
|
75
|
+
return entry if !m
|
76
|
+
|
77
|
+
fn = m[2]
|
78
|
+
line = m[3].to_i
|
79
|
+
source_map = cache[fn] ||= Compiler.source_map_store[fn]
|
80
|
+
return entry if !source_map
|
81
|
+
|
82
|
+
ref = source_map[line] || "?(#{line})"
|
83
|
+
entry.sub(m[1], ref)
|
84
|
+
end
|
85
|
+
|
86
|
+
def make_argument_error(err, backtrace)
|
87
|
+
m = err.message.match(/(given (\d+), expected (\d+))/)
|
88
|
+
if m
|
89
|
+
rectified = format('given %d, expected %d', m[2].to_i - 1, m[3].to_i - 1)
|
90
|
+
message = err.message.gsub(m[1], rectified)
|
91
|
+
else
|
92
|
+
message = err.message
|
99
93
|
end
|
94
|
+
ArgumentError.new(message).tap { it.set_backtrace(backtrace) }
|
95
|
+
end
|
100
96
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
97
|
+
# Renders Markdown into HTML. The `opts` argument will be merged with the
|
98
|
+
# default Kramdown options in order to change the rendering behaviour.
|
99
|
+
#
|
100
|
+
# @param markdown [String] Markdown
|
101
|
+
# @param opts [Hash] Kramdown option overrides
|
102
|
+
# @return [String] HTML
|
103
|
+
def markdown(markdown, **opts)
|
104
|
+
@markdown_deps_loaded ||= true.tap do
|
105
|
+
require 'kramdown'
|
106
|
+
require 'rouge'
|
107
|
+
require 'kramdown-parser-gfm'
|
107
108
|
end
|
109
|
+
|
110
|
+
opts = default_kramdown_options.merge(opts)
|
111
|
+
Kramdown::Document.new(markdown, **opts).to_html
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns the default Kramdown options used for rendering Markdown.
|
115
|
+
#
|
116
|
+
# @return [Hash] Kramdown options
|
117
|
+
def default_kramdown_options
|
118
|
+
@default_kramdown_options ||= {
|
119
|
+
entity_output: :numeric,
|
120
|
+
syntax_highlighter: :rouge,
|
121
|
+
input: 'GFM',
|
122
|
+
hard_wrap: false
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
# Sets the default Kramdown options used for rendering Markdown.
|
127
|
+
#
|
128
|
+
# @param opts [Hash] Kramdown options
|
129
|
+
# @return [Hash] Kramdown options
|
130
|
+
def default_kramdown_options=(opts)
|
131
|
+
@default_kramdown_options = opts
|
108
132
|
end
|
109
133
|
end
|