Modernize Kramdown for Markdown converter.

This commit is contained in:
Jordon Bedwell 2015-11-03 19:48:29 -06:00
parent b01b089f69
commit 3432fd2c2d
6 changed files with 173 additions and 74 deletions

View File

@ -18,8 +18,9 @@ group :test do
gem 'jekyll_test_plugin_malicious' gem 'jekyll_test_plugin_malicious'
gem 'minitest-reporters' gem 'minitest-reporters'
gem 'minitest-profile' gem 'minitest-profile'
gem 'minitest'
gem 'rspec-mocks' gem 'rspec-mocks'
gem 'minitest'
gem "nokogiri"
if RUBY_PLATFORM =~ /cygwin/ || RUBY_VERSION.start_with?("2.2") if RUBY_PLATFORM =~ /cygwin/ || RUBY_VERSION.start_with?("2.2")
gem 'test-unit' gem 'test-unit'

View File

@ -68,17 +68,7 @@ module Jekyll
'footnote_nr' => 1, 'footnote_nr' => 1,
'entity_output' => 'as_char', 'entity_output' => 'as_char',
'toc_levels' => '1..6', 'toc_levels' => '1..6',
'smart_quotes' => 'lsquo,rsquo,ldquo,rdquo', 'smart_quotes' => 'lsquo,rsquo,ldquo,rdquo'
'enable_coderay' => false,
'coderay' => {
'coderay_wrap' => 'div',
'coderay_line_numbers' => 'inline',
'coderay_line_number_start' => 1,
'coderay_tab_width' => 4,
'coderay_bold_every' => 10,
'coderay_css' => 'style'
}
} }
}] }]

View File

@ -1,33 +1,86 @@
# Frozen-string-literal: true
# Encoding: utf-8
module Jekyll module Jekyll
module Converters module Converters
class Markdown class Markdown
class KramdownParser class KramdownParser
CODERAY_DEFAULTS = {
"css" => "style",
"bold_every" => 10,
"line_numbers" => "inline",
"line_number_start" => 1,
"tab_width" => 4,
"wrap" => "div"
}
def initialize(config) def initialize(config)
require 'kramdown' Jekyll::External.require_with_graceful_fail "kramdown"
@config = config @main_fallback_highlighter = config["highlighter"] || "rogue"
# If kramdown supported highlighter enabled, use that @config = config["kramdown"] || {}
highlighter = @config['highlighter'] setup
if highlighter == 'rouge' || highlighter == 'coderay' end
@config['kramdown']['syntax_highlighter'] ||= highlighter
end # Setup and normalize the configuration:
rescue LoadError # * Create Kramdown if it doesn't exist.
STDERR.puts 'You are missing a library required for Markdown. Please run:' # * Set syntax_highlighter, detecting enable_coderay and merging highlighter if none.
STDERR.puts ' $ [sudo] gem install kramdown' # * Merge kramdown[coderay] into syntax_highlighter_opts stripping coderay_.
raise Errors::FatalException.new("Missing dependency: kramdown") # * Make sure `syntax_highlighter_opts` exists.
def setup
@config["syntax_highlighter"] ||= highlighter
@config["syntax_highlighter_opts"] ||= {}
@config["coderay"] ||= {} # XXX: Legacy.
modernize_coderay_config
end end
def convert(content) def convert(content)
# Check for use of coderay Kramdown::Document.new(content, @config).to_html
if @config['kramdown']['enable_coderay']
%w[wrap line_numbers line_numbers_start tab_width bold_every css default_lang].each do |opt|
key = "coderay_#{opt}"
@config['kramdown'][key] = @config['kramdown']['coderay'][key] unless @config['kramdown'].key?(key)
end
end
Kramdown::Document.new(content, Utils.symbolize_hash_keys(@config['kramdown'])).to_html
end end
# config[kramdown][syntax_higlighter] > config[kramdown][enable_coderay] > config[highlighter]
# Where `enable_coderay` is now deprecated because Kramdown
# supports Rouge now too.
private
def highlighter
@highlighter ||= begin
if highlighter = @config["syntax_highlighter"] then highlighter
elsif @config.key?("enable_coderay") && @config["enable_coderay"]
Jekyll.logger.warn "DEPRECATION: You are using 'enable_coderay', use syntax_highlighter: coderay."
"coderay"
else
@main_fallback_highlighter
end
end
end
private
def strip_coderay_prefix(hash)
hash.each_with_object({}) do |(key, val), hsh|
cleaned_key = key.gsub(/\Acoderay_/, "")
Jekyll.logger.warn "You are using '#{key}'. Normalizing to #{cleaned_key}." if key != cleaned_key
hsh[cleaned_key] = val
end
end
# If our highlighter is CodeRay we go in to merge the CodeRay defaults
# with your "coderay" key if it's there, deprecating it in the
# process of you using it.
private
def modernize_coderay_config
if highlighter == "coderay"
Jekyll.logger.warn "DEPRECATION: kramdown.coderay is deprecated use syntax_highlighter_opts."
@config["syntax_highlighter_opts"] = begin
strip_coderay_prefix(
@config["syntax_highlighter_opts"] \
.merge(CODERAY_DEFAULTS) \
.merge(@config["coderay"])
)
end
end
end
end end
end end
end end

