diff --git a/doc/config.yml.sample b/doc/config.yml.sample index 5550ca1af..a9c10fe56 100644 --- a/doc/config.yml.sample +++ b/doc/config.yml.sample @@ -172,9 +172,9 @@ secnolevel: 2 # fontdir内から取り込まれる対象となるファイル拡張子。省略した場合は以下 # font_ext: ["ttf", "woff", "otf"] -# ソースコードハイライトを利用する (pygmentsには外部gemが必要) +# ソースコードハイライトを利用する (rouge,pygmentsには外部gemが必要) # highlight: -# html: "pygments" +# html: "rouge" # latex: "listings" # カタログファイル名を指定する diff --git a/doc/config.yml.sample-simple b/doc/config.yml.sample-simple index 8e40dcd8e..4f2b98e0c 100644 --- a/doc/config.yml.sample-simple +++ b/doc/config.yml.sample-simple @@ -51,7 +51,7 @@ colophon: true ## Syntax Highlighting highlight: - html: "pygments" + html: "rouge" latex: "listings" ## for HTML diff --git a/doc/sample.css b/doc/sample.css index 909554813..1e4d2d966 100644 --- a/doc/sample.css +++ b/doc/sample.css @@ -107,6 +107,220 @@ p.flushright { text-align: right; } +/** + * from Rouge + */ +.highlight table td { padding: 5px; } +.highlight table pre { margin: 0; } +.highlight .cm { + color: #999988; + font-style: italic; +} +.highlight .cp { + color: #999999; + font-weight: bold; +} +.highlight .c1 { + color: #999988; + font-style: italic; +} +.highlight .cs { + color: #999999; + font-weight: bold; + font-style: italic; +} +.highlight .c, .highlight .cd { + color: #999988; + font-style: italic; +} +.highlight .err { + color: #a61717; + background-color: #e3d2d2; +} +.highlight .gd { + color: #000000; + background-color: #ffdddd; +} +.highlight .ge { + color: #000000; + font-style: italic; +} +.highlight .gr { + color: #aa0000; +} +.highlight .gh { + color: #999999; +} +.highlight .gi { + color: #000000; + background-color: #ddffdd; +} +.highlight .go { + color: #888888; +} +.highlight .gp { + color: #555555; +} +.highlight .gs { + font-weight: bold; +} +.highlight .gu { + color: #aaaaaa; +} +.highlight .gt { + color: #aa0000; +} +.highlight .kc { + color: #000000; + font-weight: bold; +} +.highlight .kd { + color: #000000; + font-weight: bold; +} +.highlight .kn { + color: #000000; + font-weight: bold; +} +.highlight .kp { + color: #000000; + font-weight: bold; +} +.highlight .kr { + color: #000000; + font-weight: bold; +} +.highlight .kt { + color: #445588; + font-weight: bold; +} +.highlight .k, .highlight .kv { + color: #000000; + font-weight: bold; +} +.highlight .mf { + color: #009999; +} +.highlight .mh { + color: #009999; +} +.highlight .il { + color: #009999; +} +.highlight .mi { + color: #009999; +} +.highlight .mo { + color: #009999; +} +.highlight .m, .highlight .mb, .highlight .mx { + color: #009999; +} +.highlight .sb { + color: #d14; +} +.highlight .sc { + color: #d14; +} +.highlight .sd { + color: #d14; +} +.highlight .s2 { + color: #d14; +} +.highlight .se { + color: #d14; +} +.highlight .sh { + color: #d14; +} +.highlight .si { + color: #d14; +} +.highlight .sx { + color: #d14; +} +.highlight .sr { + color: #009926; +} +.highlight .s1 { + color: #d14; +} +.highlight .ss { + color: #990073; +} +.highlight .s { + color: #d14; +} +.highlight .na { + color: #008080; +} +.highlight .bp { + color: #999999; +} +.highlight .nb { + color: #0086B3; +} +.highlight .nc { + color: #445588; + font-weight: bold; +} +.highlight .no { + color: #008080; +} +.highlight .nd { + color: #3c5d5d; + font-weight: bold; +} +.highlight .ni { + color: #800080; +} +.highlight .ne { + color: #990000; + font-weight: bold; +} +.highlight .nf { + color: #990000; + font-weight: bold; +} +.highlight .nl { + color: #990000; + font-weight: bold; +} +.highlight .nn { + color: #555555; +} +.highlight .nt { + color: #000080; +} +.highlight .vc { + color: #008080; +} +.highlight .vg { + color: #008080; +} +.highlight .vi { + color: #008080; +} +.highlight .nv { + color: #008080; +} +.highlight .ow { + color: #000000; + font-weight: bold; +} +.highlight .o { + color: #000000; + font-weight: bold; +} +.highlight .w { + color: #bbbbbb; +} +.highlight { + background-color: #f8f8f8; +} +.rouge-table { border-spacing: 0 } +.rouge-gutter { text-align: right } /** * from EBPAJ EPUB 3 File Creation Guide sample style diff --git a/lib/review/htmlbuilder.rb b/lib/review/htmlbuilder.rb index 918ca8acd..3b713645a 100644 --- a/lib/review/htmlbuilder.rb +++ b/lib/review/htmlbuilder.rb @@ -450,6 +450,7 @@ def list_body(id, lines, lang) class_names = ["list"] lexer = lang || File.extname(id).gsub(/\./, '') class_names.push("language-#{lexer}") unless lexer.blank? + class_names.push("highlight") if highlight? print %Q[
]
       body = lines.inject(''){|i, j| i + detab(j) + "\n"}
       puts highlight(:body => body, :lexer => lexer, :format => 'html')
