Permalink
Browse files

added support for creole (used by moinmoin wikis)

  • Loading branch information...
flavorjones committed Sep 30, 2010
1 parent bb6cf3c commit ece26dc6740f5e2669ddd3684c2f4b9a6e3755a0
Showing with 290 additions and 4 deletions.
  1. +1 −0 Rakefile
  2. +1 −0 lib/mcbean.rb
  3. +126 −0 lib/mcbean/creole.rb
  4. +7 −4 mcbean.gemspec
  5. +155 −0 test/test_creole.rb
View
@@ -18,6 +18,7 @@ Hoe.spec 'mcbean' do
self.extra_deps << ["loofah", ">= 0.4.7"]
self.extra_deps << ["rdiscount", ">= 1.6.0"]
self.extra_deps << ["RedCloth", ">= 4.2.0"]
+ self.extra_deps << ["creole", ">= 0.3.7"]
self.extra_dev_deps << ["minitest", ">= 1.6.0"]
self.extra_dev_deps << ["hoe-git"]
self.extra_dev_deps << ["hoe-gemspec"]
View
@@ -76,6 +76,7 @@ def __html__ # :nodoc:
require "mcbean/markdown"
require "mcbean/textile"
+require "mcbean/creole"
if Loofah::VERSION < McBean::REQUIRED_LOOFAH_VERSION
raise RuntimeError, "McBean requires Loofah #{McBean::REQUIRED_LOOFAH_VERSION} or later (currently #{Loofah::VERSION})"
View
@@ -0,0 +1,126 @@
+require 'creole'
+
+class McBean
+ attr_writer :__creole__ # :nodoc:
+
+ @@__formats__ << "creole"
+
+ ##
+ # Create a McBean from a Creole document string (or IO object)
+ #
+ def McBean.creole string_or_io
+ mcbean = new
+ mcbean.__creole__ = McBean::Creolize::Antidote.new(string_or_io.respond_to?(:read) ? string_or_io.read : string_or_io)
+ mcbean
+ end
+
+ ##
+ # Generate a Creole string representation of the McBeaned document.
+ #
+ # So you can convert documents in other formats to Creole as follows:
+ #
+ # McBean.fragment(File.read(path_to_html_file)).to_creole
+ #
+ def to_creole
+ __creole__.to_s
+ end
+
+ def __creole__ generate_from_html_if_necessary=true # :nodoc:
+ @__creole__ ||= nil
+ if @__creole__.nil? && generate_from_html_if_necessary
+ @__creole__ = McBean::Creolize::Antidote.new(
+ Loofah::Helpers.remove_extraneous_whitespace(
+ __html__.dup.scrub!(:escape).scrub!(Creolize.new).text(:encode_special_chars => false)
+ ))
+ end
+ @__creole__
+ end
+
+ # :stopdoc:
+ class Creolize < Loofah::Scrubber
+ class Antidote
+ attr_accessor :string
+ def initialize string
+ @string = string
+ end
+
+ def to_html
+ # interesting (or not) that the Creole authors use 'creolize'
+ # in the opposite sense that I do.
+ Creole.creolize string, :extensions => false
+ end
+
+ def to_s
+ string
+ end
+ end
+
+ def initialize
+ @direction = :bottom_up
+ @link_references = nil
+ @link_reference_count = 0
+ end
+
+ def scrub(node)
+ return CONTINUE if node.text?
+ replacement_killer = \
+ case node.name
+ when "h1"
+ new_text node, "\n= #{node.content} =\n"
+ when "h2"
+ new_text node, "\n== #{node.content} ==\n"
+ when "h3"
+ new_text node, "\n=== #{node.content} ===\n"
+ when "h4"
+ new_text node, "\n==== #{node.content} ====\n"
+ # when "blockquote"
+ # new_text node, "\nbq. #{node.content.gsub(/\n\n/, "\n").sub(/^\n/,'')}"
+ when "li"
+ nil # handled by parent list tag
+ when "ul"
+ fragment = []
+ node.xpath("./li").each do |li|
+ fragment << "* #{li.text}" if li.text =~ /\S/
+ end
+ new_text node, "\n#{fragment.join("\n")}\n"
+ when "ol"
+ fragment = []
+ node.xpath("./li").each do |li|
+ fragment << "# #{li.text}" if li.text =~ /\S/
+ end
+ new_text node, "\n#{fragment.join("\n")}\n"
+ when "code"
+ if node.parent.name == "pre"
+ new_text node, node.content
+ else
+ nil
+ end
+ when "pre"
+ new_text node, "\n{{{\n#{node.content}\n}}}\n"
+
+ when "br"
+ new_text node, "\\\\"
+ when "a"
+ if node['href'].nil?
+ new_text node, node.content
+ else
+ new_text node, %Q{[[#{node['href']}|#{node.text}]]}
+ end
+ else
+ if Loofah::HashedElements::BLOCK_LEVEL[node.name]
+ new_text node, "\n#{node.content}\n"
+ else
+ nil
+ end
+ end
+ node.replace(replacement_killer) if replacement_killer
+ end
+
+ private
+
+ def new_text(node, text)
+ Nokogiri::XML::Text.new(text, node.document)
+ end
+ end
+ # :startdoc:
+end
View
@@ -2,11 +2,11 @@
Gem::Specification.new do |s|
s.name = %q{mcbean}
- s.version = "0.3.0.20100929225458"
+ s.version = "0.3.0.20100930005041"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Mike Dalessio"]
- s.date = %q{2010-09-29}
+ s.date = %q{2010-09-30}
s.default_executable = %q{mcbean}
s.description = %q{McBean can convert documents from one format to another. McBean currently supports:
@@ -20,14 +20,14 @@ with the help of Loofah, Nokogiri, RDiscount and RedCloth.
s.email = ["mike.dalessio@gmail.com"]
s.executables = ["mcbean"]
s.extra_rdoc_files = ["Manifest.txt", "CHANGELOG.rdoc", "README.rdoc"]
- s.files = [".autotest", "CHANGELOG.rdoc", "Manifest.txt", "README.rdoc", "Rakefile", "bin/mcbean", "lib/mcbean.rb", "lib/mcbean/markdown.rb", "lib/mcbean/textile.rb", "test/helper.rb", "test/test_markdown.rb", "test/test_mcbean.rb", "test/test_textile.rb", "test/test_wiki.rb"]
+ s.files = [".autotest", "CHANGELOG.rdoc", "Manifest.txt", "README.rdoc", "Rakefile", "bin/mcbean", "lib/mcbean.rb", "lib/mcbean/markdown.rb", "lib/mcbean/textile.rb", "test/helper.rb", "test/test_markdown.rb", "test/test_mcbean.rb", "test/test_textile.rb", "test/test_creole.rb"]
s.homepage = %q{http://github.com/flavorjones/mcbean}
s.rdoc_options = ["--main", "README.rdoc"]
s.require_paths = ["lib"]
s.rubyforge_project = %q{mcbean}
s.rubygems_version = %q{1.3.7}
s.summary = %q{McBean can convert documents from one format to another}
- s.test_files = ["test/test_wiki.rb", "test/test_markdown.rb", "test/test_mcbean.rb", "test/test_textile.rb"]
+ s.test_files = ["test/test_markdown.rb", "test/test_mcbean.rb", "test/test_textile.rb", "test/test_creole.rb"]
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
@@ -37,6 +37,7 @@ with the help of Loofah, Nokogiri, RDiscount and RedCloth.
s.add_runtime_dependency(%q<loofah>, [">= 0.4.7"])
s.add_runtime_dependency(%q<rdiscount>, [">= 1.6.0"])
s.add_runtime_dependency(%q<RedCloth>, [">= 4.2.0"])
+ s.add_runtime_dependency(%q<creole>, [">= 0.3.7"])
s.add_development_dependency(%q<rubyforge>, [">= 2.0.4"])
s.add_development_dependency(%q<minitest>, [">= 1.6.0"])
s.add_development_dependency(%q<hoe-git>, [">= 0"])
@@ -47,6 +48,7 @@ with the help of Loofah, Nokogiri, RDiscount and RedCloth.
s.add_dependency(%q<loofah>, [">= 0.4.7"])
s.add_dependency(%q<rdiscount>, [">= 1.6.0"])
s.add_dependency(%q<RedCloth>, [">= 4.2.0"])
+ s.add_dependency(%q<creole>, [">= 0.3.7"])
s.add_dependency(%q<rubyforge>, [">= 2.0.4"])
s.add_dependency(%q<minitest>, [">= 1.6.0"])
s.add_dependency(%q<hoe-git>, [">= 0"])
@@ -58,6 +60,7 @@ with the help of Loofah, Nokogiri, RDiscount and RedCloth.
s.add_dependency(%q<loofah>, [">= 0.4.7"])
s.add_dependency(%q<rdiscount>, [">= 1.6.0"])
s.add_dependency(%q<RedCloth>, [">= 4.2.0"])
+ s.add_dependency(%q<creole>, [">= 0.3.7"])
s.add_dependency(%q<rubyforge>, [">= 2.0.4"])
s.add_dependency(%q<minitest>, [">= 1.6.0"])
s.add_dependency(%q<hoe-git>, [">= 0"])
View
@@ -0,0 +1,155 @@
+require File.dirname(__FILE__) + "/helper"
+
+describe McBean::Creolize do
+ describe ".creole" do
+ describe "passed a string" do
+ it "sets #__creole__ to be a Creolize::Antidote" do
+ McBean.creole("= hello =").__creole__.must_be_instance_of McBean::Creolize::Antidote
+ end
+ end
+
+ describe "passed an IO" do
+ it "sets #__creole__ to be a Creolize::Antidote" do
+ io = StringIO.new "= hello ="
+ McBean.creole(io).__creole__.must_be_instance_of McBean::Creolize::Antidote
+ end
+ end
+ end
+
+ describe "#to_html" do
+ attr_accessor :mcbean
+
+ describe "on an instance created by .creole" do
+ before do
+ @mcbean = McBean.creole "= ohai! =\n\n"
+ end
+
+ it "returns an html string" do
+ html = mcbean.to_html
+ html.must_be_instance_of String
+ html.must_match %r{<h1>ohai!</h1>}
+ end
+ end
+ end
+
+ describe "#to_creole" do
+ it "adds whitespace around block elements" do
+ assert_creole "before<div>inner</div>after", "before\ninner\nafter", false
+ end
+
+ it "converts h1 tag" do
+ assert_creole "<h1>Foo</h1>", "\n= Foo =\n"
+ end
+
+ it "converts h2 tag" do
+ assert_creole "<h2>Foo</h2>", "\n== Foo ==\n"
+ end
+
+ it "converts h3 tag" do
+ assert_creole "<h3>Foo</h3>", "\n=== Foo ===\n"
+ end
+
+ it "converts h4 tag" do
+ assert_creole "<h4>Foo</h4>", "\n==== Foo ====\n"
+ end
+
+ # it "converts blockquote tag" do
+ # assert_creole "<blockquote>\n<p>foo fazz<br />\nbizz buzz<br />\nwang wong</p>\n</blockquote>", "\n foo fazz\n bizz buzz\n wang wong\n"
+ # end
+
+ it "converts ul lists" do
+ html = "<ul><li>foo</li><li>wuxx</li></ul>"
+ creole = "\n* foo\n* wuxx\n"
+ assert_creole html, creole
+ end
+
+ it "converts ol lists" do
+ html = "<ol><li>foo</li><li>wuxx</li></ol>"
+ creole = "\n# foo\n# wuxx\n"
+ assert_creole html, creole
+ end
+
+ it "ignores empty unordered list items" do
+ assert_creole "<ul>\n<li>one</li>\n<li></li>\n<li>three</li>\n</ul>\n",
+ "\n* one\n* three\n",
+ false
+ end
+
+ it "ignores empty ordered list items" do
+ assert_creole "<ol>\n<li>one</li>\n<li></li>\n<li>three</li>\n</ol>\n",
+ "\n# one\n# three\n",
+ false
+ end
+
+ it "converts code blocks" do
+ assert_creole "<pre><code>This is a code block\ncontinued</code></pre>",
+ "\n{{{\nThis is a code block\ncontinued\n}}}\n", false
+ end
+
+ it "converts pre blocks" do
+ assert_creole "<pre>This is a pre block\ncontinued</pre>",
+ "\n{{{\nThis is a pre block\ncontinued\n}}}\n"
+ end
+
+ it "converts <br> tags to '\\'" do
+ assert_creole "<p>hello<br>there</p>", "\nhello\\\\there\n", false
+ assert_creole "<p>hello<br/>there</p>", "\nhello\\\\there\n"
+ end
+
+ describe "anchors" do
+ it "converts <a> tags" do
+ assert_creole "<p>Yes, magic helmet. And <a href=\"http://sample.com/\">I will give you a sample</a>.</p>",
+ %Q{\nYes, magic helmet. And [[http://sample.com/|I will give you a sample]].\n}
+ end
+
+ describe "<a> tags without hrefs" do
+ it "ignores them" do
+ assert_creole "<div><a name='link-target'>target title</a></div>",
+ "\ntarget title\n",
+ false
+
+ assert_creole "<div><a id='link-target'>target title</a></div>",
+ "\ntarget title\n",
+ false
+ end
+ end
+
+ describe "<a> tags with titles" do
+ it "ignores the title" do
+ assert_creole %Q{<p>Yes, magic helmet. And <a href="http://sample.com/" title="Fudd">I will give you a sample</a>.</p>},
+ %Q{\nYes, magic helmet. And [[http://sample.com/|I will give you a sample]].\n}, false
+ end
+ end
+ end
+ end
+
+ def assert_creole html, creole, roundtrip=true
+ assert_equal(html, McBean::Creolize::Antidote.new(creole).to_html, "creole roundtrip failed") if roundtrip
+ assert_equal(creole, McBean.fragment(html).to_creole, "fragment transformation failed")
+ assert_equal(Loofah::Helpers.remove_extraneous_whitespace("\n#{creole}\n"),
+ McBean.document("<div>#{html}</div>").to_creole, "document transformation failed")
+ end
+
+end
+
+describe McBean::Creolize::Antidote do
+ describe "given that creole is already pretty well tested, let's limit ourselves to a token test case" do
+ it "convert creole into html" do
+ McBean.creole("= hello =\n").to_html.must_match %r{<body><h1>hello</h1></body>}
+ end
+ end
+
+ describe "given creole with an unsafe tag" do
+ it "escapes the tag" do
+ creole = "= hello =\n\n<script>alert('ohai!');</script>\n\nlol\n"
+ McBean.creole(creole).to_html.must_include "&lt;script&gt;alert('ohai!');&lt;/script&gt;"
+ end
+ end
+
+ describe "given creole with an invalid tag" do
+ it "escapes the tag" do
+ creole = "= hello =\n\n<xyzzy>Adventure!</xyzzy>\n\nlol\n"
+ McBean.creole(creole).to_html.must_match %r{&lt;xyzzy&gt;Adventure!&lt;/xyzzy&gt;}
+ end
+ end
+end

0 comments on commit ece26dc

Please sign in to comment.