View File

@ -8,6 +8,10 @@ module Jekyll
SLUGIFY_DEFAULT_REGEXP = Regexp.new('[^[:alnum:]]+').freeze SLUGIFY_DEFAULT_REGEXP = Regexp.new('[^[:alnum:]]+').freeze
SLUGIFY_PRETTY_REGEXP = Regexp.new("[^[:alnum:]._~!$&'()+,;=@]+").freeze SLUGIFY_PRETTY_REGEXP = Regexp.new("[^[:alnum:]._~!$&'()+,;=@]+").freeze
def strip_heredoc(str)
str.gsub(/^[ \t]{#{(str.scan(/^[ \t]*(?=\S)/).min || "").size}}/, "")
end
# Non-destructive version of deep_merge_hashes! See that method. # Non-destructive version of deep_merge_hashes! See that method.
# #
# Returns the merged hashes. # Returns the merged hashes.

View File

@ -11,13 +11,13 @@ unless ENV['TRAVIS']
end end
end end
require "nokogiri"
require 'rubygems' require 'rubygems'
require 'ostruct' require 'ostruct'
require 'minitest/autorun' require 'minitest/autorun'
require 'minitest/reporters' require 'minitest/reporters'
require 'minitest/profile' require 'minitest/profile'
require 'rspec/mocks' require 'rspec/mocks'
require 'jekyll' require 'jekyll'
Jekyll.logger = Logger.new(StringIO.new) Jekyll.logger = Logger.new(StringIO.new)
@ -51,15 +51,15 @@ class JekyllUnitTest < Minitest::Test
end end
def before_setup def before_setup
::RSpec::Mocks.setup RSpec::Mocks.setup
super super
end end
def after_teardown def after_teardown
super super
::RSpec::Mocks.verify RSpec::Mocks.verify
ensure ensure
::RSpec::Mocks.teardown RSpec::Mocks.teardown
end end
def fixture_site(overrides = {}) def fixture_site(overrides = {})
@ -120,4 +120,10 @@ class JekyllUnitTest < Minitest::Test
end end
alias_method :capture_stdout, :capture_output alias_method :capture_stdout, :capture_output
alias_method :capture_stderr, :capture_output alias_method :capture_stderr, :capture_output
def nokogiri_fragment(str)
Nokogiri::HTML.fragment(
str
)
end
end end

View File

@ -8,65 +8,110 @@ class TestKramdown < JekyllUnitTest
@config = { @config = {
'markdown' => 'kramdown', 'markdown' => 'kramdown',
'kramdown' => { 'kramdown' => {
'auto_ids' => false, 'smart_quotes' => 'lsquo,rsquo,ldquo,rdquo',
'footnote_nr' => 1,
'entity_output' => 'as_char', 'entity_output' => 'as_char',
'toc_levels' => '1..6', 'toc_levels' => '1..6',
'smart_quotes' => 'lsquo,rsquo,ldquo,rdquo', 'auto_ids' => false,
'footnote_nr' => 1,
'enable_coderay' => true, 'syntax_highlighter_opts' => {
'coderay_bold_every'=> 12, 'bold_every' => 8, 'css' => :class
'coderay' => {
'coderay_css' => :style,
'coderay_bold_every' => 8
} }
} }
} }
@config = Jekyll.configuration(@config) @config = Jekyll.configuration(@config)
@markdown = Converters::Markdown.new(@config) @markdown = Converters::Markdown.new(
@config
)
end end
# http://kramdown.gettalong.org/converter/html.html#options should "run Kramdown" do
should "pass kramdown options" do
assert_equal "<h1>Some Header</h1>", @markdown.convert('# Some Header #').strip assert_equal "<h1>Some Header</h1>", @markdown.convert('# Some Header #').strip
end end
should "convert quotes to smart quotes" do context "when asked to convert smart quotes" do
assert_match /<p>(&#8220;|“)Pit(&#8217;|)hy(&#8221;|”)<\/p>/, @markdown.convert(%{"Pit'hy"}).strip should "convert" do
assert_match %r!<p>(&#8220;|“)Pit(&#8217;|)hy(&#8221;|”)<\/p>!, @markdown.convert(%{"Pit'hy"}).strip
end
override = { 'kramdown' => { 'smart_quotes' => 'lsaquo,rsaquo,laquo,raquo' } } should "support custom types" do
markdown = Converters::Markdown.new(Utils.deep_merge_hashes(@config, override)) override = {
assert_match /<p>(&#171;|«)Pit(&#8250;|)hy(&#187;|»)<\/p>/, markdown.convert(%{"Pit'hy"}).strip 'kramdown' => {
'smart_quotes' => 'lsaquo,rsaquo,laquo,raquo'
}
}
markdown = Converters::Markdown.new(Utils.deep_merge_hashes(@config, override))
assert_match %r!<p>(&#171;|«)Pit(&#8250;|)hy(&#187;|»)<\/p>!, \
markdown.convert(%{"Pit'hy"}).strip
end
end end
should "render fenced code blocks with syntax highlighting" do should "render fenced code blocks with syntax highlighting" do
assert_equal "<div class=\"highlighter-rouge\"><pre class=\"highlight\"><code><span class=\"nb\">puts</span> <span class=\"s2\">\"Hello world\"</span>\n</code></pre>\n</div>", @markdown.convert( result = nokogiri_fragment(@markdown.convert(Utils.strip_heredoc <<-MARKDOWN))
<<-EOS ~~~ruby
~~~ruby puts "Hello World"
puts "Hello world" ~~~
~~~ MARKDOWN
EOS
).strip selector = "div.highlighter-rouge>pre.highlight>code"
refute result.css(selector).empty?
end end
context "moving up nested coderay options" do context "when a custom highlighter is chosen" do
setup do should "use the chosen highlighter if it's available" do
@markdown.convert('some markup') override = { "markdown" => "kramdown", "kramdown" => { "syntax_highlighter" => :coderay }}
@converter_config = @markdown.instance_variable_get(:@config)['kramdown'] markdown = Converters::Markdown.new(Utils.deep_merge_hashes(@config, override))
result = nokogiri_fragment(markdown.convert(Utils.strip_heredoc <<-MARKDOWN))
~~~ruby
puts "Hello World"
~~~
MARKDOWN
selector = "div.highlighter-coderay>div.CodeRay>div.code>pre"
refute result.css(selector).empty?
end end
should "work correctly" do should "support legacy enable_coderay... for now" do
assert_equal :style, @converter_config['coderay_css'] override = {
"markdown" => "kramdown",
"kramdown" => {
"syntax_highlighter" => nil,
"enable_coderay" => true
}
}
markdown = Converters::Markdown.new(Utils.deep_merge_hashes(@config, override))
result = nokogiri_fragment(markdown.convert(Utils.strip_heredoc <<-MARKDOWN))
~~~ruby
puts "Hello World"
~~~
MARKDOWN
selector = "div.highlighter-coderay>div.CodeRay>div.code>pre"
refute result.css(selector).empty?
end
end
should "move coderay to syntax_highlighter_opts" do
original = Kramdown::Document.method(:new)
markdown = Converters::Markdown.new(Utils.deep_merge_hashes(@config, {
"markdown" => "kramdown",
"kramdown" => {
"syntax_highlighter" => "coderay",
"coderay" => {
"hello" => "world"
}
}
}))
expect(Kramdown::Document).to receive(:new) do |arg1, hash|
assert_equal hash["syntax_highlighter_opts"]["hello"], "world"
original.call(arg1, hash)
end end
should "also work for defaults" do markdown.convert("hello world")
default = Jekyll::Configuration::DEFAULTS['kramdown']['coderay']['coderay_tab_width']
assert_equal default, @converter_config['coderay_tab_width']
end
should "not overwrite" do
assert_equal 12, @converter_config['coderay_bold_every']
end
end end
end end
end end