Erbal: Very small, very fast Ragel/C based ERB parser

Intro

Erbal is a lightweight ERB parser that uses the Ragel State Machine Compiler (www.complang.org/ragel/). It’s written in C, it’s very fast. Erbal also produces faster Ruby code than Ruby’s ERB implementation to give you a evaluation-time performance benefit.

Using Erbal

>> require 'erbal'
>> src = Erbal.new("<% a=1 -%>a is: <%= a -%>").parse
=> "@output_buffer = ''; a=1 ;\n@output_buffer.concat(%Q`a is: \#{ a }`);@output_buffer"
>> eval(src)
=> "a is: 1"

Erbal.new takes an optional 2nd argument which is a hash of options, available options are:

  • :buffer - The name of the output buffer. Default is ‘@output_buffer’.

  • :buffer_initial_value - The initial value of the output buffer. Default is a blank string.

  • :unsafe_concat_method - The method to call on the buffer when concatenating a string which DOES need escaping, i.e <%= some_method %> will result in the use of unsafe_concat_method. Default is ‘concat’.

  • :safe_concat_method - The method to call on the buffer when concatenating a string which doesn’t need escaping, i.e text outside of ERB tags (your raw HTML) uses the safe concatenation method. Default is ‘concat’.

  • :safe_concat_keyword - By default the ‘<%=’ tag uses the unsafe_concat_method, with this option you can specify a keyword to signify that the safe_concat_method should be used instead. For example if safe_concat_method is set to ‘raw’ then <%= raw some_method %> will result in a concatenation using the safe_concat_method. Be mindful that even thought the ‘raw’ keyword looks like a method call, it isn’t. Erbal expects that the key is followed by one or more spaces, so a tag like ‘<%= raw(some_method) %>’ will not be recognised by Erbal. Whitespace before the keyword is optional, so ‘<%=raw’ is valid. The keyword can either be one or more a-z characters, or a single of the following: ! @ $ * = ^ & +. For example, given :safe_concat_keyword => ‘!’ then the following is valid: ‘<%=! some_method %>’. Default is blank, i.e no keyword is specified.

NOTE: Erbal itself does NOT perform escaping, it is the responsibility of your unsafe_concat_method to escape the string passed to it.

Rails 2.3

Create the file ‘config/initializers/erbal.rb’ containing:

require 'erbal/rails'

Rails 2.3 + rails_xss (github.com/rails/rails_xss)

If you’re using the rails_xss plugin, use this in your initialiser instead:

require 'erbal'

class ErbalTemplateHandler < ActionView::TemplateHandler
  include ActionView::TemplateHandlers::Compilable
  def compile(template)
    ::Erbal.new("<% __in_erb_template=true %>#{template.source}",
      {:buffer => '@output_buffer',
       :buffer_initial_value => 'ActiveSupport::SafeBuffer.new',
       :safe_concat_method => 'safe_concat',
       :unsafe_concat_method => 'concat',
       :safe_concat_keyword => 'raw'}
    ).parse
  end
end

ActionView::Template.register_template_handler :erb, ErbalTemplateHandler

Rails 3

I’ve not looked into yet.. patches are welcome! ;)

Benchmarks

These benchmarks were run on a Mac OS X 10.6.4, 2.66 Ghx Intel Core i5 with 8 GB of 1067 MHz DDR3 RAM.

Ruby: 1.8.7 (2010-04-19 patchlevel 253) [i686-darwin10.4.0], MBARI 0x6770, Ruby Enterprise Edition 2010.02

Erubis: 2.6.6

Erbal: 1.2

Parsing Benchmark

=> Erb
0.850 0.852 0.864 0.860 0.846 0.845
=> Average: 0.853

=> Erubis (using FastEruby engine)
0.438 0.442 0.475 0.444 0.442 0.442
=> Average: 0.447

=> Erubis (using default Eruby engine)
0.446 0.422 0.443 0.443 0.422 0.443
=> Average: 0.437

=> Erbal
0.042 0.068 0.067 0.040 0.068 0.041
=> Average: 0.054

eval() Benchmark

=> Erb
0.207 0.179 0.179 0.191 0.179 0.179
=> Average: 0.186

=> Erubis (using FastEruby engine)
0.125 0.127 0.128 0.127 0.127 0.141
=> Average: 0.129

=> Erubis (using default Eruby engine)
0.165 0.176 0.176 0.176 0.165 0.176
=> Average: 0.172

=> Erbal
0.128 0.117 0.129 0.116 0.129 0.129
=> Average: 0.124

Contributing

  • Fork the project.

  • Make your feature addition or bug fix.

  • Add tests for it, specs live in the specs/ directory.

  • Commit and send me a pull request.

Credits

Many thanks to Adrian Thurston for writing the Ragel State Machine Compiler (www.complang.org/ragel/)!

Copyrigh

Copyright © 2010-2011 Envato & Ian Leitch. See LICENSE for details.