blob: 09ff309a28daa59a8dbd39a87416708d2efb7e76 [file] [log] [blame]
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)
case document
when Page
regenerate_page?(document)
when Document
regenerate_document?(document)
else
source_path = document.respond_to?(:path) ? document.path : nil
dest_path = if document.respond_to?(:destination)
document.destination(@site.dest)
end
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
if cache.key? path
return cache[path]
end
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?
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
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
private
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
private
def regenerate_document?(document)
!document.write? || document.data["regenerate"] ||
source_modified_or_dest_missing?(
document.path, document.destination(@site.dest)
)
end
private
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|
if modified?(dependency)
return cache[dependency] = cache[path] = true
end
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