Browse files

Add feature to colorize #+BEGIN_SRC blocks with Pygments or CodeRay

  • Loading branch information...
1 parent 0c07b90 commit 8dc7348834efa04a2b3e5963b66755828421d1c7 @wallyqs wallyqs committed Jun 10, 2012
View
8 Gemfile
@@ -6,3 +6,11 @@ group :development do
gem 'rspec'
gem 'tilt'
end
+
+group :coderay do
+ gem 'coderay'
+end
+
+group :pygments do
+ gem 'pygments.rb'
+end
View
55 lib/org-ruby/html_output_buffer.rb
@@ -1,6 +1,17 @@
require OrgRuby.libpath(*%w[org-ruby html_symbol_replace])
require OrgRuby.libpath(*%w[org-ruby output_buffer])
+begin
+ require 'pygments'
+rescue LoadError
+ # Pygments is not supported so we try instead with CodeRay
+ begin
+ require 'coderay'
+ rescue LoadError
+ # No code syntax highlighting
+ end
+end
+
module Orgmode
class HtmlOutputBuffer < OutputBuffer
@@ -86,13 +97,39 @@ def pop_mode(mode = nil)
end
def flush!
- escape_buffer!
- if mode_is_code(@buffer_mode) then
+ if buffer_mode_is_src_block?
+
+ # Only try to colorize #+BEGIN_SRC blocks with a specified language,
+ # but we still have to catch the cases when a lexer for the language was not available
+ if not @block_lang.empty? and (defined? CodeRay or defined? Pygments)
+ # NOTE: CodeRay and Pygments already escape the html once, so no need to escape_buffer!
+ if defined? CodeRay
+ # CodeRay might throw a warning when unsupported lang is set
+ silence_warnings do
+ @buffer = CodeRay.scan(@buffer, @block_lang.to_s).html(:wrap => nil, :css => :style)
+ end
+ elsif defined? Pygments
+ begin
+ @buffer = Pygments.highlight(@buffer, :lexer => @block_lang)
+ rescue ::RubyPython::PythonError
+ # Not supported lexer from Pygments
+ end
+ end
+ else
+ escape_buffer!
+ end
+
+ @logger.debug "FLUSH SRC CODE ==========> #{@buffer.inspect}"
+ @output << @buffer
+ elsif mode_is_code(@buffer_mode) then
+ escape_buffer!
+
# Whitespace is significant in :code mode. Always output the buffer
# and do not do any additional translation.
@logger.debug "FLUSH CODE ==========> #{@buffer.inspect}"
@output << @buffer << "\n"
else
+ escape_buffer!
if @buffer.length > 0 and @output_type == :horizontal_rule then
@output << "<hr />\n"
elsif @buffer.length > 0 and @output_type == :definition_list then
@@ -166,6 +203,10 @@ def buffer_mode_is_table?
@buffer_mode == :table
end
+ def buffer_mode_is_src_block?
+ @buffer_mode == :src
+ end
+
# Escapes any HTML content in the output accumulation buffer @buffer.
def escape_buffer!
@buffer.gsub!(/&/, "&amp;")
@@ -245,5 +286,15 @@ def inline_formatting(str)
Orgmode.special_symbols_to_html(str)
str
end
+
+ # Helper method taken from Rails
+ # https://github.com/rails/rails/blob/c2c8ef57d6f00d1c22743dc43746f95704d67a95/activesupport/lib/active_support/core_ext/kernel/reporting.rb#L10
+ def silence_warnings
+ warn_level = $VERBOSE
+ $VERBOSE = nil
+ yield
+ ensure
+ $VERBOSE = warn_level
+ end
end # class HtmlOutputBuffer
end # module Orgmode
View
7 lib/org-ruby/line.rb
@@ -164,10 +164,14 @@ def block_lang
$3 if @line =~ BlockRegexp
end
- def code_block_type?
+ def code_block?
block_type =~ /^(EXAMPLE|SRC)$/i
end
+ def code_block_line?
+ @assigned_paragraph_type == :src
+ end
+
InlineExampleRegexp = /^\s*:\s/
# Test if the line matches the "inline example" case:
@@ -200,6 +204,7 @@ def in_buffer_setting?
# Determines the paragraph type of the current line.
def paragraph_type
return :blank if blank?
+ return :src if code_block_line? # Do not try to guess the type of this line if it is accumulating source code
return :definition_list if definition_list? # order is important! A definition_list is also an unordered_list!
return :ordered_list if ordered_list?
return :unordered_list if unordered_list?
View
16 lib/org-ruby/output_buffer.rb
@@ -78,9 +78,16 @@ def pop_mode(mode = nil)
# Prepares the output buffer to receive content from a line.
# As a side effect, this may flush the current accumulated text.
def prepare(line)
- @logger.debug "Looking at #{line.paragraph_type}: #{line.to_s}"
- if not should_accumulate_output?(line) then
- @block_lang = line.block_lang if line.begin_block? and line.code_block_type?
+ @logger.debug "Looking at #{line.paragraph_type}(#{current_mode}) : #{line.to_s}"
+ if line.begin_block? and line.code_block?
+ flush!
+ # We try to get the lang from #+BEGIN_SRC blocks
+ @block_lang = line.block_lang
+ @output_type = line.paragraph_type
+ elsif current_mode == :example and line.end_block?
+ flush!
+ @output_type = line.paragraph_type
+ elsif not should_accumulate_output?(line)
flush!
maintain_list_indent_stack(line)
@output_type = line.paragraph_type
@@ -207,6 +214,9 @@ def output_footnotes!
# line breaks.)
def should_accumulate_output?(line)
+ # Special case: We are accumulating source code block content for colorizing
+ return true if line.paragraph_type == :src and @output_type == :src
+
# Special case: Preserve line breaks in block code mode.
return false if preserve_whitespace?
View
24 lib/org-ruby/parser.rb
@@ -120,7 +120,8 @@ def initialize(lines, offset=0)
end
end
table_header_set = false if !line.table?
- mode = :code if line.begin_block? and line.block_type == "EXAMPLE"
+ mode = :code if line.begin_block? and line.block_type.casecmp("EXAMPLE") == 0
+ mode = :src_code if line.begin_block? and line.block_type.casecmp("SRC") == 0
mode = :block_comment if line.begin_block? and line.block_type == "COMMENT"
mode = :property_drawer if line.property_drawer_begin_block?
if (@current_headline) then
@@ -143,7 +144,7 @@ def initialize(lines, offset=0)
# As long as we stay in code mode, force lines to be either blank or paragraphs.
# Don't try to interpret structural items, like headings and tables.
line = Line.new line, self
- if line.end_block? and line.block_type == "EXAMPLE"
+ if line.end_block? and line.code_block?
mode = :normal
else
line.assigned_paragraph_type = :paragraph unless line.blank?
@@ -154,6 +155,20 @@ def initialize(lines, offset=0)
@header_lines << line
end
+ when :src_code
+
+ line = Line.new line, self
+ if line.end_block? and line.code_block?
+ mode = :normal
+ else
+ line.assigned_paragraph_type = :src
+ end
+ if (@current_headline) then
+ @current_headline.body_lines << line
+ else
+ @header_lines << line
+ end
+
when :property_drawer
line = Line.new line, self
@@ -270,8 +285,11 @@ def self.translate(lines, output_buffer)
output_buffer << line.output_text
- else
+ when :src
+
+ output_buffer << line.output_text << "\n"
+ else
if output_buffer.preserve_whitespace? then
output_buffer << line.output_text
else
View
81 spec/html_examples/advanced-code.html
@@ -1,81 +0,0 @@
-<p class="title">advanced-code.org</p>
-<p>Turns out there&#8217;s more way to do code than just BEGIN_EXAMPLE.</p>
-<h1><span class="heading-number heading-number-1">1 </span>Inline examples</h1>
-<p>This should work:</p>
-<pre class="example">
- fixed width? how does this work?
- ...........
- ............
- .
- . . . .
- . ..
- ....... .....
- . .
- ....
-</pre>
-<p>Two ASCII blobs.</p>
-<h1><span class="heading-number heading-number-1">2 </span>BEGIN_SRC</h1>
-<p>And this:</p>
-<pre class="src">
-<code class="ruby">
- # Finds all emphasis matches in a string.
- # Supply a block that will get the marker and body as parameters.
- def match_all(str)
- str.scan(@org_emphasis_regexp) do |match|
- yield $2, $3
- end
- end
-</code>
-</pre>
-<p>Now let&#8217;s test case-insensitive code blocks.</p>
-<pre class="src">
-<code class="ruby">
- # Finds all emphasis matches in a string.
- # Supply a block that will get the marker and body as parameters.
- def match_all(str)
- str.scan(@org_emphasis_regexp) do |match|
- yield $2, $3
- end
- end
-</code>
-</pre>
-<pre class="src">
-<code class="clojure">
-(def fib-seq
- (concat
- [0 1]
- ((fn rfib [a b]
- (lazy-cons (+ a b) (rfib b (+ a b)))) 0 1)))
-
-user&gt; (take 20 fib-seq)
-(0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181)
-</code>
-</pre>
-<p>Even if no language is set, it is still wrapped in code tags but class is empty.</p>
-<pre class="src">
-<code class="">
-echo 'Defaults env_keeps="http_proxy https_proxy ftp_proxy"' | sudo tee -a /etc/sudoers
-</code>
-</pre>
-<h1><span class="heading-number heading-number-1">3 </span>It should be possible to write a colon at the beginning of an example</h1>
-<blockquote>
- <p>I really love to write about :symbols. They sure are the best things in the world!</p>
-</blockquote>
-<pre class="src">
-<code class="ruby">
-{
-:one =&gt; 1,
-:two =&gt; 2
-}
-</code>
-</pre>
-<pre class="src">
-<code class="clojure">
-(defproject helloworld "0.1"
-:dependencies [[org.clojure/clojure
- "1.1.0-master-SNAPSHOT"]
- [org.clojure/clojure-contrib
- "1.0-SNAPSHOT"]]
-:main helloworld)
-</code>
-</pre>
View
106 spec/html_examples/advanced-code.org
@@ -1,106 +0,0 @@
-#+TITLE: advanced-code.org
-#+AUTHOR: Brian Dewey
-#+EMAIL: bdewey@gmail.com
-#+DATE: 2009-12-30 Wed
-#+DESCRIPTION: More types of code support
-#+KEYWORDS:
-#+LANGUAGE: en
-#+OPTIONS: H:3 num:t toc:nil \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t
-#+OPTIONS: TeX:t LaTeX:nil skip:nil d:nil todo:t pri:nil tags:not-in-toc
-#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
-#+EXPORT_SELECT_TAGS: export
-#+EXPORT_EXCLUDE_TAGS: noexport
-#+LINK_UP:
-#+LINK_HOME:
-
-Turns out there's more way to do code than just BEGIN_EXAMPLE.
-
-* Inline examples
-
-This should work:
-
-: fixed width? how does this work?
-: ...........
-: ............
-: .
-: . . . .
-: . ..
-: ....... .....
-: . .
-: ....
-
-Two ASCII blobs.
-
-* BEGIN_SRC
-:PROPERTIES:
-:ARCHIVE_TIME: 2009-12-26 Sat 22:16
-:ARCHIVE_FILE: ~/brians-brain/content/projects/orgmode_parser.org
-:ARCHIVE_OLPATH: &lt;%= @page.title %&gt;/Future Development
-:ARCHIVE_CATEGORY: orgmode_parser
-:ARCHIVE_TODO: DONE
-:END:
-
-And this:
-
-#+BEGIN_SRC ruby
- # Finds all emphasis matches in a string.
- # Supply a block that will get the marker and body as parameters.
- def match_all(str)
- str.scan(@org_emphasis_regexp) do |match|
- yield $2, $3
- end
- end
-#+END_SRC
-
-Now let's test case-insensitive code blocks.
-
-#+begin_src ruby
- # Finds all emphasis matches in a string.
- # Supply a block that will get the marker and body as parameters.
- def match_all(str)
- str.scan(@org_emphasis_regexp) do |match|
- yield $2, $3
- end
- end
-#+end_src
-
-#+begin_src clojure
-(def fib-seq
- (concat
- [0 1]
- ((fn rfib [a b]
- (lazy-cons (+ a b) (rfib b (+ a b)))) 0 1)))
-
-user> (take 20 fib-seq)
-(0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181)
-#+end_src
-
-Even if no language is set, it is still wrapped in code tags but class is empty.
-
-#+BEGIN_SRC
-echo 'Defaults env_keeps="http_proxy https_proxy ftp_proxy"' | sudo tee -a /etc/sudoers
-#+END_SRC
-
-* It should be possible to write a colon at the beginning of an example
-
-#+BEGIN_QUOTE
-I really love to write about
-:symbols. They sure are the
-best things in the world!
-#+END_QUOTE
-
-#+BEGIN_SRC ruby
-{
-:one => 1,
-:two => 2
-}
-#+END_SRC
-
-#+BEGIN_SRC clojure
-(defproject helloworld "0.1"
-:dependencies [[org.clojure/clojure
- "1.1.0-master-SNAPSHOT"]
- [org.clojure/clojure-contrib
- "1.0-SNAPSHOT"]]
-:main helloworld)
-#+END_SRC
View
38 spec/parser_spec.rb
@@ -154,5 +154,43 @@
end
end
end
+
+ describe "Export to HTML test cases with code syntax highlight" do
+ code_syntax_examples_directory = File.join(File.dirname(__FILE__), "html_code_syntax_highlight_examples")
+
+ # Include the code syntax highlight support tests
+ if defined? CodeRay
+ # Use CodeRay for syntax highlight (pure Ruby solution)
+ org_files = File.expand_path(File.join(code_syntax_examples_directory, "*-coderay.org"))
+ elsif defined? Pygments
+ # Use pygments (so that it works with Jekyll, Gollum and possibly Github)
+ org_files = File.expand_path(File.join(code_syntax_examples_directory, "*-pygments.org"))
+ else
+ # Do not use syntax coloring for source code blocks
+ org_files = File.expand_path(File.join(code_syntax_examples_directory, "*-no-color.org"))
+ end
+ files = Dir.glob(org_files)
+
+ files.each do |file|
+ basename = File.basename(file, ".org")
+ textile_name = File.join(code_syntax_examples_directory, basename + ".html")
+ textile_name = File.expand_path(textile_name)
+
+ it "should convert #{basename}.org to HTML" do
+ expected = IO.read(textile_name)
+ expected.should be_kind_of(String)
+ parser = Orgmode::Parser.new(IO.read(file))
+ actual = parser.to_html
+ actual.should be_kind_of(String)
+ actual.should == expected
+ end
+
+ it "should render #{basename}.org to HTML using Tilt templates" do
+ expected = IO.read(textile_name)
+ template = Tilt.new(file).render
+ template.should == expected
+ end
+ end
+ end
end
View
60 tasks/test_case.rake
@@ -42,8 +42,68 @@ namespace :testcase do
p = Orgmode::Parser.new(data)
puts p.to_html
end
+
+ # Special namespace to test syntax highlighting with different technologies
+ namespace :highlight do
+ @code_syntax_examples_directory = File.join(File.dirname(__FILE__), "../spec/html_code_syntax_highlight_examples")
+
+ desc "List all of the current HTML test cases"
+ task :list do
+ org_files = File.expand_path(File.join(@code_syntax_examples_directory, "*.org" ))
+ files = Dir.glob(org_files)
+ files.each do |file|
+ puts File.basename(file, ".org")
+ end
+ end
+
+ desc "Special tests cases for code syntax highlight support"
+ task :accept, :case do |t, args|
+ basename = args[:case]
+ raise "Must supply a test case name. Example: rake testcase:accept[casename]" unless basename
+
+ fname = File.expand_path(File.join(@code_syntax_examples_directory, "#{basename}.org"))
+ oname = File.expand_path(File.join(@code_syntax_examples_directory, "#{basename}.html"))
+
+ data = IO.read(fname)
+ puts "=== #{fname} is: ===>>>\n\n"
+ puts data
+ puts "\n\n=== ACCEPTING OUTPUT: ===>>>\n\n"
+ p = Orgmode::Parser.new(data)
+ puts p.to_html
+ File.open(oname, "w") do |s|
+ s.write(p.to_html)
+ end
+ end
+
+ desc "Inspect code syntax highlight support"
+ task :inspect, :case do |t, args|
+ basename = args[:case]
+ raise "Must supply a test case name. Example: rake testcase:inspecthighlight[casename]" unless basename
+
+ fname = File.expand_path(File.join(@code_syntax_examples_directory, "#{basename}.org"))
+
+ data = IO.read(fname)
+ puts "=== #{fname} is: ===>>>\n\n"
+ puts data
+ puts "\n\n=== #{fname} converts to: ===>>>\n\n"
+ p = Orgmode::Parser.new(data)
+ puts p.to_html
+ end
+ end
+
end
desc "Alias for testcase:list"
task :testcase => ["testcase:list"]
+task :test do
+ puts "Testing without CodeRay nor Pygments for code syntax highlight"
+ system('bundle --without pygments:coderay > /dev/null 2>&1')
+ system('bundle exec rake spec')
+ puts "Testing with CodeRay for code syntax highlight"
+ system('bundle --without pygments > /dev/null 2>&1')
+ system('bundle exec rake spec')
+ puts "Testing with Pygments for code syntax highlight"
+ system('bundle --without coderay > /dev/null 2>&1')
+ system('bundle exec rake spec')
+end

0 comments on commit 8dc7348

Please sign in to comment.