Merge pull request #1859 from robin850/rouge

This commit is contained in:
Matt Rogers 2013-12-30 22:45:07 -06:00
commit 012387396a
22 changed files with 168 additions and 64 deletions

View File

@ -7,6 +7,7 @@
* Remove support for Ruby 1.8.x (#1780) * Remove support for Ruby 1.8.x (#1780)
* Move to jekyll/jekyll from mojombo/jekyll (#1817) * Move to jekyll/jekyll from mojombo/jekyll (#1817)
* Allow custom markdown processors (#1872) * Allow custom markdown processors (#1872)
* Provide support for the Rouge syntax highlighter (#1859)
### Minor Enhancements ### Minor Enhancements
* Move the EntryFilter class into the Jekyll module to avoid polluting the * Move the EntryFilter class into the Jekyll module to avoid polluting the
@ -37,6 +38,7 @@
* Add in History and site changes from `v1-stable` branch (#1836) * Add in History and site changes from `v1-stable` branch (#1836)
* Testing additions on the Excerpt class (#1893) * Testing additions on the Excerpt class (#1893)
* Update Kramdown to `~> 1.3` (#1894) * Update Kramdown to `~> 1.3` (#1894)
* Fix the `highlight` tag feature (#1859)
### Site Enhancements ### Site Enhancements
* Document Kramdown's GFM parser option (#1791) * Document Kramdown's GFM parser option (#1791)

View File

@ -91,11 +91,19 @@ Feature: Site configuration
And I should see "<a href=\"http://google.com\">Google</a>" in "_site/index.html" And I should see "<a href=\"http://google.com\">Google</a>" in "_site/index.html"
Scenario: Highlight code with pygments Scenario: Highlight code with pygments
Given I have an "index.html" file that contains "{% highlight ruby %} puts 'Hello world!' {% endhighlight %}" Given I have an "index.html" page that contains "{% highlight ruby %} puts 'Hello world!' {% endhighlight %}"
And I have a configuration file with "pygments" set to "true"
When I run jekyll When I run jekyll
Then the _site directory should exist Then the _site directory should exist
And I should see "puts 'Hello world!'" in "_site/index.html" And I should see "Hello world!" in "_site/index.html"
And I should see "class=\"highlight\"" in "_site/index.html"
Scenario: Highlight code with rouge
Given I have an "index.html" page that contains "{% highlight ruby %} puts 'Hello world!' {% endhighlight %}"
And I have a configuration file with "highlighter" set to "rouge"
When I run jekyll
Then the _site directory should exist
And I should see "Hello world!" in "_site/index.html"
And I should see "class=\"highlight\"" in "_site/index.html"
Scenario: Set time and no future dated posts Scenario: Set time and no future dated posts
Given I have a _layouts directory Given I have a _layouts directory

View File

@ -52,6 +52,7 @@ Gem::Specification.new do |s|
s.add_development_dependency('activesupport', '~> 3.2.13') s.add_development_dependency('activesupport', '~> 3.2.13')
s.add_development_dependency('jekyll_test_plugin') s.add_development_dependency('jekyll_test_plugin')
s.add_development_dependency('jekyll_test_plugin_malicious') s.add_development_dependency('jekyll_test_plugin_malicious')
s.add_development_dependency('rouge', '~> 1.3')
# = MANIFEST = # = MANIFEST =
s.files = %w[ s.files = %w[

View File

@ -24,12 +24,12 @@ module Jekyll
'limit_posts' => 0, 'limit_posts' => 0,
'lsi' => false, 'lsi' => false,
'future' => true, # remove and make true just default 'future' => true, # remove and make true just default
'pygments' => true,
'relative_permalinks' => true, # backwards-compatibility with < 1.0 'relative_permalinks' => true, # backwards-compatibility with < 1.0
# will be set to false once 2.0 hits # will be set to false once 2.0 hits
'markdown' => 'maruku', 'markdown' => 'maruku',
'highlighter' => 'pygments',
'permalink' => 'date', 'permalink' => 'date',
'baseurl' => '/', 'baseurl' => '/',
'include' => ['.htaccess'], 'include' => ['.htaccess'],
@ -210,6 +210,16 @@ module Jekyll
config.delete('server_port') config.delete('server_port')
end end
if config.has_key? 'pygments'
Jekyll.logger.warn "Deprecation:", "The 'pygments' configuration option" +
" has been renamed to 'highlighter'. Please update your" +
" config file accordingly. The allowed values are 'rouge', " +
"'pygments' or null."
config['highlighter'] = 'pygments' if config['pygments']
config.delete('pygments')
end
%w[include exclude].each do |option| %w[include exclude].each do |option|
if config.fetch(option, []).is_a?(String) if config.fetch(option, []).is_a?(String)
Jekyll.logger.warn "Deprecation:", "The '#{option}' configuration option" + Jekyll.logger.warn "Deprecation:", "The '#{option}' configuration option" +

View File

@ -1,27 +1,27 @@
module Jekyll module Jekyll
class Converter < Plugin class Converter < Plugin
# Public: Get or set the pygments prefix. When an argument is specified, # Public: Get or set the highlighter prefix. When an argument is specified,
# the prefix will be set. If no argument is specified, the current prefix # the prefix will be set. If no argument is specified, the current prefix
# will be returned. # will be returned.
# #
# pygments_prefix - The String prefix (default: nil). # highlighter_prefix - The String prefix (default: nil).
# #
# Returns the String prefix. # Returns the String prefix.
def self.pygments_prefix(pygments_prefix = nil) def self.highlighter_prefix(highlighter_prefix = nil)
@pygments_prefix = pygments_prefix if pygments_prefix @highlighter_prefix = highlighter_prefix if highlighter_prefix
@pygments_prefix @highlighter_prefix
end end
# Public: Get or set the pygments suffix. When an argument is specified, # Public: Get or set the highlighter suffix. When an argument is specified,
# the suffix will be set. If no argument is specified, the current suffix # the suffix will be set. If no argument is specified, the current suffix
# will be returned. # will be returned.
# #
# pygments_suffix - The String suffix (default: nil). # highlighter_suffix - The String suffix (default: nil).
# #
# Returns the String suffix. # Returns the String suffix.
def self.pygments_suffix(pygments_suffix = nil) def self.highlighter_suffix(highlighter_suffix = nil)
@pygments_suffix = pygments_suffix if pygments_suffix @highlighter_suffix = highlighter_suffix if highlighter_suffix
@pygments_suffix @highlighter_suffix
end end
# Initialize the converter. # Initialize the converter.
@ -31,18 +31,18 @@ module Jekyll
@config = config @config = config
end end
# Get the pygments prefix. # Get the highlighter prefix.
# #
# Returns the String prefix. # Returns the String prefix.
def pygments_prefix def highlighter_prefix
self.class.pygments_prefix self.class.highlighter_prefix
end end
# Get the pygments suffix. # Get the highlighter suffix.
# #
# Returns the String suffix. # Returns the String suffix.
def pygments_suffix def highlighter_suffix
self.class.pygments_suffix self.class.highlighter_suffix
end end
end end
end end

View File

@ -3,8 +3,8 @@ module Jekyll
class Markdown < Converter class Markdown < Converter
safe true safe true
pygments_prefix "\n" highlighter_prefix "\n"
pygments_suffix "\n" highlighter_suffix "\n"
def setup def setup
return if @setup return if @setup

View File

@ -22,7 +22,7 @@ module Jekyll
end end
end end
module WithoutPygments module WithoutHighlighting
require 'cgi' require 'cgi'
include CommonMethods include CommonMethods
@ -37,19 +37,50 @@ module Jekyll
end end
end end
module WithRouge
require 'rouge'
require 'rouge/plugins/redcarpet'
if Rouge.version < '1.3.0'
abort "Please install Rouge 1.3.0 or greater and try running Jekyll again."
end
include Rouge::Plugins::Redcarpet
include CommonMethods
def block_code(code, lang)
code = "<pre>#{super}</pre>"
output = "<div class=\"highlight\">"
output << add_code_tags(code, lang)
output << "</div>"
end
protected
def rouge_formatter(opts = {})
Rouge::Formatters::HTML.new(opts.merge(wrap: false))
end
end
def initialize(config) def initialize(config)
require 'redcarpet' require 'redcarpet'
@config = config @config = config
@redcarpet_extensions = {} @redcarpet_extensions = {}
@config['redcarpet']['extensions'].each { |e| @redcarpet_extensions[e.to_sym] = true } @config['redcarpet']['extensions'].each { |e| @redcarpet_extensions[e.to_sym] = true }
@renderer ||= if @config['pygments'] @renderer ||= case @config['highlighter']
when 'pygments'
Class.new(Redcarpet::Render::HTML) do Class.new(Redcarpet::Render::HTML) do
include WithPygments include WithPygments
end end
when 'rouge'
Class.new(Redcarpet::Render::HTML) do
include WithRouge
end
else else
Class.new(Redcarpet::Render::HTML) do Class.new(Redcarpet::Render::HTML) do
include WithoutPygments include WithoutHighlighting
end end
end end
rescue LoadError rescue LoadError

View File

@ -3,8 +3,8 @@ module Jekyll
class Textile < Converter class Textile < Converter
safe true safe true
pygments_prefix '<notextile>' highlighter_prefix '<notextile>'
pygments_suffix '</notextile>' highlighter_suffix '</notextile>'
def setup def setup
return if @setup return if @setup

View File

@ -145,8 +145,8 @@ module Jekyll
info = { :filters => [Jekyll::Filters], :registers => { :site => self.site, :page => payload['page'] } } info = { :filters => [Jekyll::Filters], :registers => { :site => self.site, :page => payload['page'] } }
# render and transform content (this becomes the final content of the object) # render and transform content (this becomes the final content of the object)
payload["pygments_prefix"] = converter.pygments_prefix payload["highlighter_prefix"] = converter.highlighter_prefix
payload["pygments_suffix"] = converter.pygments_suffix payload["highlighter_suffix"] = converter.highlighter_suffix
self.content = self.render_liquid(self.content, self.content = self.render_liquid(self.content,
payload, payload,

View File

@ -9,8 +9,8 @@ module Jekyll
arg_is_present? args, "--auto", "The switch '--auto' has been replaced with '--watch'." arg_is_present? args, "--auto", "The switch '--auto' has been replaced with '--watch'."
arg_is_present? args, "--no-auto", "To disable auto-replication, simply leave off \ arg_is_present? args, "--no-auto", "To disable auto-replication, simply leave off \
the '--watch' switch." the '--watch' switch."
arg_is_present? args, "--pygments", "The 'pygments' setting can only be set in \ arg_is_present? args, "--pygments", "The 'pygments'settings has been removed in \
your config files." favour of 'highlighter'."
arg_is_present? args, "--paginate", "The 'paginate' setting can only be set in your \ arg_is_present? args, "--paginate", "The 'paginate' setting can only be set in your \
config files." config files."
arg_is_present? args, "--url", "The 'url' setting can only be set in your config files." arg_is_present? args, "--url", "The 'url' setting can only be set in your config files."

View File

@ -1,7 +1,7 @@
module Jekyll module Jekyll
class Site class Site
attr_accessor :config, :layouts, :posts, :pages, :static_files, attr_accessor :config, :layouts, :posts, :pages, :static_files,
:categories, :exclude, :include, :source, :dest, :lsi, :pygments, :categories, :exclude, :include, :source, :dest, :lsi, :highlighter,
:permalink_style, :tags, :time, :future, :safe, :plugins, :limit_posts, :permalink_style, :tags, :time, :future, :safe, :plugins, :limit_posts,
:show_drafts, :keep_files, :baseurl, :data, :file_read_opts, :gems :show_drafts, :keep_files, :baseurl, :data, :file_read_opts, :gems
@ -13,7 +13,7 @@ module Jekyll
def initialize(config) def initialize(config)
self.config = config.clone self.config = config.clone
%w[safe lsi pygments baseurl exclude include future show_drafts limit_posts keep_files gems].each do |opt| %w[safe lsi highlighter baseurl exclude include future show_drafts limit_posts keep_files gems].each do |opt|
self.send("#{opt}=", config[opt]) self.send("#{opt}=", config[opt])
end end

View File

@ -41,8 +41,11 @@ eos
end end
def render(context) def render(context)
if context.registers[:site].pygments case context.registers[:site].highlighter
when 'pygments'
render_pygments(context, super) render_pygments(context, super)
when 'rouge'
render_rouge(context, super)
else else
render_codehighlighter(context, super) render_codehighlighter(context, super)
end end
@ -58,9 +61,28 @@ eos
@lang @lang
) )
output = context["pygments_prefix"] + output if context["pygments_prefix"] output = context["highlighter_prefix"] + output if context["highlighter_prefix"]
output = output + context["pygments_suffix"] if context["pygments_suffix"] output << context["highlighter_suffix"] if context["highlighter_suffix"]
output
return output
end
def render_rouge(context, code)
require 'rouge'
linenos = @options.keys.include?('linenos')
lexer = Rouge::Lexer.find_fancy(@lang, code) || Rouge::Lexers::PlainText
formatter = Rouge::Formatters::HTML.new(line_numbers: linenos, wrap: false)
pre = "<pre>#{formatter.format(lexer.lex(code))}</pre>"
output = context["highlighter_prefix"] || ""
output << "<div class=\"highlight\">"
output << add_code_tags(pre, @lang)
output << "</div>"
output << context["highlighter_suffix"] if context["highlighter_suffix"]
return output
end end
def render_codehighlighter(context, code) def render_codehighlighter(context, code)

View File

@ -1,3 +1,3 @@
name: Your New Jekyll Site name: Your New Jekyll Site
markdown: redcarpet markdown: redcarpet
pygments: true highlighter: pygments

View File

@ -1,4 +1,4 @@
pygments: true highlight: pygments
relative_permalinks: false relative_permalinks: false
gauges_id: 503c5af6613f5d0f19000027 gauges_id: 503c5af6613f5d0f19000027
permalink: /news/:year/:month/:day/:title/ permalink: /news/:year/:month/:day/:title/

View File

@ -288,7 +288,7 @@ encoding: nil
future: true future: true
show_drafts: nil show_drafts: nil
limit_posts: 0 limit_posts: 0
pygments: true highlighter: pygments
relative_permalinks: true relative_permalinks: true
@ -363,7 +363,7 @@ Jekyll handles two special Redcarpet extensions:
# ...ruby code # ...ruby code
``` ```
With both fenced code blocks and pygments enabled, this will statically highlight the code; without pygments, it will add a `class="LANGUAGE"` attribute to the `<code>` element, which can be used as a hint by various JavaScript code highlighting libraries. With both fenced code blocks and highlighter enabled, this will statically highlight the code; without any syntax highlighter, it will add a `class="LANGUAGE"` attribute to the `<code>` element, which can be used as a hint by various JavaScript code highlighting libraries.
- `smart` --- This pseudo-extension turns on SmartyPants, which converts straight quotes to curly quotes and runs of hyphens to em (`---`) and en (`--`) dashes. - `smart` --- This pseudo-extension turns on SmartyPants, which converts straight quotes to curly quotes and runs of hyphens to em (`---`) and en (`--`) dashes.
All other extensions retain their usual names from Redcarpet, and no renderer options aside from `smart` can be specified in Jekyll. [A list of available extensions can be found in the Redcarpet README file.][redcarpet_extensions] Make sure you're looking at the README for the right version of Redcarpet: Jekyll currently uses v2.2.x, and extensions like `footnotes` and `highlight` weren't added until after version 3.0.0. The most commonly used extensions are: All other extensions retain their usual names from Redcarpet, and no renderer options aside from `smart` can be specified in Jekyll. [A list of available extensions can be found in the Redcarpet README file.][redcarpet_extensions] Make sure you're looking at the README for the right version of Redcarpet: Jekyll currently uses v2.2.x, and extensions like `footnotes` and `highlight` weren't added until after version 3.0.0. The most commonly used extensions are:

View File

@ -66,9 +66,10 @@ Check out [the extras page](../extras/) for more information.
<h5>ProTip™: Enable Syntax Highlighting</h5> <h5>ProTip™: Enable Syntax Highlighting</h5>
<p> <p>
If youre the kind of person who is using Jekyll, then chances are youll If youre the kind of person who is using Jekyll, then chances are youll
want to enable syntax highlighting using Pygments. You should really want to enable syntax highlighting using <a href="http://pygments.org/">Pygments</a>
<a href="../templates/#code_snippet_highlighting">check out how to do or <a href="https://github.com/jayferd/rouge">Rouge</a>. You should really
that</a> before you go any further. <a href="../templates/#code_snippet_highlighting">check out how to
do that</a> before you go any farther.
</p> </p>
</div> </div>

View File

@ -139,8 +139,8 @@ your `excerpt_separator` to `""`.
## Highlighting code snippets ## Highlighting code snippets
Jekyll also has built-in support for syntax highlighting of code snippets using Jekyll also has built-in support for syntax highlighting of code snippets using
Pygments, and including a code snippet in any post is easy. Just use the either Pygments or Rouge, and including a code snippet in any post is easy. Just
dedicated Liquid tag as follows: use the dedicated Liquid tag as follows:
{% highlight text %} {% highlight text %}
{% raw %}{% highlight ruby %}{% endraw %} {% raw %}{% highlight ruby %}{% endraw %}

View File

@ -230,8 +230,14 @@ These parameters are available via Liquid in the include:
Jekyll has built in support for syntax highlighting of [over 100 Jekyll has built in support for syntax highlighting of [over 100
languages](http://pygments.org/languages/) thanks to languages](http://pygments.org/languages/) thanks to
[Pygments](http://pygments.org/). To use Pygments, you must have Python installed on your [Pygments](http://pygments.org/). To use Pygments, you must have Python installed
system and set `pygments` to `true` in your site's configuration file. on your system and set `highlighter` to `pygments` in your site's configuration
file.
Alternatively, you can use [Rouge](https://github.com/jayferd/rouge) to highlight
your code snippets. It doesn't support as many languages as Pygments does but
it should fit in most cases and it's written in pure Ruby ; you don't need Python
on your system!
To render a code block with syntax highlighting, surround your code as follows: To render a code block with syntax highlighting, surround your code as follows:
@ -247,8 +253,9 @@ end
The argument to the `highlight` tag (`ruby` in the example above) is the The argument to the `highlight` tag (`ruby` in the example above) is the
language identifier. To find the appropriate identifier to use for the language language identifier. To find the appropriate identifier to use for the language
you want to highlight, look for the “short name” on the [Lexers you want to highlight, look for the “short name” on the [Pygments' Lexers
page](http://pygments.org/docs/lexers/). page](http://pygments.org/docs/lexers/) or the [Rouge
wiki](https://github.com/jayferd/rouge/wiki/List-of-supported-languages-and-lexers).
#### Line numbers #### Line numbers

View File

@ -123,8 +123,8 @@ bug](http://aaronqian.com/articles/2009/04/07/redcloth-ate-my-notextile.html)
and will hopefully be fixed for 4.2. You can still use 4.1.9, but the and will hopefully be fixed for 4.2. You can still use 4.1.9, but the
test suite requires that 4.1.0 be installed. If you use a version of test suite requires that 4.1.0 be installed. If you use a version of
RedCloth that does not have the notextile tag, you may notice that RedCloth that does not have the notextile tag, you may notice that
syntax highlighted blocks from Pygments are not formatted correctly, syntax highlighted blocks from Pygments or Rouge are not formatted
among other things. If youre seeing this just install 4.1.0. correctly, among other things. If youre seeing this just install 4.1.0.
### Liquid ### Liquid

View File

@ -51,11 +51,12 @@ class TestConfiguration < Test::Unit::TestCase
context "#backwards_compatibilize" do context "#backwards_compatibilize" do
setup do setup do
@config = Configuration[{ @config = Configuration[{
"auto" => true, "auto" => true,
"watch" => true, "watch" => true,
"server" => true, "server" => true,
"exclude" => "READ-ME.md, Gemfile,CONTRIBUTING.hello.markdown", "exclude" => "READ-ME.md, Gemfile,CONTRIBUTING.hello.markdown",
"include" => "STOP_THE_PRESSES.txt,.heloses, .git" "include" => "STOP_THE_PRESSES.txt,.heloses, .git",
"pygments" => true,
}] }]
end end
should "unset 'auto' and 'watch'" do should "unset 'auto' and 'watch'" do
@ -78,6 +79,11 @@ class TestConfiguration < Test::Unit::TestCase
assert @config.backwards_compatibilize.has_key?("include") assert @config.backwards_compatibilize.has_key?("include")
assert_equal @config.backwards_compatibilize["include"], %w[STOP_THE_PRESSES.txt .heloses .git] assert_equal @config.backwards_compatibilize["include"], %w[STOP_THE_PRESSES.txt .heloses .git]
end end
should "set highlighter to pygments" do
assert @config.has_key?("pygments")
assert !@config.backwards_compatibilize.has_key?("pygments")
assert_equal @config.backwards_compatibilize["highlighter"], "pygments"
end
end end
context "#fix_common_issues" do context "#fix_common_issues" do
setup do setup do

View File

@ -28,7 +28,7 @@ class TestRedcarpet < Test::Unit::TestCase
context "with pygments enabled" do context "with pygments enabled" do
setup do setup do
@markdown = Converters::Markdown.new @config.merge({ 'pygments' => true }) @markdown = Converters::Markdown.new @config.merge({ 'highlighter' => 'pygments' })
end end
should "render fenced code blocks with syntax highlighting" do should "render fenced code blocks with syntax highlighting" do
@ -42,9 +42,25 @@ puts "Hello world"
end end
end end
context "with pygments disabled" do context "with rouge enabled" do
setup do setup do
@markdown = Converters::Markdown.new @config.merge({ 'pygments' => false }) @markdown = Converters::Markdown.new @config.merge({ 'highlighter' => 'rouge' })
end
should "render fenced code blocks with syntax highlighting" do
assert_equal "<div class=\"highlight\"><pre><code class=\"ruby language-ruby\" data-lang=\"ruby\"><span class=\"nb\">puts</span> <span class=\"s2\">\"Hello world\"</span>\n</code></pre></div>", @markdown.convert(
<<-EOS
```ruby
puts "Hello world"
```
EOS
).strip
end
end
context "without any highlighter" do
setup do
@markdown = Converters::Markdown.new @config.merge({ 'highlighter' => nil })
end end
should "render fenced code blocks without syntax highlighting" do should "render fenced code blocks without syntax highlighting" do

View File

@ -6,7 +6,7 @@ class TestTags < Test::Unit::TestCase
def create_post(content, override = {}, converter_class = Jekyll::Converters::Markdown) def create_post(content, override = {}, converter_class = Jekyll::Converters::Markdown)
stub(Jekyll).configuration do stub(Jekyll).configuration do
Jekyll::Configuration::DEFAULTS.deep_merge({'pygments' => true}).deep_merge(override) Jekyll::Configuration::DEFAULTS.deep_merge({'highlighter' => 'pygments'}).deep_merge(override)
end end
site = Site.new(Jekyll.configuration) site = Site.new(Jekyll.configuration)
@ -16,8 +16,8 @@ class TestTags < Test::Unit::TestCase
info = { :filters => [Jekyll::Filters], :registers => { :site => site } } info = { :filters => [Jekyll::Filters], :registers => { :site => site } }
@converter = site.converters.find { |c| c.class == converter_class } @converter = site.converters.find { |c| c.class == converter_class }
payload = { "pygments_prefix" => @converter.pygments_prefix, payload = { "highlighter_prefix" => @converter.highlighter_prefix,
"pygments_suffix" => @converter.pygments_suffix } "highlighter_suffix" => @converter.highlighter_suffix }
@result = Liquid::Template.parse(content).render!(payload, info) @result = Liquid::Template.parse(content).render!(payload, info)
@result = @converter.convert(@result) @result = @converter.convert(@result)