-
-
Notifications
You must be signed in to change notification settings - Fork 9.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Incrementally rebuild when a data file is changed (#8771)
Merge pull request 8771
- Loading branch information
Showing
11 changed files
with
315 additions
and
5 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
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,83 @@ | ||
# frozen_string_literal: true | ||
|
||
module Jekyll | ||
class DataEntry | ||
attr_accessor :context | ||
attr_reader :data | ||
|
||
# Create a Jekyll wrapper for given parsed data object. | ||
# | ||
# site - The current Jekyll::Site instance. | ||
# abs_path - Absolute path to the data source file. | ||
# parsed_data - Parsed representation of data source file contents. | ||
# | ||
# Returns nothing. | ||
def initialize(site, abs_path, parsed_data) | ||
@site = site | ||
@path = abs_path | ||
@data = parsed_data | ||
end | ||
|
||
# Liquid representation of current instance is the parsed data object. | ||
# | ||
# Mark as a dependency for regeneration here since every renderable object primarily uses the | ||
# parsed data object while the parent resource is being rendered by Liquid. Accessing the data | ||
# object directly via Ruby interface `#[]()` is outside the scope of regeneration. | ||
# | ||
# FIXME: Marking as dependency on every call is non-ideal. Optimize at later day. | ||
# | ||
# Returns the parsed data object. | ||
def to_liquid | ||
add_regenerator_dependencies if incremental_build? | ||
@data | ||
end | ||
|
||
# -- Overrides to maintain backwards compatibility -- | ||
|
||
# Any missing method will be forwarded to the underlying data object stored in the instance | ||
# variable `@data`. | ||
def method_missing(method, *args, &block) | ||
@data.respond_to?(method) ? @data.send(method, *args, &block) : super | ||
end | ||
|
||
def respond_to_missing?(method, *) | ||
@data.respond_to?(method) || super | ||
end | ||
|
||
def <=>(other) | ||
data <=> (other.is_a?(self.class) ? other.data : other) | ||
end | ||
|
||
def ==(other) | ||
data == (other.is_a?(self.class) ? other.data : other) | ||
end | ||
|
||
# Explicitly defined to bypass re-routing from `method_missing` hook for greater performance. | ||
# | ||
# Returns string representation of parsed data object. | ||
def inspect | ||
@data.inspect | ||
end | ||
|
||
private | ||
|
||
def incremental_build? | ||
@incremental = @site.config["incremental"] if @incremental.nil? | ||
@incremental | ||
end | ||
|
||
def add_regenerator_dependencies | ||
page = context.registers[:page] | ||
return unless page&.key?("path") | ||
|
||
absolute_path = \ | ||
if page["collection"] | ||
@site.in_source_dir(@site.config["collections_dir"], page["path"]) | ||
else | ||
@site.in_source_dir(page["path"]) | ||
end | ||
|
||
@site.regenerator.add_dependency(absolute_path, @path) | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# frozen_string_literal: true | ||
|
||
module Jekyll | ||
# A class that behaves very similar to Ruby's `Hash` class yet different in how it is handled by | ||
# Liquid. This class emulates Hash by delegation instead of inheritance to minimize overridden | ||
# methods especially since some Hash methods returns another Hash instance instead of the | ||
# subclass instance. | ||
class DataHash | ||
# | ||
# Delegate given (zero-arity) method(s) to the Hash object stored in instance variable | ||
# `@registry`. | ||
# NOTE: Avoiding the use of `Forwardable` module's `def_delegators` for preventing unnecessary | ||
# creation of interim objects on multiple calls. | ||
def self.delegate_to_registry(*symbols) | ||
symbols.each { |sym| define_method(sym) { @registry.send(sym) } } | ||
end | ||
private_class_method :delegate_to_registry | ||
|
||
# -- core instance methods -- | ||
|
||
attr_accessor :context | ||
|
||
def initialize | ||
@registry = {} | ||
end | ||
|
||
def [](key) | ||
@registry[key].tap do |value| | ||
value.context = context if value.respond_to?(:context=) | ||
end | ||
end | ||
|
||
# `Hash#to_liquid` returns the Hash instance itself. | ||
# Mimic that behavior by returning `self` instead of returning the `@registry` variable value. | ||
def to_liquid | ||
self | ||
end | ||
|
||
# -- supplementary instance methods to emulate Hash -- | ||
|
||
delegate_to_registry :freeze, :inspect | ||
|
||
def merge(other, &block) | ||
merged_registry = @registry.merge(other, &block) | ||
dup.tap { |d| d.instance_variable_set(:@registry, merged_registry) } | ||
end | ||
|
||
def merge!(other, &block) | ||
@registry.merge!(other, &block) | ||
self | ||
end | ||
|
||
def method_missing(method, *args, &block) | ||
@registry.send(method, *args, &block) | ||
end | ||
|
||
def respond_to_missing?(method, *) | ||
@registry.respond_to?(method) | ||
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
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 @@ | ||
true |
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,4 @@ | ||
- java | ||
- ruby | ||
- rust | ||
- golang |
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,34 @@ | ||
# frozen_string_literal: true | ||
|
||
require "helper" | ||
|
||
class TestDataEntry < JekyllUnitTest | ||
context "Data Entry" do | ||
setup do | ||
site = fixture_site | ||
site.read | ||
@data_hash = site.site_data | ||
end | ||
|
||
should "expose underlying data object es Liquid representation" do | ||
subject = @data_hash["languages"] | ||
assert_equal Jekyll::DataEntry, subject.class | ||
assert_equal subject.data, subject.to_liquid | ||
end | ||
|
||
should "respond to `#[](key)` when expected to but raise Exception otherwise" do | ||
greeting = @data_hash["greetings"] | ||
assert greeting["foo"] | ||
|
||
boolean = @data_hash["boolean"] # the value is a Boolean. | ||
assert_raises(NoMethodError) { boolean["foo"] } | ||
end | ||
|
||
should "compare with another instance of same class using underlying data" do | ||
assert_equal( | ||
[%w(java ruby), %w(java ruby rust golang)], | ||
[@data_hash["languages_plus"], @data_hash["languages"]].sort | ||
) | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# frozen_string_literal: true | ||
|
||
require "helper" | ||
|
||
class TestDataHash < JekyllUnitTest | ||
context "Data Hash" do | ||
setup do | ||
@site = fixture_site | ||
@site.read | ||
end | ||
|
||
should "only mimic a ::Hash instance" do | ||
subject = @site.site_data | ||
assert_equal Jekyll::DataHash, subject.class | ||
refute subject.is_a?(Hash) | ||
|
||
copy = subject.dup | ||
assert copy["greetings"]["foo"] | ||
assert_includes copy.dig("greetings", "foo"), "Hello!" | ||
|
||
copy["greetings"] = "Hola!" | ||
assert_equal "Hola!", copy["greetings"] | ||
refute copy["greetings"]["foo"] | ||
|
||
frozen_data_hash = Jekyll::DataHash.new.freeze | ||
assert_raises(FrozenError) { frozen_data_hash["lorem"] = "ipsum" } | ||
end | ||
|
||
should "be mergable" do | ||
alpha = Jekyll::DataHash.new | ||
beta = Jekyll::DataHash.new | ||
|
||
assert_equal "{}", alpha.inspect | ||
sample_data = { "foo" => "bar" } | ||
|
||
assert_equal sample_data["foo"], alpha.merge(sample_data)["foo"] | ||
assert_equal alpha.class, alpha.merge(sample_data).class | ||
assert_empty alpha | ||
|
||
beta.merge!(sample_data) | ||
assert_equal sample_data["foo"], alpha.merge(beta)["foo"] | ||
assert_equal alpha.class, alpha.merge(beta).class | ||
assert_empty alpha | ||
|
||
beta.merge!(@site.site_data) | ||
assert_equal alpha.class, beta.class | ||
assert_includes beta.dig("greetings", "foo"), "Hello!" | ||
|
||
assert_empty alpha | ||
assert_equal sample_data["foo"], Jekyll::Utils.deep_merge_hashes(alpha, sample_data)["foo"] | ||
assert_includes( | ||
Jekyll::Utils.deep_merge_hashes(alpha, beta).dig("greetings", "foo"), | ||
"Hello!" | ||
) | ||
end | ||
end | ||
end |