From 37e8a16d68aa94273507007f6440a092db60fb45 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Wed, 19 Feb 2020 14:07:28 +0530 Subject: [PATCH 1/3] Subclass Kramdown::Document to reduce allocations --- .../converters/markdown/kramdown_parser.rb | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/jekyll/converters/markdown/kramdown_parser.rb b/lib/jekyll/converters/markdown/kramdown_parser.rb index 64bb1d1c8d2..5f71e46eca7 100644 --- a/lib/jekyll/converters/markdown/kramdown_parser.rb +++ b/lib/jekyll/converters/markdown/kramdown_parser.rb @@ -1,5 +1,55 @@ # Frozen-string-literal: true +module Kramdown + # A Kramdown::Document subclass meant to optimize memory usage from initializing + # a kramdown document for parsing. + # + # The optimization is by using the same options Hash (and its derivatives) for + # converting all Markdown documents in a Jekyll site. + class JekyllDocument < Document + class << self + attr_reader :options, :parser + + # The implementation is basically the core logic in +Kramdown::Document#initialize+ + # + # rubocop:disable Naming/MemoizedInstanceVariableName + def setup(options) + @options ||= Options.merge(options).freeze + @parser ||= begin + parser_name = (@options[:input] || "kramdown").to_s + parser_name = parser_name[0..0].upcase + parser_name[1..-1] + try_require("parser", parser_name) + + if Parser.const_defined?(parser_name) + Parser.const_get(parser_name) + else + raise Kramdown::Error, "kramdown has no parser to handle the specified " \ + "input format: #{@options[:input]}" + end + end + end + # rubocop:enable Naming/MemoizedInstanceVariableName + + private + + def try_require(type, name) + require "kramdown/#{type}/#{Utils.snake_case(name)}" + rescue LoadError + false + end + end + + def initialize(source, options = {}) + JekyllDocument.setup(options) + + @options = JekyllDocument.options + @root, @warnings = JekyllDocument.parser.parse(source, @options) + end + end +end + +# + module Jekyll module Converters class Markdown @@ -37,7 +87,7 @@ def setup end def convert(content) - document = Kramdown::Document.new(content, @config) + document = Kramdown::JekyllDocument.new(content, @config) html_output = document.to_html if @config["show_warnings"] document.warnings.each do |warning| From d66facb4972a245052ea8d6446140ffd5ec4b298 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Wed, 19 Feb 2020 15:46:21 +0530 Subject: [PATCH 2/3] Reset variables if set up with different options --- lib/jekyll/converters/markdown/kramdown_parser.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/jekyll/converters/markdown/kramdown_parser.rb b/lib/jekyll/converters/markdown/kramdown_parser.rb index 5f71e46eca7..0c64605ef7e 100644 --- a/lib/jekyll/converters/markdown/kramdown_parser.rb +++ b/lib/jekyll/converters/markdown/kramdown_parser.rb @@ -14,6 +14,11 @@ class << self # # rubocop:disable Naming/MemoizedInstanceVariableName def setup(options) + @cache ||= {} + @cache[:id] ||= options.hash + + # reset variables on a subsequent set up with a different options Hash + reset! unless @cache[:id] == options.hash @options ||= Options.merge(options).freeze @parser ||= begin parser_name = (@options[:input] || "kramdown").to_s @@ -32,6 +37,10 @@ def setup(options) private + def reset! + @options = @parser = @cache[:id] = nil + end + def try_require(type, name) require "kramdown/#{type}/#{Utils.snake_case(name)}" rescue LoadError From 73c05aae8abcaa845f42d34d91234e3e347bb3f9 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Wed, 19 Feb 2020 23:37:56 +0530 Subject: [PATCH 3/3] Reset variables if set up with different options --- lib/jekyll/converters/markdown/kramdown_parser.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/jekyll/converters/markdown/kramdown_parser.rb b/lib/jekyll/converters/markdown/kramdown_parser.rb index 0c64605ef7e..aebd6ee7fef 100644 --- a/lib/jekyll/converters/markdown/kramdown_parser.rb +++ b/lib/jekyll/converters/markdown/kramdown_parser.rb @@ -15,10 +15,13 @@ class << self # rubocop:disable Naming/MemoizedInstanceVariableName def setup(options) @cache ||= {} - @cache[:id] ||= options.hash # reset variables on a subsequent set up with a different options Hash - reset! unless @cache[:id] == options.hash + unless @cache[:id] == options.hash + @options = @parser = nil + @cache[:id] = options.hash + end + @options ||= Options.merge(options).freeze @parser ||= begin parser_name = (@options[:input] || "kramdown").to_s @@ -37,10 +40,6 @@ def setup(options) private - def reset! - @options = @parser = @cache[:id] = nil - end - def try_require(type, name) require "kramdown/#{type}/#{Utils.snake_case(name)}" rescue LoadError