rbTenjin FAQ

last update: $Date: 2008-02-04 22:21:53 +0900 (Mon, 04 Feb 2008) $

Release: 0.6.2

Table of contents:

Basic

I got an SyntaxError exception.

Command-line option '-z' checks syntax of template file. You should check template by it.

File 'ex1.rbhtml':
<ul>
<?rb (0..10).each { |i| ?>
  <li>#{i}</li>
<?rb end ?>
</ul>
Result:
$ rbtenjin -wz ex1.rbhtml
ex1.rbhtml:4: syntax error, unexpected kEND, expecting '}'
ex1.rbhtml:5: syntax error, unexpected $end, expecting '}'

'#{@_content}' includes extra newline at end. Can I delete it?

Yes. You can use '<?rb echo(@_content) ?>' or '<?rb _buf << @_content ?>' instead of '#{@_conent}'.

File 'ex2-layout.rbhtml':
<!-- -->
#{@_content}
<!-- -->

<!-- -->
<?rb echo(@_content) ?>
<!-- -->

<!-- -->
<?rb _buf << @_content ?>
<!-- -->
File 'ex2-content.rbhtml':
foo
bar
baz
Result:
$ rbtenjin --layout=ex2-layout.rbhtml ex2-content.rbhtml
<!-- -->
foo
bar
baz

<!-- -->

<!-- -->
foo
bar
baz
<!-- -->

<!-- -->
foo
bar
baz
<!-- -->

Can I change 'escape()' function name?

Yes. You can change them by setting :escapefunc option for Tenjin::Template.new() or Tenjin::Engine.new().

File 'ex3.rb':
require 'tenjin'
engine = Tenjin::Engine.new(:escapefunc=>'CGI.escapeHTML')
template = engine.get_template('ex3.rbhtml')
print template.script
File 'ex3.rbhtml':
Hello ${@name}!
Result:
$ ruby ex3.rb
 _buf << %Q`Hello #{CGI.escapeHTML((@name).to_s)}!\n`

Command-line option '--escapefunc=name' is equivarent to the above.

Result:
$ rbtenjin -sb --escapefunc=CGI.escapeHTML ex3.rbhtml
 _buf << %Q`Hello #{CGI.escapeHTML((@name).to_s)}!\n`

Can I change '_buf' variable name?

No. Variable name '_buf' should not and will never be changed.

Template

Is it possible to specify variables passed to template?

Yes. You can specify template arguments by '<?rb #@ARGS arg1, arg2, arg3 ?>'.

File 'ex5-layout.rbhtml'
<?xml version="1.0 ?>
<?rb #@ARGS x, y ?>
<p>
  x = #{x}
  y = #{y}
  z = #{z}
</p>

Template arguments line is converted into local variable assignment statements.

Source code
$ rbtenjin -s ex5.rbhtml
_buf = '';  _buf << %Q`<?xml version="1.0 ?>\n`
 x = @x; y = @y;
 _buf << %Q`<p>
  x = #{x}
  y = #{y}
  z = #{z}
</p>\n`
_buf.to_s

Undeclared arguments are not available even when they are passed via context object.

Result:
$ rbtenjin -c 'x=10; y=20; z=30' ex5.rbhtml
ex5.rbhtml:6:in `_render': undefined local variable or method `z' for #<Tenjin::Context:0x35a9e4> (NameError)

Is there any way to use eRuby template?

Yes. It is able to use eRuby template files by Tenjin::ErubisTemplate class.

It is required to install Erubis to use Tenjin::ErubisTemplate class.

File 'ex6-layout.rhtml'
<html>
 <body>
  <h1><%=h @title %></h1>
<%= @_content %>
 </body>
</html>
File 'ex6-content.rhtml'
  <% @title = 'eRuby template example' %>
  <ul>
  <% for item in @items %>
   <li><%=h item %></li>
  <% end %>
  </ul>
File 'ex6-main.rb':
require 'erubis'
require 'tenjin'

include Erubis::XmlHelper

context = { :items => ['<AAA>', 'B&B', '"CCC"'] }
engine = Tenjin::Engine.new(:layout=>'ex6-layout.rhtml',
                           :templateclass=>Tenjin::ErubisTemplate)
output = engine.render('ex6-content.rhtml', context)
print output
Result:
$ ruby ex6.rb
<html>
 <body>
  <h1>eRuby template example</h1>
  <ul>
   <li>&lt;AAA&gt;</li>
   <li>B&amp;B</li>
   <li>&quot;CCC&quot;</li>
  </ul>

 </body>
</html>

Is it able to change embedded expression pattern?

Yes, you can create subclass of Template class and override embedded expression pattern.

ex7-expr-pattern.rbhtml:
<p>HTML escaped: ${@value}</p>
<p>not escaped:  #{@value}</p>
<p>not escaped:  <%= @value %></p>
ex7-expr-pattern.rb:
require 'tenjin'

class MyTemplate < Tenjin::Template

  ## return pattern object for embedded expressions
  def expr_pattern()
    return /([$#])\{(.*?)\}|<%=(.*?)%>/m
  end

  ## if you don't use '#{...}', you must escape '#' in addition to '\\' and '`'
  #def escape_str(str)
  #  return str.gsub(/[\\`\#]/, '\\\\\&')
  #end

  ## return expression string and flag whether escape or not from matched object
  def get_expr_and_escapeflag(match)
    if match[1]
      expr = match[2]
      escapeflag = match[1] == '$'
    else
      expr = match[3].strip()
      escapeflag = false
    end
    return expr, escapeflag
  end

end

if __FILE__ == $0
  context = { :value => 'AAA&BBB' }
  engine = Tenjin::Engine.new(:templateclass=>MyTemplate)
  output = engine.render('ex7-expr-pattern.rbhtml', context)
  puts output
end
Result:
$ ruby ex7-expr-pattern.rb
<p>HTML escaped: AAA&amp;BBB</p>
<p>not escaped:  AAA&BBB</p>
<p>not escaped:  AAA&BBB</p>

Does rbTenjin support M17N?

No, but it is easy to support M17N. The point is:

The following is an example to generate M17N pages from a template file.

ex8-m18n.rbhtml:
<div>
<?RB ## '_()' represents translator method ?>
 <p>${{_('Hello')}} ${@username}!</p>
</div>
ex8-m18n.rb:
require 'tenjin'

##
## message catalog to translate message
##
MESSAGE_CATALOG = {
  'en' => { 'Hello'   => 'Hello',
            'Good bye'=> 'Good bye', },
  'fr' => { 'Hello'   => 'Bonjour',
            'Good bye'=> 'Au revoir', },
}


##
## add translation method to Context class
##
class Tenjin::Context

  def _(message_key)
    message_dict = MESSAGE_CATALOG[@_lang]
    return message_key unless message_dict
    return message_dict[message_key] || message_key
  end

end


##
## engine class which supports M17N
##
class M17NEngine < Tenjin::Engine

  attr_accessor :lang

  ## constructor takes ':lang' options
  def initialize(properties={})
    super(properties)
    @lang = properties[:lang] || 'en'   # set language
  end

  ## change cache filename to 'file.html.lang.cache'
  def cachename(filename)
    return "#{filename}.#{@lang}.cache"
  end

  ## set language to context object
  def hook_context(context)
    context = super(context)
    context['_lang'] = @lang
    return context
  end

end


##
## test program
##
if $0 == __FILE__

  template_name = 'ex8-m18n.rbhtml'
  context = { :username => 'World' }
  
  ## engine for english
  engine = M17NEngine.new(:preprocess=>true)
  output = engine.render(template_name, context)   # same template
  puts "--- lang: %s ---" %  engine.lang
  puts output
  puts
  
  ## engine for French
  engine = M17NEngine.new(:preprocess=>true, :lang=>'fr')
  output = engine.render(template_name, context)   # same template
  puts "--- lang: %s ---" %  engine.lang
  puts output

end
Result:
$ ruby ex8-m18n.rb
--- lang: en ---
<div>
 <p>Hello World!</p>
</div>

--- lang: fr ---
<div>
 <p>Bonjour World!</p>
</div>

rbTenjin doesn't provide M17N feature directly because requirements for M17N are different for each applications or frameworks. Some applications or frameworks adapt GetText library and others use its original M17N library. What rbTenjin should do is not to provide M17N feature but to show an example to support M17N.

Layout Template

Can I change layout template name in a template file?

Yes. If you set @_layout, its value is regarded as layout template name.

See the next section for details.

Can I nest layout templates for any depth?

Yes. If you set @_layout, you can nest layout templates in any depth.

The following example shows that:

File 'ex9-content.rbhtml':
<?rb @title = 'Changing Layout Template Test' ?>
<?rb ## specify layout template name ?>
<?rb @_layout = 'ex9-mylayout.rbhtml' ?>
foo
bar
baz
File 'ex9-mylayout.rbhtml':
<?rb ## use default layout template name ?>
<?rb @_layout = true ?>
<div id="content">
<?rb _buf << @_content ?>
</div>
File 'ex9-baselayout.rbhtml':
<html>
  <body>
<?rb if @title ?>
    <h1>${@title}</h1>
<?rb end ?>
<?rb _buf << @_content ?>
  </body>
</html>
Result:
$ rbtenjin --layout=ex9-baselayout.rbhtml ex9-content.rbhtml
<html>
  <body>
    <h1>Changing Layout Template Test</h1>
<div id="content">
foo
bar
baz
</div>
  </body>
</html>

Can I disable default layout template for a certain template?

Yes. If you set false to @_layout, default layout template will not be applied.

Is Django-like "Template Inheritance" supported?

No, but you can emulate it partially by combination of template capturing and '@_layout'.

File 'ex10-baselayout.rbhtml':
<html>
 <body>

<?rb ## if variable '@header_part' is defined then print it, ?>
<?rb ## else print default header part. ?>
  <div id="header">
<?rb unless captured_as(:header_part) ?>
   <img src="img/logo.png" alt="logo" ?>
<?rb end ?>
  </div>

<?rb ## main content part ?>
  <div id="content">
<?rb _buf << @content_part ?>
  </div>

<?rb ## if variable '@footer_part' is defined then print it, ?>
<?rb ## else print default footer part. ?>
  <div id="footer">
<?rb unless captured_as(:footer_part) ?>
   <hr />
   <em>webmaster@example.com</em>
<?rb end ?>
  </div>
  
 </body>
</html>
File 'ex10-customlayout.rbhtml':
<?rb ## '@_layout' variable is equivarent to '{% extends "foobar.html" %}' ?>
<?rb ## in Django template engine. ?>
<?rb @_layout = 'ex10-baselayout.rbhtml' ?>

<?rb ## you can override header or footer by capturing. ?>
<?rb start_capture(:footer_part) ?>
<address style="text-align:right">
  copyright&copy; 2007 kuwata-lab all rights reserved<br />
  <a href="webmaster&#64;kuwata-lab.com">webmaster&#64;kuwata-lab.com</a>
</address>
<?rb stop_capture() ?>
File 'ex10-content.rbhtml':
<?rb ## '@_layout' variable is equivarent to '{% extends "foobar.html" %}' ?>
<?rb ## in Django template engine. ?>
<?rb @_layout = 'ex10-customlayout.rbhtml' ?>

<?rb ## main content part ?>
<?rb start_capture(:content_part) ?>
<ul>
<?rb for item in @items ?>
  <li>${item}</li>
<?rb end ?>
</ul>
<?rb stop_capture() ?>

'captured_as()' is a pre-defined helper function. For example,

<?rb unless captured_as(:header_part): ?>
   <img src="img/logo.png" alt="logo" ?>
<?rb end ?>

is equivarent to the following.

<?rb if @header_part: ?>
<?rb     _buf << @header_part ?>
<?rb else ?>
   <img src="img/logo.png" alt="logo" ?>
<?rb end ?>

The following is the result. It shows that footer part in baselayout is overrided by other templates.

Result:
$ rbtenjin -c "@items=['AAA', 'BBB', 'CCC']" ex10-content.rbhtml
<html>
 <body>

  <div id="header">
   <img src="img/logo.png" alt="logo" ?>
  </div>

  <div id="content">
<ul>
  <li>AAA</li>
  <li>BBB</li>
  <li>CCC</li>
</ul>
  </div>

  <div id="footer">
<address style="text-align:right">
  copyright&copy; 2007 kuwata-lab all rights reserved<br />
  <a href="webmaster&#64;kuwata-lab.com">webmaster&#64;kuwata-lab.com</a>
</address>
  </div>
  
 </body>
</html>

Performance

How fast is rbTenjin compared with other solutions?

rbTenjin contains benchmark script. This shows that rbTenjin works much faster than other solutions.

MacOS X 10.4 Tiger, Intel CoreDuo 1.83GHz, Memory 2GB
$ cd rbtenjin-X.X.X/benchmark
$ ruby -v
ruby 1.8.6 (2007-03-13 patchlevel 0) [i686-darwin8.9.1]
$ ruby bench.rb -n 10000
                     user     system      total        real
eruby           12.190000   0.260000  12.450000 ( 12.464225)
eruby-cache     11.320000   0.410000  11.730000 ( 11.756440)
erb             36.190000   0.370000  36.560000 ( 36.694964)
erb-reuse       10.720000   0.020000  10.740000 ( 10.770338)
erubis          10.130000   0.310000  10.440000 ( 10.476733)
erubis-reuse     6.380000   0.010000   6.390000 (  6.405158)
tenjin           6.600000   0.410000   7.010000 (  7.021953)
tenjin-nocache   8.180000   0.360000   8.540000 (  8.562649)
tenjin-reuse     4.370000   0.180000   4.550000 (  4.549724)

In addition, module size of rbTenjin is small, and it is very light-weight to import it. This is important for CGI program. Other solutions may be very heavy to import the module and suitable only for apache module or FastCGI.

Why rbTenjin is so fast?

Because it doesn't use template engine original language.

Other template engines, such as Template-Toolkit(perl), Django(python), or Smarty(php), has their original languages. This is not good idea for script language because:

In addition, rbTenjin is faster than Jakarta Velocity which is a very popular template engine in Java. (It means that dynamic Java is slower than script languages.)

Template engine should use their host language directly unless there are some kind of reasons.

Is there any way to get more speed?