Implementing Coderay with Redcloth and Haml

Published 18 January 2010

I wanted to implement code highlighting for our Arctic Kiwi website just to make the posts a little easier on the eye. \r\nI first tried out the redcloth-with-coderay gem which you can find on \github\. I had mixed results with this gem and I didn’t need all the functionality it provided. I would recommend taking a look though if the implementation below isn’t to your taste. I ended up taking the approach Ryan Bates uses for his railscasts website.\r\n\r\nTo start make sure you have the required config.gem lines in your environment.rb.\r\n@@@ ruby\r\ do |config|\r\n config.gem \“RedCloth\”\r\n config.gem \“haml\”\r\n config.gem \“coderay\”\r\n …\r\nend\r\n@@@\r\nNow create a file in you lib directory called textilizer.rb and insert the following code.\r\n@@@ ruby\r\nclass Textilizer\r\n def initialize(text)\r\n text = text\r\n end\r\n \r\n def to_html\r\n\r\n end\r\n \r\n def formatted_text\r\n @text.gsub(/^@ ?(\\w*)\\r?\\n(.+?)\\r?\\n@\\r?$/m) do |match|\r\n lang = $1.empty? ? nil : $1\r\n \"\\n<notextile>\" + CodeRay.scan($2, lang).div(:css => :class) + \"</notextile>\"\r\n end\r\n end\r\nend\r\n@\r\nThe code is fairly straight forward, it stores the text provided in an instance variable when a new textilizer instance is created. When to_html is called on the instance it calls Redcloth's to_html method after parsing the text through the regular expression in the formatted text method.\r\nThe formatted text method finds blocks of code wrapped in @@ and passes it to coderay for syntax highlighting. It then wraps each of these blocks in no-textile tags so Redcloth will ignore the contents.\r\n\r\nIn your application_helper.rb file add the following code which will allow you to use a textilize method call in your views.\r\n@@@ ruby\r\nmodule ApplicationHelper\r\n def textilize(text)\r\n unless text.blank?\r\n end\r\nend\r\n@@@\r\nNow in your views you can call the textilize method as follows.\r\n@@@ ruby\r\n~ textilize object.content #haml\r\n@\r\n@ rhtml\r\n<%= textilize @object.content %> #erb\r\n@\r\nNotice when using haml that the ~ sign is used. This is important as it tells haml to preserve the whitespace and ommitting the ~ will cause the output to be formatted incorrectly. If you don't like the ~ (tilde) syntax you can use (as described \"here\": \r\n@ ruby\r\n= find_and_preserve(textilize @object.content)\r\n@\r\nFinally to incorporate the code highlighting in your text just wrap the code block as shown below.\r\n@\r\n @@ \r\n code\r\n @\r\n@@@\r\nThe argument can be omitted, and you will get your code in a pre block without any syntax highlighting. The \Coderay\ examples and documentation are very good and show you which languages are currently supported.\r\nI may turn this into a mini github project, but that is for another post.\r\nOne last note, as we all write well tested code the following specs (written in rspec) should round out your implementation.\r\n@@@ ruby\r\ndescribe Textilizer do\r\n def textilize(text)\r\n\r\n end\r\n \r\n it \“should do basic textile\” do\r\n textilize(\“hello world\”).should == \“

hello world

\”\r\n end\r\n \r\n it \“should wrap @ in a code block\” do\r\n textilize(\“@\\nfoo\\n@@@\”).strip.should == CodeRay.scan(‘foo’, nil).div(:css => :class).strip\r\n end\r\n \r\n it \“should not process textile in code block\” do\r\n textilize(\“@\\nfoo bar\\n@@@\”).strip.should == CodeRay.scan(‘foo bar’, nil).div(:css => :class).strip\r\n end\r\n \r\n it \“allow language for code block\” do\r\n textilize(\“@ ruby\\n@foo\\n@@@\”).strip.should == CodeRay.scan(‘@foo’, ‘ruby’).div(:css => :class).strip\r\n end\r\n \r\n it \“allow code block in the middle\” do\r\n textilize(\“foo\\n@@@\\ntest\\n@@@\\nbar\”).should include(CodeRay.scan(‘test’, ‘ruby’).div(:css => :class).strip)\r\n end\r\n \r\n it \“should handle \\\\r in code block\” do\r\n textilize(\“\\r\\n@@@\\r\\nfoo\\r\\n@@@\\r\\n\”).strip.should == CodeRay.scan(‘foo’, nil).div(:css => :class).strip\r\n end\r\n \r\n it \“should allow multiple code blocks\” do\r\n textilize(\“@\\nfoo\\n@@@\\n\\n@@@\\nbar\\n@@@\”).strip.should == CodeRay.scan(‘foo’, nil).div(:css => :class).strip + \“\\n\” +CodeRay.scan(‘bar’, nil).div(:css => :class).strip\r\n end\r\nend\r\n@@@