diff --git a/lib/jekyll/drops/document_drop.rb b/lib/jekyll/drops/document_drop.rb index 4eb9e50c..b8a0d3e5 100644 --- a/lib/jekyll/drops/document_drop.rb +++ b/lib/jekyll/drops/document_drop.rb @@ -5,10 +5,12 @@ module Jekyll class DocumentDrop < Drop extend Forwardable + NESTED_OBJECT_FIELD_BLACKLIST = %w{ + content output excerpt next previous + }.freeze + mutable false - def_delegator :@obj, :next_doc, :next - def_delegator :@obj, :previous_doc, :previous def_delegator :@obj, :relative_path, :path def_delegators :@obj, :id, :output, :content, :to_s, :relative_path, :url @@ -27,6 +29,37 @@ module Jekyll cmp 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 def_delegator :@obj, :data, :fallback_data end diff --git a/lib/jekyll/drops/drop.rb b/lib/jekyll/drops/drop.rb index d1bffcc5..23ebeb4b 100644 --- a/lib/jekyll/drops/drop.rb +++ b/lib/jekyll/drops/drop.rb @@ -3,7 +3,9 @@ module Jekyll module Drops 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. # Mutability determines whether or not pre-defined fields may be @@ -138,6 +140,22 @@ module Jekyll JSON.pretty_generate to_h 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. # # block - a block which accepts one argument, the key diff --git a/lib/jekyll/drops/jekyll_drop.rb b/lib/jekyll/drops/jekyll_drop.rb index c4009da0..7ff19f9a 100644 --- a/lib/jekyll/drops/jekyll_drop.rb +++ b/lib/jekyll/drops/jekyll_drop.rb @@ -16,6 +16,18 @@ module Jekyll def environment Jekyll.env 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 diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 9afdbde3..f2025c13 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -279,6 +279,7 @@ module Jekyll def site_payload Drops::UnifiedPayloadDrop.new self end + alias_method :to_liquid, :site_payload # Get the implementation class for the given Converter. # Returns the Converter instance implementing the given Converter. diff --git a/test/test_document.rb b/test/test_document.rb index b9e69a7d..c0b6311c 100644 --- a/test/test_document.rb +++ b/test/test_document.rb @@ -44,6 +44,46 @@ class TestDocument < JekyllUnitTest assert_equal "foo.bar", @document.data["whatever"] 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 setup do diff --git a/test/test_filters.rb b/test/test_filters.rb index 565e8208..5015184d 100644 --- a/test/test_filters.rb +++ b/test/test_filters.rb @@ -198,6 +198,44 @@ class TestFilters < JekyllUnitTest assert_equal "[{\"name\":\"Jack\"},{\"name\":\"Smith\"}]", @filter.jsonify([{:name => 'Jack'}, {:name => 'Smith'}]) 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" => "
This should be published.
\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) def to_liquid [message]