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).

Leave a reply