summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/jekyll-4.4.1/lib/jekyll/regenerator.rb
blob: 88c6a819a9e8fcbde7dac1b4f7b97db2b9eb6f63 (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
# frozen_string_literal: true

module Jekyll
  class Regenerator
    attr_reader :site, :metadata, :cache
    attr_accessor :disabled
    private :disabled, :disabled=

    def initialize(site)
      @site = site

      # Read metadata from file
      read_metadata

      # Initialize cache to an empty hash
      clear_cache
    end

    # Checks if a renderable object needs to be regenerated
    #
    # Returns a boolean.
    def regenerate?(document)
      return true if disabled

      case document
      when Page
        regenerate_page?(document)
      when Document
        regenerate_document?(document)
      else
        source_path = document.respond_to?(:path) ? document.path : nil
        dest_path = document.destination(@site.dest) if document.respond_to?(:destination)
        source_modified_or_dest_missing?(source_path, dest_path)
      end
    end

    # Add a path to the metadata
    #
    # Returns true, also on failure.
    def add(path)
      return true unless File.exist?(path)

      metadata[path] = {
        "mtime" => File.mtime(path),
        "deps"  => [],
      }
      cache[path] = true
    end

    # Force a path to regenerate
    #
    # Returns true.
    def force(path)
      cache[path] = true
    end

    # Clear the metadata and cache
    #
    # Returns nothing
    def clear
      @metadata = {}
      clear_cache
    end

    # Clear just the cache
    #
    # Returns nothing
    def clear_cache
      @cache = {}
    end

    # Checks if the source has been modified or the
    # destination is missing
    #
    # returns a boolean
    def source_modified_or_dest_missing?(source_path, dest_path)
      modified?(source_path) || (dest_path && !File.exist?(dest_path))
    end

    # Checks if a path's (or one of its dependencies)
    # mtime has changed
    #
    # Returns a boolean.
    def modified?(path)
      return true if disabled?

      # objects that don't have a path are always regenerated
      return true if path.nil?

      # Check for path in cache
      return cache[path] if cache.key? path

      if metadata[path]
        # If we have seen this file before,
        # check if it or one of its dependencies has been modified
        existing_file_modified?(path)
      else
        # If we have not seen this file before, add it to the metadata and regenerate it
        add(path)
      end
    end

    # Add a dependency of a path
    #
    # Returns nothing.
    def add_dependency(path, dependency)
      return if metadata[path].nil? || disabled

      unless metadata[path]["deps"].include? dependency
        metadata[path]["deps"] << dependency
        add(dependency) unless metadata.include?(dependency)
      end
      regenerate? dependency
    end

    # Write the metadata to disk
    #
    # Returns nothing.
    def write_metadata
      unless disabled?
        Jekyll.logger.debug "Writing Metadata:", ".jekyll-metadata"
        File.binwrite(metadata_file, Marshal.dump(metadata))
      end
    end

    # Produce the absolute path of the metadata file
    #
    # Returns the String path of the file.
    def metadata_file
      @metadata_file ||= site.in_source_dir(".jekyll-metadata")
    end

    # Check if metadata has been disabled
    #
    # Returns a Boolean (true for disabled, false for enabled).
    def disabled?
      self.disabled = !site.incremental? if disabled.nil?
      disabled
    end

    private

    # Read metadata from the metadata file, if no file is found,
    # initialize with an empty hash
    #
    # Returns the read metadata.
    def read_metadata
      @metadata =
        if !disabled? && File.file?(metadata_file)
          content = File.binread(metadata_file)

          begin
            Marshal.load(content)
          rescue TypeError
            SafeYAML.load(content)
          rescue ArgumentError => e
            Jekyll.logger.warn("Failed to load #{metadata_file}: #{e}")
            {}
          end
        else
          {}
        end
    end

    def regenerate_page?(document)
      document.asset_file? || document.data["regenerate"] ||
        source_modified_or_dest_missing?(
          site.in_source_dir(document.relative_path), document.destination(@site.dest)
        )
    end

    def regenerate_document?(document)
      !document.write? || document.data["regenerate"] ||
        source_modified_or_dest_missing?(
          document.path, document.destination(@site.dest)
        )
    end

    def existing_file_modified?(path)
      # If one of this file dependencies have been modified,
      # set the regeneration bit for both the dependency and the file to true
      metadata[path]["deps"].each do |dependency|
        return cache[dependency] = cache[path] = true if modified?(dependency)
      end

      if File.exist?(path) && metadata[path]["mtime"].eql?(File.mtime(path))
        # If this file has not been modified, set the regeneration bit to false
        cache[path] = false
      else
        # If it has been modified, set it to true
        add(path)
      end
    end
  end
end