@@ -494,11 +495,12 @@ def listnum_body(lines, lang)
         body = lines.inject(''){|i, j| i + detab(j) + "\n"}
         lexer = lang
         first_line_number = get_line_num
-        puts highlight(:body => body, :lexer => lexer, :format => 'html',
-                       :options => {:linenos => 'inline', :nowrap => false, :linenostart => first_line_number})
+        puts highlight(:body => body, :lexer => lexer, :format => 'html', :linenum => true,
+                       :options => {:linenostart => first_line_number})
       else
         class_names = ["list"]
         class_names.push("language-#{lang}") unless lang.blank?
+        class_names.push("highlight") if highlight?
         print %Q[
]
         first_line_num = get_line_num
         lines.each_with_index do |line, i|
@@ -515,6 +517,7 @@ def emlist(lines, caption = nil, lang = nil)
       end
       class_names = ["emlist"]
       class_names.push("language-#{lang}") unless lang.blank?
+      class_names.push("highlight") if highlight?
       print %Q[
]
       body = lines.inject(''){|i, j| i + detab(j) + "\n"}
       lexer = lang
@@ -533,11 +536,12 @@ def emlistnum(lines, caption = nil, lang = nil)
         body = lines.inject(''){|i, j| i + detab(j) + "\n"}
         lexer = lang
         first_line_number = get_line_num
-        puts highlight(:body => body, :lexer => lexer, :format => 'html',
-                       :options => {:linenos => 'inline', :nowrap => false, :linenostart => first_line_number})
+        puts highlight(:body => body, :lexer => lexer, :format => 'html', :linenum => true,
+                       :options => {:linenostart => first_line_number})
       else
         class_names = ["emlist"]
         class_names.push("language-#{lang}") unless lang.blank?
+        class_names.push("highlight") if highlight?
         print %Q[
]
         first_line_num = get_line_num
         lines.each_with_index do |line, i|
diff --git a/lib/review/htmlutils.rb b/lib/review/htmlutils.rb
index 8aef9edf2..9442de40c 100644
--- a/lib/review/htmlutils.rb
+++ b/lib/review/htmlutils.rb
@@ -9,6 +9,7 @@
 #
 
 require 'cgi/util'
+require 'rouge'
 module ReVIEW
 
   module HTMLUtils
@@ -44,14 +45,25 @@ def escape_comment(str)
 
     def highlight?
       @book.config["highlight"] &&
-        @book.config["highlight"]["html"] == "pygments"
+        @book.config["highlight"]["html"]
     end
 
     def highlight(ops)
       if @book.config["pygments"].present?
         raise ReVIEW::ConfigError, "'pygments:' in config.yml is obsoleted."
       end
+      return ops[:body].to_s if !highlight?
 
