Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request 7169
- Loading branch information
Showing
7 changed files
with
268 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
--- | ||
layout: tutorials | ||
permalink: /tutorials/cache-api/ | ||
title: Cache API | ||
--- | ||
|
||
Jekyll includes a caching API, which is used both internally as well as exposed | ||
for plugins, which can be used to cache the output of deterministic functions to | ||
speed up site generation. This cache will be persistent across builds, but | ||
cleared when Jekyll detects any changes to `_config.yml`. | ||
|
||
## Jekyll::Cache.new(name) → new_cache | ||
|
||
If there has already been a cache created with `name`, this will return a | ||
reference to that existing Cache. Otherwise, create a new Cache called `name`. | ||
|
||
If this Cache will be used by a Gem-packaged plugin, `name` should either be the | ||
name of the Gem, or prefixed with the name of the Gem followed by `::` (if a | ||
plugin expects to use multiple Caches). If this Cache will be used internally by | ||
Jekyll, `name` should be the name of the class that is using the Cache (ie: | ||
`"Jekyll::Converters::Markdown"`). | ||
|
||
Cached objects are shared between all Caches created with the same `name`, but | ||
are _not_ shared between Caches with different names. There can be an object | ||
stored with key `1` in `Jekyll::Cache.new("a")` and an object stored with key | ||
`1` in `Jekyll::Cache.new("b")` and these will not point to the same cached | ||
object. This way, you do not need to ensure that keys are globally unique. | ||
|
||
## getset(key) {block} | ||
|
||
This is the most common way to utilize the Cache. | ||
|
||
`block` is a bit of code that takes a lot of time to compute, but always | ||
generates the same output given a particular input (like converting Markdown to | ||
HTML). `key` is a `String` (or an object with `to_s`) that uniquely identifies | ||
the input to the function. | ||
|
||
If `key` already exists in the Cache, it will be returned and `block` will never | ||
be executed. If `key` does not exist in the Cache, `block` will be executed and | ||
the result will be added to the Cache and returned. | ||
|
||
```ruby | ||
def cache | ||
@@cache ||= Jekyll::Cache.new("ConvertMarkdown") | ||
end | ||
|
||
def convert_markdown_to_html(markdown) | ||
cache.getset(markdown) do | ||
expensive_conversion_method(markdown) | ||
end | ||
end | ||
``` | ||
|
||
In the above example, `expensive_conversion_method` will only be called once for | ||
any given `markdown` input. If `convert_markdown_to_html` is called a second | ||
time with the same input, the cached output will be returned. | ||
|
||
Because posts will frequently remain unchanged from one build to the next, this | ||
is an effective way to avoid performing the same computations each time the site | ||
is built. | ||
|
||
## clear | ||
|
||
This will clear all cached objects from a particular Cache. The Cache will be | ||
empty, both in memory and on disk. | ||
|
||
|
||
### The following methods will probably only be used in special circumstances | ||
|
||
## cache[key] → value | ||
|
||
Fetches `key` from Cache and returns its `value`. Raises if `key` does not exist | ||
in Cache. | ||
|
||
## cache[key] = value | ||
|
||
Adds `value` to Cache under `key`. | ||
Returns nothing. | ||
|
||
## key?(key) → true or false | ||
|
||
Returns `true` if `key` already exists in Cache. False otherwise. | ||
|
||
## delete(key) | ||
|
||
Removes `key` from Cache. | ||
Returns nothing. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
# frozen_string_literal: true | ||
|
||
require "digest" | ||
|
||
module Jekyll | ||
class Cache | ||
# rubocop:disable Style/ClassVars | ||
@@caches = {} | ||
@@disk_cache_enabled = true | ||
|
||
# Get an existing named cache, or create a new one if none exists | ||
# | ||
# name - name of the cache | ||
# | ||
# Returns nothing. | ||
def initialize(name) | ||
@@base_dir ||= File.expand_path(".jekyll-cache/Jekyll/Cache") | ||
@cache = @@caches[name] ||= {} | ||
@name = name.gsub(%r![^\w\s-]!, "-") | ||
end | ||
|
||
# Disable Marshaling cached items to disk | ||
def self.disable_disk_cache! | ||
@@disk_cache_enabled = false | ||
end | ||
# rubocop:enable Style/ClassVars | ||
|
||
# Clear all caches | ||
def self.clear | ||
delete_cache_files | ||
@@caches.each_value(&:clear) | ||
end | ||
|
||
# Clear this particular cache | ||
def clear | ||
delete_cache_files | ||
@cache.clear | ||
end | ||
|
||
# Retrieve a cached item | ||
# Raises if key does not exist in cache | ||
# | ||
# Returns cached value | ||
def [](key) | ||
return @cache[key] if @cache.key?(key) | ||
path = path_to(hash(key)) | ||
if @@disk_cache_enabled && File.file?(path) && File.readable?(path) | ||
@cache[key] = load(path) | ||
else | ||
raise | ||
end | ||
end | ||
|
||
# Add an item to cache | ||
# | ||
# Returns nothing. | ||
def []=(key, value) | ||
@cache[key] = value | ||
return unless @@disk_cache_enabled | ||
path = path_to(hash(key)) | ||
dump(path, value) | ||
end | ||
|
||
# If an item already exists in the cache, retrieve it | ||
# Else execute code block, and add the result to the cache, and return that | ||
# result | ||
def getset(key) | ||
self[key] | ||
rescue StandardError | ||
value = yield | ||
self[key] = value | ||
value | ||
end | ||
|
||
# Remove one particular item from the cache | ||
# | ||
# Returns nothing. | ||
def delete(key) | ||
@cache.delete(key) | ||
return unless @@disk_cache_enabled | ||
path = path_to(hash(key)) | ||
File.delete(path) | ||
end | ||
|
||
# Check if `key` already exists in this cache | ||
# | ||
# Returns true if key exists in the cache, false otherwise | ||
def key?(key) | ||
# First, check if item is already cached in memory | ||
return true if @cache.key?(key) | ||
# Otherwise, it might be cached on disk | ||
# but we should not consider the disk cache if it is disabled | ||
return false unless @@disk_cache_enabled | ||
path = path_to(hash(key)) | ||
File.file?(path) && File.readable?(path) | ||
end | ||
|
||
# Compare the current config to the cached config | ||
# If they are different, clear all caches | ||
# | ||
# Returns nothing. | ||
def self.clear_if_config_changed(config) | ||
config = config.inspect | ||
cache = Jekyll::Cache.new "Jekyll::Cache" | ||
return if cache.key?("config") && cache["config"] == config | ||
clear | ||
cache = Jekyll::Cache.new "Jekyll::Cache" | ||
cache["config"] = config | ||
nil | ||
end | ||
|
||
private | ||
|
||
# Given a hashed key, return the path to where this item would be saved on | ||
# disk | ||
def path_to(hash = nil) | ||
@base_dir ||= File.join(@@base_dir, @name) | ||
return @base_dir if hash.nil? | ||
File.join(@base_dir, hash[0..1], hash[2..-1]).freeze | ||
end | ||
|
||
# Given a key, return a SHA2 hash that can be used for caching this item to | ||
# disk | ||
def hash(key) | ||
Digest::SHA2.hexdigest(key).freeze | ||
end | ||
|
||
# Remove all this caches items from disk | ||
# | ||
# Returns nothing. | ||
def delete_cache_files | ||
FileUtils.rm_rf(path_to) if @@disk_cache_enabled | ||
end | ||
|
||
# Delete all cached items from all caches | ||
# | ||
# Returns nothing. | ||
def self.delete_cache_files | ||
FileUtils.rm_rf(@@base_dir) if @@disk_cache_enabled | ||
end | ||
private_class_method :delete_cache_files | ||
|
||
# Load `path` from disk and return the result | ||
# This MUST NEVER be called in Safe Mode | ||
# rubocop:disable Security/MarshalLoad | ||
def load(path) | ||
raise unless @@disk_cache_enabled | ||
cached_file = File.open(path, "rb") | ||
value = Marshal.load(cached_file) | ||
cached_file.close | ||
value | ||
end | ||
# rubocop:enable Security/MarshalLoad | ||
|
||
# Given a path and a value, save value to disk at path | ||
# This should NEVER be called in Safe Mode | ||
# | ||
# Returns nothing. | ||
def dump(path, value) | ||
return unless @@disk_cache_enabled | ||
dir = File.dirname(path) | ||
FileUtils.mkdir_p(dir) | ||
File.open(path, "wb") do |cached_file| | ||
Marshal.dump(value, cached_file) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
_site | ||
.sass-cache | ||
.jekyll-cache | ||
.jekyll-metadata | ||
vendor |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
*.gem | ||
.bundle | ||
.jekyll-cache | ||
.sass-cache | ||
_site | ||
Gemfile.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters