From 82c3ee365f5a8b948ce2ea853d75a954fa709939 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Mon, 21 Dec 2015 22:45:26 -0500 Subject: [PATCH] Initial work on using Liquid::Drops instead of Hashes. The properties of Liquid::Drops are only evaluated when they're asked for and therefore save computation time. This prevents a lot of GC time cleaning up objects that are not needed, because they're not created unless requested. Additionally, this saves time for actual computation of those values because they can be computed only if needed. It's funny how much it helps when you only do what is needed. Far less overhead. --- lib/jekyll.rb | 1 + lib/jekyll/collection.rb | 9 +--- lib/jekyll/convertible.rb | 15 +++---- lib/jekyll/document.rb | 42 +------------------ lib/jekyll/drops/collection_drop.rb | 22 ++++++++++ lib/jekyll/drops/document_drop.rb | 50 +++++++++++++++++++++++ lib/jekyll/drops/immutable_drop.rb | 29 +++++++++++++ lib/jekyll/drops/jekyll_drop.rb | 21 ++++++++++ lib/jekyll/drops/mutable_drop.rb | 28 +++++++++++++ lib/jekyll/drops/site_drop.rb | 38 +++++++++++++++++ lib/jekyll/drops/unified_payload_drop.rb | 24 +++++++++++ lib/jekyll/drops/url_drop.rb | 52 ++++++++++++++++++++++++ lib/jekyll/page.rb | 8 ++-- lib/jekyll/renderer.rb | 31 ++++++-------- lib/jekyll/site.rb | 20 +-------- lib/jekyll/url.rb | 14 +++++++ 16 files changed, 303 insertions(+), 101 deletions(-) create mode 100644 lib/jekyll/drops/collection_drop.rb create mode 100644 lib/jekyll/drops/document_drop.rb create mode 100644 lib/jekyll/drops/immutable_drop.rb create mode 100644 lib/jekyll/drops/jekyll_drop.rb create mode 100644 lib/jekyll/drops/mutable_drop.rb create mode 100644 lib/jekyll/drops/site_drop.rb create mode 100644 lib/jekyll/drops/unified_payload_drop.rb create mode 100644 lib/jekyll/drops/url_drop.rb diff --git a/lib/jekyll.rb b/lib/jekyll.rb index 3e8e639d..a90a5a0b 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -172,6 +172,7 @@ end require_all 'jekyll/commands' require_all 'jekyll/converters' require_all 'jekyll/converters/markdown' +require_all 'jekyll/drops' require_all 'jekyll/generators' require_all 'jekyll/tags' diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index c4a1f456..f74ccf6f 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -170,14 +170,7 @@ module Jekyll # # Returns a representation of this collection for use in Liquid. def to_liquid - metadata.merge({ - "label" => label, - "docs" => docs, - "files" => files, - "directory" => directory, - "output" => write?, - "relative_directory" => relative_directory - }) + Drops::CollectionDrop.new self end # Whether the collection's documents ought to be written as individual diff --git a/lib/jekyll/convertible.rb b/lib/jekyll/convertible.rb index 016ff245..db34307a 100644 --- a/lib/jekyll/convertible.rb +++ b/lib/jekyll/convertible.rb @@ -210,13 +210,8 @@ module Jekyll while layout Jekyll.logger.debug "Rendering Layout:", path - payload = Utils.deep_merge_hashes( - payload, - { - "content" => output, - "layout" => layout.data - } - ) + payload.content = output + payload.layout = layout.data self.output = render_liquid(layout.content, payload, @@ -250,11 +245,11 @@ module Jekyll Jekyll.logger.debug "Pre-Render Hooks:", self.relative_path Jekyll::Hooks.trigger hook_owner, :pre_render, self, payload - info = { :filters => [Jekyll::Filters], :registers => { :site => site, :page => payload['page'] } } + info = { :filters => [Jekyll::Filters], :registers => { :site => site, :page => payload.page } } # render and transform content (this becomes the final content of the object) - payload["highlighter_prefix"] = converters.first.highlighter_prefix - payload["highlighter_suffix"] = converters.first.highlighter_suffix + payload.highlighter_prefix = converters.first.highlighter_prefix + payload.highlighter_suffix = converters.first.highlighter_suffix if render_with_liquid? Jekyll.logger.debug "Rendering Liquid:", self.relative_path diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index 528fed68..622f1180 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -38,12 +38,10 @@ module Jekyll end def output=(output) - @to_liquid = nil @output = output end def content=(content) - @to_liquid = nil @content = content end @@ -181,27 +179,7 @@ module Jekyll # # Returns the Hash of key-value pairs for replacement in the URL. def url_placeholders - { - collection: collection.label, - path: cleaned_relative_path, - output_ext: output_ext, - name: Utils.slugify(basename_without_ext), - title: Utils.slugify(data['slug'], mode: "pretty", cased: true) || Utils - .slugify(basename_without_ext, mode: "pretty", cased: true), - slug: Utils.slugify(data['slug']) || Utils.slugify(basename_without_ext), - year: date.strftime("%Y"), - month: date.strftime("%m"), - day: date.strftime("%d"), - hour: date.strftime("%H"), - minute: date.strftime("%M"), - second: date.strftime("%S"), - i_day: date.strftime("%-d"), - i_month: date.strftime("%-m"), - categories: (data['categories'] || []).map { |c| c.to_s.downcase }.uniq.join('/'), - short_month: date.strftime("%b"), - short_year: date.strftime("%y"), - y_day: date.strftime("%j"), - } + @url_placeholders ||= Drops::UrlDrop.new(self) end # The permalink for this Document. @@ -278,8 +256,6 @@ module Jekyll # # Returns nothing. def read(opts = {}) - @to_liquid = nil - Jekyll.logger.debug "Reading:", relative_path if yaml_file? @@ -353,21 +329,7 @@ module Jekyll # # Returns a Hash representing this Document's data. def to_liquid - @to_liquid ||= if data.is_a?(Hash) - Utils.deep_merge_hashes Utils.deep_merge_hashes({ - "output" => output, - "content" => content, - "relative_path" => relative_path, - "path" => relative_path, - "url" => url, - "collection" => collection.label, - "next" => next_doc, - "previous" => previous_doc, - "id" => id, - }, data), { 'excerpt' => data['excerpt'].to_s } - else - data - end + @to_liquid ||= Drops::DocumentDrop.new(self) end # The inspect string for this document. diff --git a/lib/jekyll/drops/collection_drop.rb b/lib/jekyll/drops/collection_drop.rb new file mode 100644 index 00000000..30c9f865 --- /dev/null +++ b/lib/jekyll/drops/collection_drop.rb @@ -0,0 +1,22 @@ +# encoding: UTF-8 +require "jekyll/drops/immutable_drop" + +module Jekyll + module Drops + class CollectionDrop < ImmutableDrop + extend Forwardable + + def_delegators :@obj, :label, :docs, :files, :directory, :relative_directory + + def output + @obj.write? + end + + private + def data + @obj.metadata + end + + end + end +end diff --git a/lib/jekyll/drops/document_drop.rb b/lib/jekyll/drops/document_drop.rb new file mode 100644 index 00000000..4d7207d5 --- /dev/null +++ b/lib/jekyll/drops/document_drop.rb @@ -0,0 +1,50 @@ +# encoding: UTF-8 + +module Jekyll + module Drops + class DocumentDrop < ImmutableDrop + + def output + @obj.output + end + + def content + @obj.content + end + + def relative_path + @obj.relative_path + end + alias_method :path, :relative_path + + def url + @obj.url + end + + def collection + @obj.collection.label + end + + def next + @obj.next_doc + end + + def previous + @obj.previous_doc + end + + def id + @obj.id + end + + def excerpt + data['excerpt'].to_s + end + + def data + @obj.data + end + + end + end +end diff --git a/lib/jekyll/drops/immutable_drop.rb b/lib/jekyll/drops/immutable_drop.rb new file mode 100644 index 00000000..51257cff --- /dev/null +++ b/lib/jekyll/drops/immutable_drop.rb @@ -0,0 +1,29 @@ +# encoding: UTF-8 + +module Jekyll + module Drops + class ImmutableDrop < Liquid::Drop + + def initialize(obj) + @obj = obj + end + + def [](key) + if respond_to? key + public_send key + else + data[key] + end + end + + def []=(key, val) + if respond_to? key + raise ArgumentError.new("Key #{key} cannot be set in the drop.") + else + data[key] = val + end + end + + end + end +end diff --git a/lib/jekyll/drops/jekyll_drop.rb b/lib/jekyll/drops/jekyll_drop.rb new file mode 100644 index 00000000..c4009da0 --- /dev/null +++ b/lib/jekyll/drops/jekyll_drop.rb @@ -0,0 +1,21 @@ +# encoding: UTF-8 + +module Jekyll + module Drops + class JekyllDrop < Liquid::Drop + class << self + def global + @global ||= JekyllDrop.new + end + end + + def version + Jekyll::VERSION + end + + def environment + Jekyll.env + end + end + end +end diff --git a/lib/jekyll/drops/mutable_drop.rb b/lib/jekyll/drops/mutable_drop.rb new file mode 100644 index 00000000..1551728e --- /dev/null +++ b/lib/jekyll/drops/mutable_drop.rb @@ -0,0 +1,28 @@ +# encoding: UTF-8 + +module Jekyll + module Drops + class MutableDrop < Liquid::Drop + + def initialize(obj) + @obj = obj + @mutations = {} + end + + def [](key) + if @mutations.key? key + @mutations[key] + elsif respond_to? key + public_send key + else + data[key] + end + end + + def []=(key, val) + @mutations[key] = val + end + + end + end +end diff --git a/lib/jekyll/drops/site_drop.rb b/lib/jekyll/drops/site_drop.rb new file mode 100644 index 00000000..77d6a416 --- /dev/null +++ b/lib/jekyll/drops/site_drop.rb @@ -0,0 +1,38 @@ +# encoding: UTF-8 + +module Jekyll + module Drops + class SiteDrop < ImmutableDrop + extend Forwardable + + def_delegator :@obj, :site_data, :data + def_delegators :@obj, :time, :pages, :static_files, :documents + + def posts + @site_posts ||= @obj.posts.docs.sort { |a, b| b <=> a } + end + + def html_pages + @site_html_pages ||= @obj.pages.select { |page| page.html? || page.url.end_with?("/") } + end + + def categories + @site_categories ||= @obj.post_attr_hash('categories') + end + + def tags + @site_tags ||= @obj.post_attr_hash('tags') + end + + def collections + @site_collections ||= @obj.collections.values.map(&:to_liquid) + end + + private + def data + @obj.config + end + + end + end +end diff --git a/lib/jekyll/drops/unified_payload_drop.rb b/lib/jekyll/drops/unified_payload_drop.rb new file mode 100644 index 00000000..65733b20 --- /dev/null +++ b/lib/jekyll/drops/unified_payload_drop.rb @@ -0,0 +1,24 @@ +# encoding: UTF-8 + +module Jekyll + module Drops + class UnifiedPayloadDrop < Liquid::Drop + + attr_accessor :page, :layout, :content, :paginator + attr_accessor :highlighter_prefix, :highlighter_suffix + + def initialize(site) + @site = site + end + + def jekyll + JekyllDrop.global + end + + def site + @site_drop ||= SiteDrop.new(@site) + end + + end + end +end diff --git a/lib/jekyll/drops/url_drop.rb b/lib/jekyll/drops/url_drop.rb new file mode 100644 index 00000000..047acacf --- /dev/null +++ b/lib/jekyll/drops/url_drop.rb @@ -0,0 +1,52 @@ +# encoding: UTF-8 + +module Jekyll + module Drops + class UrlDrop < ImmutableDrop + def collection + @obj.collection.label + end + + def path + @obj.cleaned_relative_path + end + + def output_ext + @obj.output_ext + end + + def name + Utils.slugify(@obj.basename_without_ext) + end + + def title + Utils.slugify(@obj.data['slug'], mode: "pretty", cased: true) || + Utils.slugify(@obj.basename_without_ext, mode: "pretty", cased: true) + end + + def slug + Utils.slugify(@obj.data['slug']) || Utils.slugify(@obj.basename_without_ext) + end + + def categories + category_set = Set.new + Array(@obj.data['categories']).each do |category| + category_set << category.to_s.downcase + end + category_set.to_a.join('/') + end + + def year; @obj.date.strftime("%Y"); end + def month; @obj.date.strftime("%m"); end + def day; @obj.date.strftime("%d"); end + def hour; @obj.date.strftime("%H"); end + def minute; @obj.date.strftime("%M"); end + def second; @obj.date.strftime("%S"); end + def i_day; @obj.date.strftime("%-d"); end + def i_month; @obj.date.strftime("%-m"); end + def short_month; @obj.date.strftime("%b"); end + def short_year; @obj.date.strftime("%y"); end + def y_day; @obj.date.strftime("%j"); end + end + end +end diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb index a62a9940..0644e1b6 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -117,12 +117,10 @@ module Jekyll # # Returns nothing. def render(layouts, site_payload) - payload = Utils.deep_merge_hashes({ - "page" => to_liquid, - 'paginator' => pager.to_liquid - }, site_payload) + site_payload.page = to_liquid + site_payload.paginator = pager.to_liquid - do_layout(payload, layouts) + do_layout(site_payload, layouts) end # The path to the source file diff --git a/lib/jekyll/renderer.rb b/lib/jekyll/renderer.rb index 529ff84b..09763cca 100644 --- a/lib/jekyll/renderer.rb +++ b/lib/jekyll/renderer.rb @@ -3,12 +3,12 @@ module Jekyll class Renderer - attr_reader :document, :site, :site_payload + attr_reader :document, :site, :payload def initialize(site, document, site_payload = nil) - @site = site - @document = document - @site_payload = site_payload + @site = site + @document = document + @payload = site_payload || site.site_payload end # Determine which converters to use based on this document's @@ -33,12 +33,10 @@ module Jekyll def run Jekyll.logger.debug "Rendering:", document.relative_path - payload = Utils.deep_merge_hashes({ - "page" => document.to_liquid - }, site_payload || site.site_payload) + payload.page = document.to_liquid if document.collection.label == 'posts' && document.is_a?(Document) - payload['site']['related_posts'] = document.related_posts + payload.site['related_posts'] = document.related_posts end Jekyll.logger.debug "Pre-Render Hooks:", document.relative_path @@ -46,12 +44,12 @@ module Jekyll info = { filters: [Jekyll::Filters], - registers: { :site => site, :page => payload['page'] } + registers: { :site => site, :page => payload.page } } # render and transform content (this becomes the final content of the object) - payload["highlighter_prefix"] = converters.first.highlighter_prefix - payload["highlighter_suffix"] = converters.first.highlighter_suffix + payload.highlighter_prefix = converters.first.highlighter_prefix + payload.highlighter_suffix = converters.first.highlighter_suffix output = document.content @@ -135,14 +133,9 @@ module Jekyll used = Set.new([layout]) while layout - payload = Utils.deep_merge_hashes( - payload, - { - "content" => output, - "page" => document.to_liquid, - "layout" => layout.data - } - ) + payload.content = output + payload.page = document.to_liquid + payload.layout = layout.data output = render_liquid( layout.content, diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 5e9402f4..4dc3ac84 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -259,25 +259,7 @@ module Jekyll # "tags" - The Hash of tag values and Posts. # See Site#post_attr_hash for type info. def site_payload - { - "jekyll" => { - "version" => Jekyll::VERSION, - "environment" => Jekyll.env - }, - "site" => Utils.deep_merge_hashes(config, - Utils.deep_merge_hashes(Hash[collections.map{|label, coll| [label, coll.docs]}], { - "time" => time, - "posts" => posts.docs.sort { |a, b| b <=> a }, - "pages" => pages, - "static_files" => static_files, - "html_pages" => pages.select { |page| page.html? || page.url.end_with?("/") }, - "categories" => post_attr_hash('categories'), - "tags" => post_attr_hash('tags'), - "collections" => collections.values.map(&:to_liquid), - "documents" => documents, - "data" => site_data - })) - } + Drops::UnifiedPayloadDrop.new self end # Get the implementation class for the given Converter. diff --git a/lib/jekyll/url.rb b/lib/jekyll/url.rb index 6e8042b0..b338c23a 100644 --- a/lib/jekyll/url.rb +++ b/lib/jekyll/url.rb @@ -59,6 +59,14 @@ module Jekyll # # Returns the unsanitized String URL def generate_url(template) + if @placeholders.is_a? Drops::UrlDrop + generate_url_from_drop(template) + else + generate_url_from_hash(template) + end + end + + def generate_url_from_hash(template) @placeholders.inject(template) do |result, token| break result if result.index(':').nil? if token.last.nil? @@ -70,6 +78,12 @@ module Jekyll end end + def generate_url_from_drop(template) + template.gsub(/(:[a-z_]+)/) do |match| + @placeholders.public_send(match.sub(':', '')) + end.gsub(/\/\//, '/') + end + # Returns a sanitized String URL, stripping "../../" and multiples of "/", # as well as the beginning "/" so we can enforce and ensure it.