Modernize Kramdown for Markdown converter.
This commit is contained in:
parent
b01b089f69
commit
3432fd2c2d
3
Gemfile
3
Gemfile
|
@ -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'
|
||||||
|
|
|
@ -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'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>(“|“)Pit(’|’)hy(”|”)<\/p>/, @markdown.convert(%{"Pit'hy"}).strip
|
should "convert" do
|
||||||
|
assert_match %r!<p>(“|“)Pit(’|’)hy(”|”)<\/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>(«|«)Pit(›|›)hy(»|»)<\/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>(«|«)Pit(›|›)hy(»|»)<\/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
|
||||||
|
|
Loading…
Reference in New Issue