+      if @book.config["highlight"]["html"] == "pygments"
+        highlight_pygments(ops)
+      elsif @book.config["highlight"]["html"] == "rouge"
+        highlight_rouge(ops)
+      else
+        raise ReVIEW::ConfigError, "unknown highlight method #{@book.config["highlight"]["html"]} in config.yml."
+      end
+    end
+
+    def highlight_pygments(ops)
       body = ops[:body] || ''
       if @book.config["highlight"] && @book.config["highlight"]["lang"]
         lexer = @book.config["highlight"]["lang"] # default setting
@@ -61,10 +73,13 @@ def highlight(ops)
       lexer = ops[:lexer] if ops[:lexer].present?
       format = ops[:format] || ''
       options = {:nowrap => true, :noclasses => true}
+      if ops[:linenum]
+        options[:nowrap] = false
+        options[:linenos] = 'inline'
+      end
       if ops[:options] && ops[:options].kind_of?(Hash)
         options.merge!(ops[:options])
       end
-      return body if !highlight?
 
       begin
         require 'pygments'
@@ -81,6 +96,37 @@ def highlight(ops)
       end
     end
 
+    def highlight_rouge(ops)
+      body = ops[:body] || ''
+      if ops[:lexer].present?
+        lexer = ops[:lexer]
+      elsif @book.config["highlight"] && @book.config["highlight"]["lang"]
+        lexer = @book.config["highlight"]["lang"] # default setting
+      else
+        lexer = 'text'
+      end
+      format = ops[:format] || ''
+
+      first_line_num = 1 ## default
+      if ops[:options] && ops[:options][:linenostart]
+        first_line_num = ops[:options][:linenostart]
+      end
+
+      lexer = Rouge::Lexer.find(lexer)
+      raise "unknown lexer #{lexer}" unless lexer
+
+      formatter = Rouge::Formatters::HTML.new(:css_class => 'highlight')
+      if ops[:linenum]
+        formatter = Rouge::Formatters::HTMLTable.new(formatter,
+                                                     :table_class => 'highlight rouge-table',
+                                                     :start_line => first_line_num)
+      end
+      raise "unknown formatter #{formatter}" unless formatter
+
+      text = unescape_html(body)
+      formatter.format(lexer.lex(text))
+    end
+
     def normalize_id(id)
       if id =~ /\A[a-z][a-z0-9_.-]*\Z/i
         return id
diff --git a/review.gemspec b/review.gemspec
index 5b03e182c..c11e64c06 100644
--- a/review.gemspec
+++ b/review.gemspec
@@ -24,6 +24,7 @@ Gem::Specification.new do |gem|
   gem.require_paths = ["lib"]
 
   gem.add_dependency("rubyzip")
+  gem.add_dependency("rouge")
   gem.add_development_dependency("rake")
   gem.add_development_dependency("test-unit")
   gem.add_development_dependency("pygments.rb")
diff --git a/test/sample-book/src/style.css b/test/sample-book/src/style.css
index 75c67c1bd..a30603cdb 100644
--- a/test/sample-book/src/style.css
+++ b/test/sample-book/src/style.css
@@ -250,6 +250,221 @@ em {
     font-style: italic;
 }
 
