30 Lines Implementation of eRuby
eRuby is a specification to embed Ruby code (expression or statement) in any text file. There are thee implementation of eRuby:
- ERB (pure Ruby, most popular and included in Ruby 1.8)
- eruby (implemented as C extention)
- Erubis (pure Ruby, very fast and extensible)
Using pattern matching, it is easy to implement eRuby. The following is a complete eRuby implementation (named TinyEruby) which contains only 30 lines.
class TinyEruby
def initialize(input=nil)
@src = convert(input) if input
end
attr_reader :src
def convert(input)
src = "_buf = '';" # preamble
input.scan(/(.*?)<%(=)?(.*?)%>/m) do |text, ch, code|
src << " _buf << '#{escape_text(text)}';" unless text.empty?
src << (ch == '=' ? " _buf << (#{code}).to_s" : code) << ';'
end
rest = $' || input
src << " _buf << '#{escape_text(rest)}';" unless rest.empty?
src << "\n_buf.to_s\n" # postamble
return src
end
def escape_text(text)
return text.gsub!(/['\\]/, '\\\\\&') || text
end
def result(_binding=TOPLEVEL_BINDING)
eval @src, _binding
end
end
Usage of TinyEruby is the same as ERB.
input = File.read('example.rhtml')
eruby = TinyEruby.new(input)
print eruby.src
TinyEruby will work very well but it may be slow if large eRuby file is passed. The following improves scan speed of TinyEruby very much.
...
def convert(input)
src = "_buf = '';" # preamble
pos = 0
input.scan(/<%(=)?(.*?)%>/m) do |ch, code|
match = Regexp.last_match
len = match.begin(0) - pos
text = input[pos, len]
pos = match.end(0)
src << " _buf << '#{escape_text(text)}';" unless text.empty?
src << (ch == '=' ? " _buf << (#{code}).to_s" : code) << ';'
end
rest = $' || input
src << " _buf << '#{escape_text(rest)}';" unless rest.empty?
src << "\n_buf.to_s\n" # postamble
return src
end
...
TinyEruby is so small and simple that it is easy to customize for your needs or port into other languages.
TinyEruby is already included in Erubis (lib/erubis/tiny.rb).