/
renderer.rb
265 lines (229 loc) · 7.67 KB
/
renderer.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# frozen_string_literal: true
module Jekyll
class Renderer
attr_reader :document, :site
attr_writer :layouts, :payload
def initialize(site, document, site_payload = nil)
@site = site
@document = document
@payload = site_payload
end
# Fetches the payload used in Liquid rendering.
# It can be written with #payload=(new_payload)
# Falls back to site.site_payload if no payload is set.
#
# Returns a Jekyll::Drops::UnifiedPayloadDrop
def payload
@payload ||= site.site_payload
end
# The list of layouts registered for this Renderer.
# It can be written with #layouts=(new_layouts)
# Falls back to site.layouts if no layouts are registered.
#
# Returns a Hash of String => Jekyll::Layout identified
# as basename without the extension name.
def layouts
@layouts || site.layouts
end
# Determine which converters to use based on this document's
# extension.
#
# Returns Array of Converter instances.
def converters
@converters ||= site.converters.select { |c| c.matches(document.extname) }.sort
end
# Determine the extname the outputted file should have
#
# Returns String the output extname including the leading period.
def output_ext
@output_ext ||= (permalink_ext || converter_output_ext)
end
# Prepare payload and render the document
#
# Returns String rendered document output
def run
Jekyll.logger.debug "Rendering:", document.relative_path
assign_pages!
assign_current_document!
assign_highlighter_options!
assign_layout_data!
Jekyll.logger.debug "Pre-Render Hooks:", document.relative_path
document.trigger_hooks(:pre_render, payload)
render_document
end
# Render the document.
#
# Returns String rendered document output
# rubocop: disable AbcSize
def render_document
info = {
:registers => { :site => site, :page => payload["page"] },
:strict_filters => liquid_options["strict_filters"],
:strict_variables => liquid_options["strict_variables"],
}
output = document.content
if document.render_with_liquid?
Jekyll.logger.debug "Rendering Liquid:", document.relative_path
output = render_liquid(output, payload, info, document.path)
end
Jekyll.logger.debug "Rendering Markup:", document.relative_path
output = convert(output.to_s)
document.content = output
if document.place_in_layout?
Jekyll.logger.debug "Rendering Layout:", document.relative_path
output = place_in_layouts(output, payload, info)
end
output
end
# rubocop: enable AbcSize
# Convert the document using the converters which match this renderer's document.
#
# Returns String the converted content.
def convert(content)
converters.reduce(content) do |output, converter|
begin
converter.convert output
rescue StandardError => e
Jekyll.logger.error "Conversion error:",
"#{converter.class} encountered an error while "\
"converting '#{document.relative_path}':"
Jekyll.logger.error("", e.to_s)
raise e
end
end
end
# Render the given content with the payload and info
#
# content -
# payload -
# info -
# path - (optional) the path to the file, for use in ex
#
# Returns String the content, rendered by Liquid.
def render_liquid(content, payload, info, path = nil)
template = site.liquid_renderer.file(path).parse(content)
template.warnings.each do |e|
Jekyll.logger.warn "Liquid Warning:",
LiquidRenderer.format_error(e, path || document.relative_path)
end
template.render!(payload, info)
# rubocop: disable RescueException
rescue Exception => e
Jekyll.logger.error "Liquid Exception:",
LiquidRenderer.format_error(e, path || document.relative_path)
raise e
end
# rubocop: enable RescueException
# Checks if the layout specified in the document actually exists
#
# layout - the layout to check
#
# Returns Boolean true if the layout is invalid, false if otherwise
def invalid_layout?(layout)
!document.data["layout"].nil? && layout.nil? && !(document.is_a? Jekyll::Excerpt)
end
# Render layouts and place document content inside.
#
# Returns String rendered content
def place_in_layouts(content, payload, info)
output = content.dup
layout = layouts[document.data["layout"].to_s]
validate_layout(layout)
used = Set.new([layout])
# Reset the payload layout data to ensure it starts fresh for each page.
payload["layout"] = nil
while layout
output = render_layout(output, layout, info)
add_regenerator_dependencies(layout)
if (layout = site.layouts[layout.data["layout"]])
break if used.include?(layout)
used << layout
end
end
output
end
private
# Checks if the layout specified in the document actually exists
#
# layout - the layout to check
# Returns nothing
def validate_layout(layout)
if invalid_layout?(layout)
Jekyll.logger.warn(
"Build Warning:",
"Layout '#{document.data["layout"]}' requested "\
"in #{document.relative_path} does not exist."
)
elsif !layout.nil?
layout_source = layout.path.start_with?(site.source) ? :site : :theme
Jekyll.logger.debug "Layout source:", layout_source
end
end
# Render layout content into document.output
#
# Returns String rendered content
def render_layout(output, layout, info)
payload["content"] = output
payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
render_liquid(
layout.content,
payload,
info,
layout.relative_path
)
end
def add_regenerator_dependencies(layout)
return unless document.write?
site.regenerator.add_dependency(
site.in_source_dir(document.path),
layout.path
)
end
# Set page content to payload and assign pager if document has one.
#
# Returns nothing
def assign_pages!
payload["page"] = document.to_liquid
payload["paginator"] = (document.pager.to_liquid if document.respond_to?(:pager))
end
# Set related posts to payload if document is a post.
#
# Returns nothing
def assign_current_document!
payload["site"].current_document = document
end
# Set highlighter prefix and suffix
#
# Returns nothing
def assign_highlighter_options!
payload["highlighter_prefix"] = converters.first.highlighter_prefix
payload["highlighter_suffix"] = converters.first.highlighter_suffix
end
def assign_layout_data!
layout = layouts[document.data["layout"]]
payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {}) if layout
end
def permalink_ext
document_permalink = document.permalink
if document_permalink && !document_permalink.end_with?("/")
permalink_ext = File.extname(document_permalink)
permalink_ext unless permalink_ext.empty?
end
end
def converter_output_ext
if output_exts.size == 1
output_exts.last
else
output_exts[-2]
end
end
def output_exts
@output_exts ||= converters.map do |c|
c.output_ext(document.extname)
end.compact
end
def liquid_options
@liquid_options ||= site.config["liquid"]
end
end
end