+/**
+ * from Rouge
+ */
+.highlight table td { padding: 5px; }
+.highlight table pre { margin: 0; }
+.highlight .cm {
+  color: #999988;
+  font-style: italic;
+}
+.highlight .cp {
+  color: #999999;
+  font-weight: bold;
+}
+.highlight .c1 {
+  color: #999988;
+  font-style: italic;
+}
+.highlight .cs {
+  color: #999999;
+  font-weight: bold;
+  font-style: italic;
+}
+.highlight .c, .highlight .cd {
+  color: #999988;
+  font-style: italic;
+}
+.highlight .err {
+  color: #a61717;
+  background-color: #e3d2d2;
+}
+.highlight .gd {
+  color: #000000;
+  background-color: #ffdddd;
+}
+.highlight .ge {
+  color: #000000;
+  font-style: italic;
+}
+.highlight .gr {
+  color: #aa0000;
+}
+.highlight .gh {
+  color: #999999;
+}
+.highlight .gi {
+  color: #000000;
+  background-color: #ddffdd;
+}
+.highlight .go {
+  color: #888888;
+}
+.highlight .gp {
+  color: #555555;
+}
+.highlight .gs {
+  font-weight: bold;
+}
+.highlight .gu {
+  color: #aaaaaa;
+}
+.highlight .gt {
+  color: #aa0000;
+}
+.highlight .kc {
+  color: #000000;
+  font-weight: bold;
+}
+.highlight .kd {
+  color: #000000;
+  font-weight: bold;
+}
+.highlight .kn {
+  color: #000000;
+  font-weight: bold;
+}
+.highlight .kp {
+  color: #000000;
+  font-weight: bold;
+}
+.highlight .kr {
+  color: #000000;
+  font-weight: bold;
+}
+.highlight .kt {
+  color: #445588;
+  font-weight: bold;
+}
+.highlight .k, .highlight .kv {
+  color: #000000;
+  font-weight: bold;
+}
+.highlight .mf {
+  color: #009999;
+}
+.highlight .mh {
+  color: #009999;
+}
+.highlight .il {
+  color: #009999;
+}
+.highlight .mi {
+  color: #009999;
+}
+.highlight .mo {
+  color: #009999;
+}
+.highlight .m, .highlight .mb, .highlight .mx {
+  color: #009999;
+}
+.highlight .sb {
+  color: #d14;
+}
+.highlight .sc {
+  color: #d14;
+}
+.highlight .sd {
+  color: #d14;
+}
+.highlight .s2 {
+  color: #d14;
+}
+.highlight .se {
+  color: #d14;
+}
+.highlight .sh {
+  color: #d14;
+}
+.highlight .si {
+  color: #d14;
+}
+.highlight .sx {
+  color: #d14;
+}
+.highlight .sr {
+  color: #009926;
+}
+.highlight .s1 {
+  color: #d14;
+}
+.highlight .ss {
+  color: #990073;
+}
+.highlight .s {
+  color: #d14;
+}
+.highlight .na {
+  color: #008080;
+}
+.highlight .bp {
+  color: #999999;
+}
+.highlight .nb {
+  color: #0086B3;
+}
+.highlight .nc {
+  color: #445588;
+  font-weight: bold;
+}
+.highlight .no {
+  color: #008080;
+}
+.highlight .nd {
+  color: #3c5d5d;
+  font-weight: bold;
+}
+.highlight .ni {
+  color: #800080;
+}
+.highlight .ne {
+  color: #990000;
+  font-weight: bold;
+}
+.highlight .nf {
+  color: #990000;
+  font-weight: bold;
+}
+.highlight .nl {
+  color: #990000;
+  font-weight: bold;
+}
+.highlight .nn {
+  color: #555555;
+}
+.highlight .nt {
+  color: #000080;
+}
+.highlight .vc {
+  color: #008080;
+}
+.highlight .vg {
+  color: #008080;
+}
+.highlight .vi {
+  color: #008080;
+}
+.highlight .nv {
+  color: #008080;
+}
+.highlight .ow {
+  color: #000000;
+  font-weight: bold;
+}
+.highlight .o {
+  color: #000000;
+  font-weight: bold;
+}
+.highlight .w {
+  color: #bbbbbb;
+}
+.highlight {
+  background-color: #f8f8f8;
+}
+.rouge-table { border-spacing: 0 }
+.rouge-gutter { text-align: right }
+
 /**
  * from EBPAJ EPUB 3 File Creation Guide sample style
  *
diff --git a/test/test_htmlbuilder.rb b/test/test_htmlbuilder.rb
index b19834a17..a5627096f 100644
--- a/test/test_htmlbuilder.rb
+++ b/test/test_htmlbuilder.rb
@@ -557,7 +557,7 @@ def @chapter.list(id)
     expected = <<-EOS
 

リスト1.1: this is test<&>_

-
test1
+
test1
 test1.5
 
 test<i>2</i>
@@ -583,7 +583,7 @@ def @chapter.list(id)
 
     assert_equal %Q|
\n| + %Q|

リスト1.1: this is test<&>_

\n| + - %Q|
def foo(a1, a2=:test)\n| +
+                 %Q|
def foo(a1, a2=:test)\n| +
                  %Q|  (1..3).times{\|i\| a.include?(:foo)}\n| +
                  %Q|  return true\n| +
                  %Q|end\n| +
@@ -608,7 +608,7 @@ def @chapter.list(id)
     expected = <<-EOS
 

リスト1.1: this is test<&>_

-
def foo(a1, a2=:test)
+
def foo(a1, a2=:test)
   (1..3).times{|i| a.include?(:foo)}
   return true
 end
@@ -618,6 +618,49 @@ def @chapter.list(id)
     assert_equal expected, actual
   end
 
+  def test_list_rouge
+    def @chapter.list(id)
+      Book::ListIndex::Item.new("samplelist",1)
+    end
+    @book.config["highlight"] = {}
+    @book.config["highlight"]["html"] = "rouge"
+    actual = compile_block("//list[samplelist][this is @{test}<&>_]{\ntest1\ntest1.5\n\ntest@{2}\n//}\n")
+
+    assert_equal %Q|
\n

リスト1.1: this is test<&>_

\n
test1\ntest1.5\n\ntest<i>2</i>\n
\n
\n|, actual + end + + def test_list_rouge_lang + def @chapter.list(id) + Book::ListIndex::Item.new("samplelist",1) + end + @book.config["highlight"] = {} + @book.config["highlight"]["html"] = "rouge" + actual = compile_block("//list[samplelist][this is @{test}<&>_][ruby]{\ndef foo(a1, a2=:test)\n (1..3).times{|i| a.include?(:foo)}\n return true\nend\n\n//}\n") + + expected = "
\n" + + "

リスト1.1: this is test<&>_

\n" + + "
def foo(a1, a2=:test)\n" +
+	           "  (1..3).times{|i| a.include?(:foo)}\n" +
+	           "  return true\n" +
+	           "end\n" +
+	           "\n" +
+	           "
\n" + + "
\n" + + assert_equal expected, actual + end + + def test_list_rouge_nulllang + def @chapter.list(id) + Book::ListIndex::Item.new("samplelist",1) + end + @book.config["highlight"] = {} + @book.config["highlight"]["html"] = "rouge" + actual = compile_block("//list[samplelist][this is @{test}<&>_][]{\ndef foo(a1, a2=:test)\n (1..3).times{|i| a.include?(:foo)}\n return true\nend\n\n//}\n") + + assert_equal "
\n

リスト1.1: this is test<&>_

\n
def foo(a1, a2=:test)\n  (1..3).times{|i| a.include?(:foo)}\n  return true\nend\n\n
\n
\n", actual + end + def test_list_ext def @chapter.list(id) Book::ListIndex::Item.new("samplelist.rb",1) @@ -768,6 +811,44 @@ def @chapter.list(id) assert_equal expected, actual end + def test_listnum_rouge_lang + def @chapter.list(id) + Book::ListIndex::Item.new("samplelist",1) + end + @book.config["highlight"] = {} + @book.config["highlight"]["html"] = "rouge" + actual = compile_block("//listnum[samplelist][this is @{test}<&>_][ruby]{\ndef foo(a1, a2=:test)\n (1..3).times{|i| a.include?(:foo)}\n return true\nend\n\n//}\n") + + assert_equal "
\n

リスト1.1: this is test<&>_

\n
1\n2\n3\n4\n5\n
def foo(a1, a2=:test)\n  (1..3).times{|i| a.include?(:foo)}\n  return true\nend\n\n
\n
\n", actual + end + + def test_listnum_rouge_lang_linenum + def @chapter.list(id) + Book::ListIndex::Item.new("samplelist",1) + end + @book.config["highlight"] = {} + @book.config["highlight"]["html"] = "rouge" + actual = compile_block("//firstlinenum[100]\n//listnum[samplelist][this is @{test}<&>_][ruby]{\ndef foo(a1, a2=:test)\n (1..3).times{|i| a.include?(:foo)}\n return true\nend\n\n//}\n") + + expected = <<-EOB +
+

リスト1.1: this is test<&>_

+
100
+101
+102
+103
+104
+
def foo(a1, a2=:test)
+  (1..3).times{|i| a.include?(:foo)}
+  return true
+end
+
+
+
+EOB + + assert_equal expected, actual + end def test_emlist actual = compile_block("//emlist{\nlineA\nlineB\n//}\n") @@ -786,7 +867,7 @@ def test_emlist_pygments_lang actual = compile_block("//emlist[][sql]{\nSELECT COUNT(*) FROM tests WHERE tests.no > 10 AND test.name LIKE 'ABC%'\n//}\n") expected = <<-EOS
-
SELECT COUNT(*) FROM tests WHERE tests.no > 10 AND test.name LIKE 'ABC%'
+
SELECT COUNT(*) FROM tests WHERE tests.no > 10 AND test.name LIKE 'ABC%'
 
EOS