summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/jekyll-4.4.1/lib/jekyll/renderer.rb
blob: c365a0d728994167b8dcf3c0c88697646705aeb4 (plain)
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
# 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
      @layouts  = nil
    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) }.tap(&: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 Metrics/AbcSize, Metrics/MethodLength
    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

      Jekyll.logger.debug "Post-Convert Hooks:", document.relative_path
      document.trigger_hooks(:post_convert)
      output = document.content

      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 Metrics/AbcSize, Metrics/MethodLength

    # 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|
        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

    # 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 Lint/RescueException
    rescue Exception => e
      Jekyll.logger.error "Liquid Exception:",
                          LiquidRenderer.format_error(e, path || document.relative_path)
      raise e
    end
    # rubocop: enable Lint/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)

        next unless (layout = site.layouts[layout.data["layout"]])
        break if used.include?(layout)

        used << layout
      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)
      return unless invalid_layout?(layout)

      Jekyll.logger.warn "Build Warning:", "Layout '#{document.data["layout"]}' requested " \
                                           "in #{document.relative_path} does not exist."
    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.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.tap(&:compact!)
    end

    def liquid_options
      @liquid_options ||= site.config["liquid"]
    end
  end
end