jekyll 4.2.1 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +474 -350
  3. data/LICENSE +21 -21
  4. data/README.markdown +83 -86
  5. data/exe/jekyll +57 -57
  6. data/lib/blank_template/_config.yml +3 -3
  7. data/lib/blank_template/_layouts/default.html +12 -12
  8. data/lib/blank_template/_sass/main.scss +9 -9
  9. data/lib/blank_template/assets/css/main.scss +4 -4
  10. data/lib/blank_template/index.md +8 -8
  11. data/lib/jekyll/cache.rb +186 -190
  12. data/lib/jekyll/cleaner.rb +111 -111
  13. data/lib/jekyll/collection.rb +310 -309
  14. data/lib/jekyll/command.rb +105 -105
  15. data/lib/jekyll/commands/build.rb +82 -93
  16. data/lib/jekyll/commands/clean.rb +44 -45
  17. data/lib/jekyll/commands/doctor.rb +177 -177
  18. data/lib/jekyll/commands/help.rb +34 -34
  19. data/lib/jekyll/commands/new.rb +168 -169
  20. data/lib/jekyll/commands/new_theme.rb +39 -40
  21. data/lib/jekyll/commands/serve/live_reload_reactor.rb +119 -122
  22. data/lib/jekyll/commands/serve/livereload_assets/livereload.js +1183 -1183
  23. data/lib/jekyll/commands/serve/mime_types_charset.json +71 -0
  24. data/lib/jekyll/commands/serve/servlet.rb +206 -202
  25. data/lib/jekyll/commands/serve/websockets.rb +81 -81
  26. data/lib/jekyll/commands/serve.rb +367 -362
  27. data/lib/jekyll/configuration.rb +313 -313
  28. data/lib/jekyll/converter.rb +54 -54
  29. data/lib/jekyll/converters/identity.rb +41 -41
  30. data/lib/jekyll/converters/markdown/kramdown_parser.rb +197 -199
  31. data/lib/jekyll/converters/markdown.rb +113 -113
  32. data/lib/jekyll/converters/smartypants.rb +70 -70
  33. data/lib/jekyll/convertible.rb +257 -257
  34. data/lib/jekyll/data_entry.rb +83 -0
  35. data/lib/jekyll/data_hash.rb +61 -0
  36. data/lib/jekyll/deprecator.rb +50 -50
  37. data/lib/jekyll/document.rb +543 -544
  38. data/lib/jekyll/drops/collection_drop.rb +20 -20
  39. data/lib/jekyll/drops/document_drop.rb +71 -70
  40. data/lib/jekyll/drops/drop.rb +293 -293
  41. data/lib/jekyll/drops/excerpt_drop.rb +23 -19
  42. data/lib/jekyll/drops/jekyll_drop.rb +32 -32
  43. data/lib/jekyll/drops/site_drop.rb +71 -66
  44. data/lib/jekyll/drops/static_file_drop.rb +14 -14
  45. data/lib/jekyll/drops/theme_drop.rb +36 -0
  46. data/lib/jekyll/drops/unified_payload_drop.rb +30 -26
  47. data/lib/jekyll/drops/url_drop.rb +140 -140
  48. data/lib/jekyll/entry_filter.rb +117 -121
  49. data/lib/jekyll/errors.rb +20 -20
  50. data/lib/jekyll/excerpt.rb +200 -201
  51. data/lib/jekyll/external.rb +75 -79
  52. data/lib/jekyll/filters/date_filters.rb +110 -110
  53. data/lib/jekyll/filters/grouping_filters.rb +64 -64
  54. data/lib/jekyll/filters/url_filters.rb +98 -98
  55. data/lib/jekyll/filters.rb +532 -535
  56. data/lib/jekyll/frontmatter_defaults.rb +238 -240
  57. data/lib/jekyll/generator.rb +5 -5
  58. data/lib/jekyll/hooks.rb +107 -107
  59. data/lib/jekyll/inclusion.rb +32 -32
  60. data/lib/jekyll/layout.rb +55 -67
  61. data/lib/jekyll/liquid_extensions.rb +22 -22
  62. data/lib/jekyll/liquid_renderer/file.rb +77 -77
  63. data/lib/jekyll/liquid_renderer/table.rb +55 -55
  64. data/lib/jekyll/liquid_renderer.rb +80 -80
  65. data/lib/jekyll/log_adapter.rb +151 -151
  66. data/lib/jekyll/mime.types +939 -866
  67. data/lib/jekyll/page.rb +215 -217
  68. data/lib/jekyll/page_excerpt.rb +25 -25
  69. data/lib/jekyll/page_without_a_file.rb +14 -14
  70. data/lib/jekyll/path_manager.rb +74 -74
  71. data/lib/jekyll/plugin.rb +92 -92
  72. data/lib/jekyll/plugin_manager.rb +123 -115
  73. data/lib/jekyll/profiler.rb +55 -58
  74. data/lib/jekyll/publisher.rb +23 -23
  75. data/lib/jekyll/reader.rb +209 -192
  76. data/lib/jekyll/readers/collection_reader.rb +23 -23
  77. data/lib/jekyll/readers/data_reader.rb +116 -79
  78. data/lib/jekyll/readers/layout_reader.rb +62 -62
  79. data/lib/jekyll/readers/page_reader.rb +25 -25
  80. data/lib/jekyll/readers/post_reader.rb +85 -85
  81. data/lib/jekyll/readers/static_file_reader.rb +25 -25
  82. data/lib/jekyll/readers/theme_assets_reader.rb +52 -52
  83. data/lib/jekyll/regenerator.rb +195 -195
  84. data/lib/jekyll/related_posts.rb +52 -52
  85. data/lib/jekyll/renderer.rb +263 -265
  86. data/lib/jekyll/site.rb +582 -551
  87. data/lib/jekyll/static_file.rb +205 -208
  88. data/lib/jekyll/stevenson.rb +60 -60
  89. data/lib/jekyll/tags/highlight.rb +114 -110
  90. data/lib/jekyll/tags/include.rb +275 -275
  91. data/lib/jekyll/tags/link.rb +42 -42
  92. data/lib/jekyll/tags/post_url.rb +106 -106
  93. data/lib/jekyll/theme.rb +90 -86
  94. data/lib/jekyll/theme_builder.rb +121 -121
  95. data/lib/jekyll/url.rb +167 -167
  96. data/lib/jekyll/utils/ansi.rb +57 -57
  97. data/lib/jekyll/utils/exec.rb +26 -26
  98. data/lib/jekyll/utils/internet.rb +37 -37
  99. data/lib/jekyll/utils/platforms.rb +67 -67
  100. data/lib/jekyll/utils/thread_event.rb +31 -31
  101. data/lib/jekyll/utils/win_tz.rb +46 -75
  102. data/lib/jekyll/utils.rb +378 -367
  103. data/lib/jekyll/version.rb +5 -5
  104. data/lib/jekyll.rb +197 -195
  105. data/lib/site_template/.gitignore +5 -5
  106. data/lib/site_template/404.html +25 -25
  107. data/lib/site_template/_config.yml +55 -55
  108. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -29
  109. data/lib/site_template/about.markdown +18 -18
  110. data/lib/site_template/index.markdown +6 -6
  111. data/lib/theme_template/CODE_OF_CONDUCT.md.erb +74 -74
  112. data/lib/theme_template/Gemfile +4 -4
  113. data/lib/theme_template/LICENSE.txt.erb +21 -21
  114. data/lib/theme_template/README.md.erb +50 -52
  115. data/lib/theme_template/_layouts/default.html +1 -1
  116. data/lib/theme_template/_layouts/page.html +5 -5
  117. data/lib/theme_template/_layouts/post.html +5 -5
  118. data/lib/theme_template/example/_config.yml.erb +1 -1
  119. data/lib/theme_template/example/_post.md +12 -12
  120. data/lib/theme_template/example/index.html +14 -14
  121. data/lib/theme_template/example/style.scss +7 -7
  122. data/lib/theme_template/gitignore.erb +6 -6
  123. data/lib/theme_template/theme.gemspec.erb +16 -16
  124. data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -149
  125. data/rubocop/jekyll/no_p_allowed.rb +23 -23
  126. data/rubocop/jekyll/no_puts_allowed.rb +23 -23
  127. data/rubocop/jekyll.rb +5 -5
  128. metadata +62 -14
