Add ability to render drops as JSON

Previously you could do, e.g. {{ site | jsonify }}, but with the introduction of Liquid Drops, this didn't work anymore.
This PR adds the ability to render drops as JSON. You can safely run drop.to_json and it should Do the Right Thing.
This commit is contained in:
Parker Moore 2016-05-19 11:01:09 -07:00 committed by Pat Hawks
parent e02049727b
commit 17d8c96a63
6 changed files with 145 additions and 3 deletions

View File

@ -5,10 +5,12 @@ module Jekyll
class DocumentDrop < Drop class DocumentDrop < Drop
extend Forwardable extend Forwardable
NESTED_OBJECT_FIELD_BLACKLIST = %w{
content output excerpt next previous
}.freeze
mutable false mutable false
def_delegator :@obj, :next_doc, :next
def_delegator :@obj, :previous_doc, :previous
def_delegator :@obj, :relative_path, :path def_delegator :@obj, :relative_path, :path
def_delegators :@obj, :id, :output, :content, :to_s, :relative_path, :url def_delegators :@obj, :id, :output, :content, :to_s, :relative_path, :url
@ -27,6 +29,37 @@ module Jekyll
cmp cmp
end end
def previous
@obj.previous_doc.to_liquid
end
def next
@obj.next_doc.to_liquid
end
# Generate a Hash for use in generating JSON.
# This is useful if fields need to be cleared before the JSON can generate.
#
# Returns a Hash ready for JSON generation.
def hash_for_json(state = nil)
to_h.tap do |hash|
if state && state.depth >= 2
hash["previous"] = collapse_document(hash["previous"]) if hash["previous"]
hash["next"] = collapse_document(hash["next"]) if hash["next"]
end
end
end
# Generate a Hash which breaks the recursive chain.
# Certain fields which are normally available are omitted.
#
# Returns a Hash with only non-recursive fields present.
def collapse_document(doc)
doc.keys.each_with_object({}) do |(key, _), result|
result[key] = doc[key] unless NESTED_OBJECT_FIELD_BLACKLIST.include?(key)
end
end
private private
def_delegator :@obj, :data, :fallback_data def_delegator :@obj, :data, :fallback_data
end end

View File

@ -3,7 +3,9 @@
module Jekyll module Jekyll
module Drops module Drops
class Drop < Liquid::Drop class Drop < Liquid::Drop
NON_CONTENT_METHODS = [:[], :[]=, :inspect, :to_h, :fallback_data].freeze include Enumerable
NON_CONTENT_METHODS = [:[], :[]=, :inspect, :to_h, :fallback_data, :collapse_document].freeze
# Get or set whether the drop class is mutable. # Get or set whether the drop class is mutable.
# Mutability determines whether or not pre-defined fields may be # Mutability determines whether or not pre-defined fields may be
@ -138,6 +140,22 @@ module Jekyll
JSON.pretty_generate to_h JSON.pretty_generate to_h
end end
# Generate a Hash for use in generating JSON.
# This is useful if fields need to be cleared before the JSON can generate.
#
# Returns a Hash ready for JSON generation.
def hash_for_json(state = nil)
to_h
end
# Generate a JSON representation of the Drop.
#
# Returns a JSON representation of the Drop in a String.
def to_json(state = nil)
require 'json'
JSON.generate(hash_for_json(state), state)
end
# Collects all the keys and passes each to the block in turn. # Collects all the keys and passes each to the block in turn.
# #
# block - a block which accepts one argument, the key # block - a block which accepts one argument, the key

View File

@ -16,6 +16,18 @@ module Jekyll
def environment def environment
Jekyll.env Jekyll.env
end end
def to_h
@to_h ||= {
"version" => version,
"environment" => environment
}
end
def to_json(state = nil)
require 'json'
JSON.generate(to_h, state)
end
end end
end end
end end

View File

@ -279,6 +279,7 @@ module Jekyll
def site_payload def site_payload
Drops::UnifiedPayloadDrop.new self Drops::UnifiedPayloadDrop.new self
end end
alias_method :to_liquid, :site_payload
# Get the implementation class for the given Converter. # Get the implementation class for the given Converter.
# Returns the Converter instance implementing the given Converter. # Returns the Converter instance implementing the given Converter.

