Skip to content

Commit

Permalink
Syntax highlighters are configurable now
Browse files Browse the repository at this point in the history
* Syntax highlighters can now be set via the syntax_highlighter
  options which defaults to coderay for backwards-compatibility

* Options for the syntax highlighter can be set with the
  syntax_highlighter_opts option

* The old coderay_* options are deprecated as of now and will be
  removed in 2.0.0

* The coderay implementation has been extracted and is now just
  another syntax highlighter implementation.
  • Loading branch information
gettalong committed Oct 23, 2014
1 parent d37804e commit 49e1b12
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 51 deletions.
22 changes: 4 additions & 18 deletions doc/converter/html.page
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,9 @@ kramdown supports setting a code language for code blocks and code spans, see [L
Blocks](../syntax.html#language-of-code-blocks). This language will be used for highlighting code
blocks and spans.

Syntax highlighting for text is done using the [CodeRay](http://coderay.rubychan.de) library which
can be installed, for example, via Rubygems (the gem name is `coderay`). Have a look at the CodeRay
homepage to see all supported languages.

> Note that syntax highlighting won't work unless the CodeRay library is installed!
{:.information}

Here is an example on how Ruby code is highlighted:

require 'kramdown'

Kramdown::Document.new('* something').to_html
puts 1 + 1
{: .language-ruby}

You can set the option `enable_coderay` to `false` to completely disable this feature, leaving a
clean source.
The actual syntax highlighting is configurable via the [option
'syntax_highlighter'](../options.html#option-syntax-highlighter). The default value is 'coderay'
which implies that the [Coderay syntax highlighter](../syntax_highlighter/coderay.html) is used.


## Math Support
Expand Down Expand Up @@ -149,7 +135,7 @@ Here is an example:

The HTML converter supports the following options:

{options: {items: [auto_ids, auto_id_prefix, auto_id_stripping, transliterated_header_ids, template, footnote_nr, entity_output, smart_quotes, toc_levels, enable_coderay, coderay_wrap, coderay_line_numbers, coderay_line_number_start, coderay_tab_width, coderay_bold_every, coderay_css, coderay_default_lang]}}
{options: {items: [auto_ids, auto_id_prefix, auto_id_stripping, transliterated_header_ids, template, footnote_nr, entity_output, smart_quotes, toc_levels, enable_coderay, coderay_wrap, coderay_line_numbers, coderay_line_number_start, coderay_tab_width, coderay_bold_every, coderay_css, coderay_default_lang, syntax_highlighter, syntax_highlighter_opts]}}


{include_file: doc/links.markdown}
2 changes: 2 additions & 0 deletions doc/documentation.template
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* [LaTeX](converter/latex.html)
* [kramdown](converter/kramdown.html)
* [RemoveHtmlTags](converter/remove_html_tags.html)
* Syntax Highlighters
* [Coderay](syntax_highlighter/coderay.html)
* [Configuration Options](options.html)
* [Tests](tests.html)

Expand Down
39 changes: 39 additions & 0 deletions doc/syntax_highlighter/coderay.page
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: Coderay
---

## Syntax Highlighting With Coderay

[Coderay](http://coderay.rubychan.de) can be used as syntax highlighter for code blocks and code
spans when converting to HTML.

To use Coderay, set the [option 'syntax_highlighter'](../options.html#option-syntax-highlighter) to
'coderay' and make sure that Coderay is available. The Coderay library can be installed, e.g., via
Rubygems by running `gem install coderay`.

> Note that the 'coderay_*' options are deprecated and should not be used anymore!
{:.information}

The [option 'syntax_highlighter_opts'](../options.html#option-syntax-highlighter-opts) supports the
following special keys:

span
: A key-value map of options that are only used when syntax highlighting code spans.

block
: A key-value map of options that are only used when syntax highlighting code blocks.

default_lang
: The default language that should be used when no language is set for a **code block**.

Furthermore all Coderay options (e.g. 'css', 'line_numbers', 'line_numbers_start', 'bold_every',
'tab_width', 'wrap') can be set directly on the 'syntax_highlighter_opts' option (where they apply
to code spans *and* code blocks) and/or on the 'span'/'block' keys.

Here is an example that shows how Ruby code is highlighted:

require 'kramdown'

Kramdown::Document.new('* something').to_html
puts 1 + 1
{: .language-ruby}
6 changes: 6 additions & 0 deletions doc/virtual
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
rdoc/index.html:
title: API Documentation

/options.en.html#option-syntax-highlighter:
dest_path: options.html#option-syntax-highlighter

/options.en.html#option-syntax-highlighter-opts:
dest_path: options.html#option-syntax-highlighter-opts
16 changes: 16 additions & 0 deletions lib/kramdown/converter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#++
#

require 'kramdown/utils'

module Kramdown

# This module contains all available converters, i.e. classes that take a root Element and convert
Expand All @@ -25,6 +27,20 @@ module Converter
autoload :RemoveHtmlTags, 'kramdown/converter/remove_html_tags'
autoload :Pdf, 'kramdown/converter/pdf'

extend ::Kramdown::Utils::Configurable

configurable(:syntax_highlighter)

add_syntax_highlighter(:coderay) do |converter, text, lang, type|
require 'kramdown/converter/syntax_highlighter/coderay'
if ::Kramdown::Converter::SyntaxHighlighter::Coderay::AVAILABLE
add_syntax_highlighter(:coderay, ::Kramdown::Converter::SyntaxHighlighter::Coderay)
else
add_syntax_highlighter(:coderay) {|*args| nil}
end
syntax_highlighter(:coderay).call(converter, text, lang, type)
end

end

end
14 changes: 14 additions & 0 deletions lib/kramdown/converter/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,20 @@ def extract_code_language!(attr)
lang
end

# Highlight the given +text+ in the language +lang+ with the syntax highlighter configured
# through the option 'syntax_highlighter'.
def highlight_code(text, lang, type)
return nil unless @options[:syntax_highlighter]

highlighter = ::Kramdown::Converter.syntax_highlighter(@options[:syntax_highlighter])
if highlighter
highlighter.call(self, text, lang, type)
else
warning("The configured syntax highlighter #{@options[:syntax_highlighter]} is not available.")
nil
end
end

# Generate an unique alpha-numeric ID from the the string +str+ for use as a header ID.
#
# Uses the option +auto_id_prefix+: the value of this option is prepended to every generated
Expand Down
28 changes: 6 additions & 22 deletions lib/kramdown/converter/html.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,6 @@ module Converter
# HTML element.
class Html < Base

begin
require 'coderay'

# Highlighting via coderay is available if this constant is +true+.
HIGHLIGHTING_AVAILABLE = true
rescue LoadError
HIGHLIGHTING_AVAILABLE = false # :nodoc:
end

include ::Kramdown::Utils::Html
include ::Kramdown::Parser::Html::Constants

Expand All @@ -54,7 +45,6 @@ def initialize(root, options)
@toc_code = nil
@indent = 2
@stack = []
@coderay_enabled = @options[:enable_coderay] && HIGHLIGHTING_AVAILABLE
end

# The mapping of element type to conversion method.
Expand Down Expand Up @@ -101,13 +91,10 @@ def convert_p(el, indent)
def convert_codeblock(el, indent)
attr = el.attr.dup
lang = extract_code_language!(attr)
if @coderay_enabled && (lang || @options[:coderay_default_lang])
opts = {:wrap => @options[:coderay_wrap], :line_numbers => @options[:coderay_line_numbers],
:line_number_start => @options[:coderay_line_number_start], :tab_width => @options[:coderay_tab_width],
:bold_every => @options[:coderay_bold_every], :css => @options[:coderay_css]}
lang = (lang || @options[:coderay_default_lang]).to_sym
result = CodeRay.scan(el.value, lang).html(opts).chomp << "\n"
"#{' '*indent}<div#{html_attributes(attr)}>#{result}#{' '*indent}</div>\n"
highlighted_code = highlight_code(el.value, lang, :block)

if highlighted_code
"#{' '*indent}<div#{html_attributes(attr)}>#{highlighted_code}#{' '*indent}</div>\n"
else
result = escape_html(el.value)
result.chomp!
Expand Down Expand Up @@ -262,11 +249,8 @@ def convert_img(el, indent)

def convert_codespan(el, indent)
lang = extract_code_language(el.attr)
result = if @coderay_enabled && lang
CodeRay.scan(el.value, lang.to_sym).html(:wrap => :span, :css => @options[:coderay_css]).chomp
else
escape_html(el.value)
end
result = highlight_code(el.value, lang, :span)
result = escape_html(el.value) unless result
format_as_span_html('code', el.attr, result)
end

Expand Down
78 changes: 78 additions & 0 deletions lib/kramdown/converter/syntax_highlighter/coderay.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2014 Thomas Leitner <[email protected]>
#
# This file is part of kramdown which is licensed under the MIT.
#++
#

module Kramdown::Converter::SyntaxHighlighter

# Uses Coderay to highlight code blocks and code spans.
module Coderay

begin
require 'coderay'

# Highlighting via coderay is available if this constant is +true+.
AVAILABLE = true
rescue LoadError
AVAILABLE = false # :nodoc:
end

def self.call(converter, text, lang, type)
return nil unless converter.options[:enable_coderay]

if type == :span && lang
::CodeRay.scan(text, lang.to_sym).html(options(converter, :span)).chomp
elsif type == :block && (lang || options(converter, :default_lang))
lang = (lang || options(converter, :default_lang)).to_sym
::CodeRay.scan(text, lang).html(options(converter, :block)).chomp << "\n"
else
nil
end
end

def self.options(converter, type)
prepare_options(converter)
converter.data[:syntax_highlighter_coderay][type]
end

def self.prepare_options(converter)
return if converter.data.key?(:syntax_highlighter_coderay)

cache = converter.data[:syntax_highlighter_coderay] = {}

opts = converter.options[:syntax_highlighter_opts].dup
span_opts = (opts.delete(:span) || {}).dup
block_opts = (opts.delete(:block) || {}).dup
[span_opts, block_opts].each do |hash|
hash.keys.each do |k|
hash[k.kind_of?(String) ? Kramdown::Options.str_to_sym(k) : k] = hash.delete(k)
end
end

cache[:default_lang] = converter.options[:coderay_default_lang] || opts.delete(:default_lang)
cache[:span] = {
:css => converter.options[:coderay_css]
}.update(opts).update(span_opts).update(:wrap => :span)
cache[:block] = {
:wrap => converter.options[:coderay_wrap],
:line_numbers => converter.options[:coderay_line_numbers],
:line_number_start => converter.options[:coderay_line_number_start],
:tab_width => converter.options[:coderay_tab_width],
:bold_every => converter.options[:coderay_bold_every],
:css => converter.options[:coderay_css]
}.update(opts).update(block_opts)

[:css, :wrap, :line_numbers].each do |key|
[:block, :span].each do |type|
cache[type][key] = cache[type][key].to_sym if cache[type][key].kind_of?(String)
end
end
end

end

end
74 changes: 63 additions & 11 deletions lib/kramdown/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,7 @@ def self.parse(name, data)
elsif @options[name].type == Float
Float(data) rescue raise Kramdown::Error, "Invalid float value for option '#{name}': '#{data}'"
elsif @options[name].type == Symbol
data.strip!
data = data[1..-1] if data[0] == ?:
(data.empty? || data == 'nil' ? nil : data.to_sym)
str_to_sym(data)
elsif @options[name].type == Boolean
data.downcase.strip != 'false' && !data.empty?
end
Expand All @@ -112,6 +110,17 @@ def self.parse(name, data)
data
end

# Converts the given String +data+ into a Symbol or +nil+ with the
# following provisions:
#
# - A leading colon is stripped from the string.
# - An empty value or a value equal to "nil" results in +nil+.
def self.str_to_sym(data)
data = data.strip
data = data[1..-1] if data[0] == ?:
(data.empty? || data == 'nil' ? nil : data.to_sym)
end

# ----------------------------
# :section: Option Validators
#
Expand All @@ -137,6 +146,23 @@ def self.simple_array_validator(val, name, size)
val
end

# Ensures that the option value +val+ for the option called +name+ is a valid hash. The
# parameter +val+ can be
#
# - a hash in YAML format
# - or a Ruby Hash object.
def self.simple_hash_validator(val, name)
if String === val
begin
val = YAML.load(val)
rescue RuntimeError, ArgumentError, SyntaxError
raise Kramdown::Error, "Invalid YAML value for option #{name}"
end
end
raise Kramdown::Error, "Invalid type #{val.class} for option #{name}" if !(Hash === val)
val
end

# ----------------------------
# :section: Option Definitions
#
Expand Down Expand Up @@ -269,14 +295,7 @@ def self.simple_array_validator(val, name, size)
Default: {}
Used by: kramdown parser
EOF
if String === val
begin
val = YAML.load(val)
rescue RuntimeError, ArgumentError, SyntaxError
raise Kramdown::Error, "Invalid YAML value for option link_defs"
end
end
raise Kramdown::Error, "Invalid type #{val.class} for option #{name}" if !(Hash === val)
val = simple_hash_validator(val, :link_defs)
val.each do |k,v|
if !(Array === v) || v.size > 2 || v.size < 1
raise Kramdown::Error, "Invalid structure for hash value of option #{name}"
Expand Down Expand Up @@ -494,6 +513,39 @@ def self.simple_array_validator(val, name, size)
Used by: GFM parser
EOF

define(:syntax_highlighter, Symbol, :coderay, <<EOF)
Set the syntax highlighter
Specifies the syntax highlighter that should be used for highlighting
code blocks and spans. If this option is set to +nil+, no syntax
highlighting is done.
Options for the syntax highlighter can be set with the
syntax_highlighter_opts configuration option.
Default: coderay
Used by: HTML converter
EOF

define(:syntax_highlighter_opts, Object, {}, <<EOF) do |val|
Set the syntax highlighter options
Specifies options for the syntax highlighter set via the
syntax_highlighter configuration option.
The value needs to be a hash with key-value pairs that are understood by
the used syntax highlighter.
Default: {}
Used by: HTML converter
EOF
val = simple_hash_validator(val, :syntax_highlighter_opts)
val.keys.each do |k|
val[k.kind_of?(String) ? str_to_sym(k) : k] = val.delete(k)
end
val
end

end

end
6 changes: 6 additions & 0 deletions test/testcases/block/06_codeblock/highlighting-opts.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div><span class="CodeRay">x = <span class="constant">Class</span>.new
</span>
</div>
<div><span class="CodeRay"><span class="tag">&lt;a&gt;</span>href<span class="tag">&lt;/a&gt;</span>
</span>
</div>
Loading

0 comments on commit 49e1b12

Please sign in to comment.