@@ -1,265 +1,263 @@
1
- # frozen_string_literal: true
2
-
3
- module Jekyll
4
- class Renderer
5
- attr_reader :document, :site
6
- attr_writer :layouts, :payload
7
-
8
- def initialize(site, document, site_payload = nil)
9
- @site = site
10
- @document = document
11
- @payload = site_payload
12
- @layouts = nil
13
- end
14
-
15
- # Fetches the payload used in Liquid rendering.
16
- # It can be written with #payload=(new_payload)
17
- # Falls back to site.site_payload if no payload is set.
18
- #
19
- # Returns a Jekyll::Drops::UnifiedPayloadDrop
20
- def payload
21
- @payload ||= site.site_payload
22
- end
23
-
24
- # The list of layouts registered for this Renderer.
25
- # It can be written with #layouts=(new_layouts)
26
- # Falls back to site.layouts if no layouts are registered.
27
- #
28
- # Returns a Hash of String => Jekyll::Layout identified
29
- # as basename without the extension name.
30
- def layouts
31
- @layouts || site.layouts
32
- end
33
-
34
- # Determine which converters to use based on this document's
35
- # extension.
36
- #
37
- # Returns Array of Converter instances.
38
- def converters
39
- @converters ||= site.converters.select { |c| c.matches(document.extname) }.tap(&:sort!)
40
- end
41
-
42
- # Determine the extname the outputted file should have
43
- #
44
- # Returns String the output extname including the leading period.
45
- def output_ext
46
- @output_ext ||= (permalink_ext || converter_output_ext)
47
- end
48
-
49
- # Prepare payload and render the document
50
- #
51
- # Returns String rendered document output
52
- def run
53
- Jekyll.logger.debug "Rendering:", document.relative_path
54
-
55
- assign_pages!
56
- assign_current_document!
57
- assign_highlighter_options!
58
- assign_layout_data!
59
-
60
- Jekyll.logger.debug "Pre-Render Hooks:", document.relative_path
61
- document.trigger_hooks(:pre_render, payload)
62
-
63
- render_document
64
- end
65
-
66
- # Render the document.
67
- #
68
- # Returns String rendered document output
69
- # rubocop: disable Metrics/AbcSize, Metrics/MethodLength
70
- def render_document
71
- info = {
72
- :registers => { :site => site, :page => payload["page"] },
73
- :strict_filters => liquid_options["strict_filters"],
74
- :strict_variables => liquid_options["strict_variables"],
75
- }
76
-
77
- output = document.content
78
- if document.render_with_liquid?
79
- Jekyll.logger.debug "Rendering Liquid:", document.relative_path
80
- output = render_liquid(output, payload, info, document.path)
81
- end
82
-
83
- Jekyll.logger.debug "Rendering Markup:", document.relative_path
84
- output = convert(output.to_s)
85
- document.content = output
86
-
87
- Jekyll.logger.debug "Post-Convert Hooks:", document.relative_path
88
- document.trigger_hooks(:post_convert)
89
- output = document.content
90
-
91
- if document.place_in_layout?
92
- Jekyll.logger.debug "Rendering Layout:", document.relative_path
93
- output = place_in_layouts(output, payload, info)
94
- end
95
-
96
- output
97
- end
98
- # rubocop: enable Metrics/AbcSize, Metrics/MethodLength
99
-
100
- # Convert the document using the converters which match this renderer's document.
101
- #
102
- # Returns String the converted content.
103
- def convert(content)
104
- converters.reduce(content) do |output, converter|
105
- begin
106
- converter.convert output
107
- rescue StandardError => e
108
- Jekyll.logger.error "Conversion error:",
109
- "#{converter.class} encountered an error while "\
110
- "converting '#{document.relative_path}':"
111
- Jekyll.logger.error("", e.to_s)
112
- raise e
113
- end
114
- end
115
- end
116
-
117
- # Render the given content with the payload and info
118
- #
119
- # content -
120
- # payload -
121
- # info -
122
- # path - (optional) the path to the file, for use in ex
123
- #
124
- # Returns String the content, rendered by Liquid.
125
- def render_liquid(content, payload, info, path = nil)
126
- template = site.liquid_renderer.file(path).parse(content)
127
- template.warnings.each do |e|
128
- Jekyll.logger.warn "Liquid Warning:",
129
- LiquidRenderer.format_error(e, path || document.relative_path)
130
- end
131
- template.render!(payload, info)
132
- # rubocop: disable Lint/RescueException
133
- rescue Exception => e
134
- Jekyll.logger.error "Liquid Exception:",
135
- LiquidRenderer.format_error(e, path || document.relative_path)
136
- raise e
137
- end
138
- # rubocop: enable Lint/RescueException
139
-
140
- # Checks if the layout specified in the document actually exists
141
- #
142
- # layout - the layout to check
143
- #
144
- # Returns Boolean true if the layout is invalid, false if otherwise
145
- def invalid_layout?(layout)
146
- !document.data["layout"].nil? && layout.nil? && !(document.is_a? Jekyll::Excerpt)
147
- end
148
-
149
- # Render layouts and place document content inside.
150
- #
151
- # Returns String rendered content
152
- def place_in_layouts(content, payload, info)
153
- output = content.dup
154
- layout = layouts[document.data["layout"].to_s]
155
- validate_layout(layout)
156
-
157
- used = Set.new([layout])
158
-
159
- # Reset the payload layout data to ensure it starts fresh for each page.
160
- payload["layout"] = nil
161
-
162
- while layout
163
- output = render_layout(output, layout, info)
164
- add_regenerator_dependencies(layout)
165
-
166
- next unless (layout = site.layouts[layout.data["layout"]])
167
- break if used.include?(layout)
168
-
169
- used << layout
170
- end
171
- output
172
- end
173
-
174
- private
175
-
176
- # Checks if the layout specified in the document actually exists
177
- #
178
- # layout - the layout to check
179
- # Returns nothing
180
- def validate_layout(layout)
181
- return unless invalid_layout?(layout)
182
-
183
- Jekyll.logger.warn "Build Warning:", "Layout '#{document.data["layout"]}' requested " \
184
- "in #{document.relative_path} does not exist."
185
- end
186
-
187
- # Render layout content into document.output
188
- #
189
- # Returns String rendered content
190
- def render_layout(output, layout, info)
191
- payload["content"] = output
192
- payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
193
-
194
- render_liquid(
195
- layout.content,
196
- payload,
197
- info,
198
- layout.path
199
- )
200
- end
201
-
202
- def add_regenerator_dependencies(layout)
203
- return unless document.write?
204
-
205
- site.regenerator.add_dependency(
206
- site.in_source_dir(document.path),
207
- layout.path
208
- )
209
- end
210
-
211
- # Set page content to payload and assign pager if document has one.
212
- #
213
- # Returns nothing
214
- def assign_pages!
215
- payload["page"] = document.to_liquid
216
- payload["paginator"] = (document.pager.to_liquid if document.respond_to?(:pager))
217
- end
218
-
219
- # Set related posts to payload if document is a post.
220
- #
221
- # Returns nothing
222
- def assign_current_document!
223
- payload["site"].current_document = document
224
- end
225
-
226
- # Set highlighter prefix and suffix
227
- #
228
- # Returns nothing
229
- def assign_highlighter_options!
230
- payload["highlighter_prefix"] = converters.first.highlighter_prefix
231
- payload["highlighter_suffix"] = converters.first.highlighter_suffix
232
- end
233
-
234
- def assign_layout_data!
235
- layout = layouts[document.data["layout"]]
236
- payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {}) if layout
237
- end
238
-
239
- def permalink_ext
240
- document_permalink = document.permalink
241
- if document_permalink && !document_permalink.end_with?("/")
242
- permalink_ext = File.extname(document_permalink)
243
- permalink_ext unless permalink_ext.empty?
244
- end
245
- end
246
-
247
- def converter_output_ext
248
- if output_exts.size == 1
249
- output_exts.last
250
- else
251
- output_exts[-2]
252
- end
253
- end
254
-
255
- def output_exts
256
- @output_exts ||= converters.map do |c|
257
- c.output_ext(document.extname)
258
- end.tap(&:compact!)
259
- end
260
-
261
- def liquid_options
262
- @liquid_options ||= site.config["liquid"]
263
- end
264
- end
265
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ class Renderer
5
+ attr_reader :document, :site
6
+ attr_writer :layouts, :payload
7
+
8
+ def initialize(site, document, site_payload = nil)
9
+ @site = site
10
+ @document = document
11
+ @payload = site_payload
12
+ @layouts = nil
13
+ end
14
+
15
+ # Fetches the payload used in Liquid rendering.
16
+ # It can be written with #payload=(new_payload)
17
+ # Falls back to site.site_payload if no payload is set.
18
+ #
19
+ # Returns a Jekyll::Drops::UnifiedPayloadDrop
20
+ def payload
21
+ @payload ||= site.site_payload
22
+ end
23
+
24
+ # The list of layouts registered for this Renderer.
25
+ # It can be written with #layouts=(new_layouts)
26
+ # Falls back to site.layouts if no layouts are registered.
27
+ #
28
+ # Returns a Hash of String => Jekyll::Layout identified
29
+ # as basename without the extension name.
30
+ def layouts
31
+ @layouts || site.layouts
32
+ end
33
+
34
+ # Determine which converters to use based on this document's
35
+ # extension.
36
+ #
37
+ # Returns Array of Converter instances.
38
+ def converters
39
+ @converters ||= site.converters.select { |c| c.matches(document.extname) }.tap(&:sort!)
40
+ end
41
+
42
+ # Determine the extname the outputted file should have
43
+ #
44
+ # Returns String the output extname including the leading period.
45
+ def output_ext
46
+ @output_ext ||= (permalink_ext || converter_output_ext)
47
+ end
48
+
49
+ # Prepare payload and render the document
50
+ #
51
+ # Returns String rendered document output
52
+ def run
53
+ Jekyll.logger.debug "Rendering:", document.relative_path
54
+
55
+ assign_pages!
56
+ assign_current_document!
57
+ assign_highlighter_options!
58
+ assign_layout_data!
59
+
60
+ Jekyll.logger.debug "Pre-Render Hooks:", document.relative_path
61
+ document.trigger_hooks(:pre_render, payload)
62
+
63
+ render_document
64
+ end
65
+
66
+ # Render the document.
67
+ #
68
+ # Returns String rendered document output
69
+ # rubocop: disable Metrics/AbcSize, Metrics/MethodLength
70
+ def render_document
71
+ info = {
72
+ :registers => { :site => site, :page => payload["page"] },
73
+ :strict_filters => liquid_options["strict_filters"],
74
+ :strict_variables => liquid_options["strict_variables"],
75
+ }
76
+
77
+ output = document.content
78
+ if document.render_with_liquid?
79
+ Jekyll.logger.debug "Rendering Liquid:", document.relative_path
80
+ output = render_liquid(output, payload, info, document.path)
81
+ end
82
+
83
+ Jekyll.logger.debug "Rendering Markup:", document.relative_path
84
+ output = convert(output.to_s)
85
+ document.content = output
86
+
87
+ Jekyll.logger.debug "Post-Convert Hooks:", document.relative_path
88
+ document.trigger_hooks(:post_convert)
89
+ output = document.content
90
+
91
+ if document.place_in_layout?
92
+ Jekyll.logger.debug "Rendering Layout:", document.relative_path
93
+ output = place_in_layouts(output, payload, info)
94
+ end
95
+
96
+ output
97
+ end
98
+ # rubocop: enable Metrics/AbcSize, Metrics/MethodLength
99
+
100
+ # Convert the document using the converters which match this renderer's document.
101
+ #
102
+ # Returns String the converted content.
103
+ def convert(content)
104
+ converters.reduce(content) do |output, converter|
105
+ converter.convert output
106
+ rescue StandardError => e
107
+ Jekyll.logger.error "Conversion error:",
108
+ "#{converter.class} encountered an error while " \
109
+ "converting '#{document.relative_path}':"
110
+ Jekyll.logger.error("", e.to_s)
111
+ raise e
112
+ end
113
+ end
114
+
115
+ # Render the given content with the payload and info
116
+ #
117
+ # content -
118
+ # payload -
119
+ # info -
120
+ # path - (optional) the path to the file, for use in ex
121
+ #
122
+ # Returns String the content, rendered by Liquid.
123
+ def render_liquid(content, payload, info, path = nil)
124
+ template = site.liquid_renderer.file(path).parse(content)
125
+ template.warnings.each do |e|
126
+ Jekyll.logger.warn "Liquid Warning:",
127
+ LiquidRenderer.format_error(e, path || document.relative_path)
128
+ end
129
+ template.render!(payload, info)
130
+ # rubocop: disable Lint/RescueException
131
+ rescue Exception => e
132
+ Jekyll.logger.error "Liquid Exception:",
133
+ LiquidRenderer.format_error(e, path || document.relative_path)
134
+ raise e
135
+ end
136
+ # rubocop: enable Lint/RescueException
137
+
138
+ # Checks if the layout specified in the document actually exists
139
+ #
140
+ # layout - the layout to check
141
+ #
142
+ # Returns Boolean true if the layout is invalid, false if otherwise
143
+ def invalid_layout?(layout)
144
+ !document.data["layout"].nil? && layout.nil? && !(document.is_a? Jekyll::Excerpt)
145
+ end
146
+
147
+ # Render layouts and place document content inside.
148
+ #
149
+ # Returns String rendered content
150
+ def place_in_layouts(content, payload, info)
151
+ output = content.dup
152
+ layout = layouts[document.data["layout"].to_s]
153
+ validate_layout(layout)
154
+
155
+ used = Set.new([layout])
156
+
157
+ # Reset the payload layout data to ensure it starts fresh for each page.
158
+ payload["layout"] = nil
159
+
160
+ while layout
161
+ output = render_layout(output, layout, info)
162
+ add_regenerator_dependencies(layout)
163
+
164
+ next unless (layout = site.layouts[layout.data["layout"]])
165
+ break if used.include?(layout)
166
+
167
+ used << layout
168
+ end
169
+ output
170
+ end
171
+
172
+ private
173
+
174
+ # Checks if the layout specified in the document actually exists
175
+ #
176
+ # layout - the layout to check
177
+ # Returns nothing
178
+ def validate_layout(layout)
179
+ return unless invalid_layout?(layout)
180
+
181
+ Jekyll.logger.warn "Build Warning:", "Layout '#{document.data["layout"]}' requested " \
182
+ "in #{document.relative_path} does not exist."
183
+ end
184
+
185
+ # Render layout content into document.output
186
+ #
187
+ # Returns String rendered content
188
+ def render_layout(output, layout, info)
189
+ payload["content"] = output
190
+ payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
191
+
192
+ render_liquid(
193
+ layout.content,
194
+ payload,
195
+ info,
196
+ layout.path
197
+ )
198
+ end
199
+
200
+ def add_regenerator_dependencies(layout)
201
+ return unless document.write?
202
+
203
+ site.regenerator.add_dependency(
204
+ site.in_source_dir(document.path),
205
+ layout.path
206
+ )
207
+ end
208
+
209
+ # Set page content to payload and assign pager if document has one.
210
+ #
211
+ # Returns nothing
212
+ def assign_pages!
213
+ payload["page"] = document.to_liquid
214
+ payload["paginator"] = (document.pager.to_liquid if document.respond_to?(:pager))
215
+ end
216
+
217
+ # Set related posts to payload if document is a post.
218
+ #
219
+ # Returns nothing
220
+ def assign_current_document!
221
+ payload["site"].current_document = document
222
+ end
223
+
224
+ # Set highlighter prefix and suffix
225
+ #
226
+ # Returns nothing
227
+ def assign_highlighter_options!
228
+ payload["highlighter_prefix"] = converters.first.highlighter_prefix
229
+ payload["highlighter_suffix"] = converters.first.highlighter_suffix
230
+ end
231
+
232
+ def assign_layout_data!
233
+ layout = layouts[document.data["layout"]]
234
+ payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {}) if layout
235
+ end
236
+
237
+ def permalink_ext
238
+ document_permalink = document.permalink
239
+ if document_permalink && !document_permalink.end_with?("/")
240
+ permalink_ext = File.extname(document_permalink)
241
+ permalink_ext unless permalink_ext.empty?
242
+ end
243
+ end
244
+
245
+ def converter_output_ext
246
+ if output_exts.size == 1
247
+ output_exts.last
248
+ else
249
+ output_exts[-2]
250
+ end
251
+ end
252
+
253
+ def output_exts
254
+ @output_exts ||= converters.map do |c|
255
+ c.output_ext(document.extname)
256
+ end.tap(&:compact!)
257
+ end
258
+
259
+ def liquid_options
260
+ @liquid_options ||= site.config["liquid"]
261
+ end
262
+ end
263
+ end