View File

@ -44,6 +44,46 @@ class TestDocument < JekyllUnitTest
assert_equal "foo.bar", @document.data["whatever"] assert_equal "foo.bar", @document.data["whatever"]
end end
should "be jsonify-able" do
page_json = @document.to_liquid.to_json
parsed = JSON.parse(page_json)
assert_equal "Jekyll.configuration", parsed["title"]
assert_equal "foo.bar", parsed["whatever"]
assert_equal nil, parsed["previous"]
next_doc = parsed["next"]
assert_equal "_methods/escape-+ #%20[].md", next_doc["path"]
assert_equal "Jekyll.escape", next_doc["title"]
next_prev_doc = next_doc["previous"]
assert_equal "Jekyll.configuration", next_prev_doc["title"]
assert_equal "_methods/configuration.md", next_prev_doc["path"]
assert_equal "_methods/escape-+ #%20[].md", next_prev_doc["next"]["path"]
assert_nil next_prev_doc["previous"] # nothing before Jekyll.configuration
assert_nil next_prev_doc["next"]["next"]
assert_nil next_prev_doc["next"]["previous"]
assert_nil next_prev_doc["next"]["content"]
assert_nil next_prev_doc["next"]["excerpt"]
assert_nil next_prev_doc["next"]["output"]
next_next_doc = next_doc["next"]
assert_equal "Jekyll.sanitized_path", next_next_doc["title"]
assert_equal "_methods/sanitized_path.md", next_next_doc["path"]
assert_equal "_methods/escape-+ #%20[].md", next_next_doc["previous"]["path"]
assert_equal "_methods/site/generate.md", next_next_doc["next"]["path"]
assert_nil next_next_doc["next"]["next"]
assert_nil next_next_doc["next"]["previous"]
assert_nil next_next_doc["next"]["content"]
assert_nil next_next_doc["next"]["excerpt"]
assert_nil next_next_doc["next"]["output"]
assert_nil next_next_doc["previous"]["next"]
assert_nil next_next_doc["previous"]["previous"]
assert_nil next_next_doc["previous"]["content"]
assert_nil next_next_doc["previous"]["excerpt"]
assert_nil next_next_doc["previous"]["output"]
end
context "with YAML ending in three dots" do context "with YAML ending in three dots" do
setup do setup do

View File

@ -198,6 +198,44 @@ class TestFilters < JekyllUnitTest
assert_equal "[{\"name\":\"Jack\"},{\"name\":\"Smith\"}]", @filter.jsonify([{:name => 'Jack'}, {:name => 'Smith'}]) assert_equal "[{\"name\":\"Jack\"},{\"name\":\"Smith\"}]", @filter.jsonify([{:name => 'Jack'}, {:name => 'Smith'}])
end end
should "convert drop to json" do
@filter.site.read
expected = {
"next" => "Categories _should_ work",
"path" => "_posts/2008-02-02-published.markdown",
"output" => nil,
"previous" => nil,
"content" => "This should be published.\n",
"id" => "/publish_test/2008/02/02/published",
"url" => "/publish_test/2008/02/02/published.html",
"relative_path" => "_posts/2008-02-02-published.markdown",
"collection" => "posts",
"excerpt" => "<p>This should be published.</p>\n",
"draft" => false,
"categories" => [
"publish_test"
],
"layout" => "default",
"title" => "Publish",
"category" => "publish_test",
"date" => "2008-02-02 00:00:00 +0000",
"slug" => "published",
"ext" => ".markdown",
"tags" => []
}
actual = @filter.jsonify(@filter.site.docs_to_write.first.to_liquid)
assert_equal expected, JSON.parse(actual)
end
should "convert drop with drops to json" do
@filter.site.read
actual = @filter.jsonify(@filter.site.to_liquid)
assert_equal JSON.parse(actual)["jekyll"], {
"environment" => "development",
"version" => Jekyll::VERSION
}
end
class M < Struct.new(:message) class M < Struct.new(:message)
def to_liquid def to_liquid
[message] [message]