The other day I switched this blog over from Typo to Mephisto, a lightweight blog/cms system written by Rick Olson. Over all, I’m glad I took the leap, but one thing I missed was a slick syntax highlighting system. I browsed through the source to make sure that there wasn’t some hidden feature supporting it that I had missed. Based on good reviews I had heard, I looked to CodeRay to get code syntax coloring working within Mephisto.
Googling with “rails+coderay” turned up this snippet from over at Rails Weenie. I tried it out by first installing CodeRay as a gem. After noticing some strange behavior I asked for help in #caboose. Rick pointed out that the Subversion version of CodeRay was miles ahead of the gem and that it would likely clear up the problems I was having. I ended up doing something like this to get the latest CodeRay into my Mephisto project:
$ cd ./vendor
$ svn export svn://rubyforge.org//var/svn/coderay/trunk/coderay/trunk/lib/
$ mv lib/* .
$ rmdir lib
(I chose not to set up externals for CodeRay, but you could go that route if you always want their edge version.)
I ended up getting Rick’s CodeRay snippet working in Mephisto as a Liquid filter, and then turned the whole thing into a Plugin called mephisto_code_colorizer. Here’s the meaningful files from the plugin, in pretty colors (paths relative to your Rails project root):
First, the init.rb, to load everything up:
./init.rb:require 'coderay'
require 'snippet_parser'
require 'colorizer'
Liquid::Template.register_filter(Mephisto::Liquid::Colorizer)
Then, Rick’s SnippetParser class definition:
./bin/snippet_parser.rb:<samp>
class SnippetParser < String
class << self
# SnippetParser.parse text do |tag, code, i|
# # return processed code
# end
def parse(text, &block)
build_snippets text, &block
end
private
def method_missing(method, *args, &block)
new(args.first).send(method, *args[1..-1], &block)
end
end
# returns snippets in an array
def snippets
build_snippets if @snippets.nil?
@snippets
end
# wraps snippets in <pre><code>
def pre_format
build_snippets do |tag, code, i|
%(<pre><code>#{code}</code></pre>)
end
end
protected
def build_snippets(&block)
@snippets = []
contents = []
tag = nil
returning [] do |output|
tokenizer = HTML::Tokenizer.new(self.strip)
while token = tokenizer.next
node = HTML::Node.parse(nil, 0, 0, token, false)
if node.tag? && node.name == 'samp'
if contents.blank? # open tag
tag = node.dup
else # closing tag
output << close_snippet(tag, contents.join, &block)
tag = nil
end
contents.clear
else # inside a code tag
(tag.nil? ? output : contents) << node.to_s
end
end
# get any unfinished code blocks
output << close_snippet(nil, contents.join, &block) unless contents.empty?
end.join
end
def close_snippet(tag, contents, &block)
@snippets << contents
block ? block.call(tag, contents, @snippets.length) : %(<samp>#{contents}</samp>)
end
end
</samp>
Finally, I created a Liquid filter named “syntax” and baked it in with the other Liquid filters, with:
./bin/colorizer.rb:<samp>
module Mephisto
module Liquid
module Colorizer
include ActionView::Helpers::TagHelper
require 'cgi'
def syntax(html)
# tag is the HTML::Node instance
# code is the text inside the code tags
# i is the counter for this current snippet.
SnippetParser.parse CGI.unescapeHTML(html) do |tag, code, i|
code.gsub! /^\s+\n/, '' # gets rid of an extra linebreak at the top.
%(<div class='samp'>) +
CodeRay.scan(code,
(tag.attributes['lang'].to_sym rescue nil) ||
:ruby).div(:line_numbers => :list, :css => :style) +
"</div>"
end
end
end
end
end
</samp>
The filter colorizes code marked up in samp tags. To filter the body of an article, use something like the following in your liquid templates:
./themes/site-1/templates/home.liquid:
...
{{ article.body | syntax }}
...
Wait! Almost forgot the css styles. Of course you’ll need them to have full control over all those nice colors. Check out my css file from this site for the colors I’m using.
I tossed this solution together pretty quickly because I have a lot of other things that I should be doing (like writing a book), but I would love to hear some feedback about how I might make this better. Please leave suggestions in the comments.
Thank you,
Rob





Great job! I’d like to package this up with Kevin’s coderay macro into a mephisto plugin.