All about Erubis (English) - Presentation Transcript
RubyKaigi2009
All about Erubis
And the future of template system
makoto kuwata <kwa@kuwata-lab.com>
http://www.kuwata-lab.com/
copyright(c) 2009 kuwata-lab.com all rights reserved.
1
I have something to say at first...
‣ Thank you for all staff of RubyKaigi!
‣ Thank you for all audience who join this
session!
copyright(c) 2009 kuwata-lab.com all rights reserved.
2
Agenda
‣ Part 1. Features of Erubis
‣ Part 2.Issues about eRuby and solutions
by Erubis
‣ Part 3. Future of template system
copyright(c) 2009 kuwata-lab.com all rights reserved.
3
Part 1. Features of Erubis
copyright(c) 2009 kuwata-lab.com all rights reserved.
4
Introduction to Erubis
‣ Pure Ruby implementation of eRuby
‣ Very fast
• http://jp.rubyist.net/magazine/?0022-FasterThanC
‣ Highly functional
• HTML escape in default
• Changing embedded pattern
• Support PHP, Java, JS, C, Perl, Scheme
• and so on...
copyright(c) 2009 kuwata-lab.com all rights reserved.
5
Basically Usage
Ruby program:
require 'rubygems' # if need
require 'erubis'
str = File.read('template.eruby')
eruby = Erubis::Eruby.new(str)
print eruby.result(binding())
command-line:
$ erubis template.eruby # execute
$ erubis -x template.eruby # convert into Ruby
$ erubis -z template.eruby # syntax check
copyright(c) 2009 kuwata-lab.com all rights reserved.
6
HTML Escape in Default
str =<<END <%= %> ... WITH escaping,
<%= var %> <%== %> ... WITHOUT escaping
<%== var %>
END
eruby = Erubis::Eruby.new(str, :escape=>true)
puts eruby.result(:var=>"<B&B>")
output:
<B&am;> User can choose escape or not
<B&B> escape in default (choosability)
copyright(c) 2009 kuwata-lab.com all rights reserved.
7
Changing Embedded Pattern
‣ ex : use '[% %]' instead of '<% %>'
[% for x in @list %]
<li>[%= x %]</li>
You must escape regexp meta
[% end %]
characters by backslash!
## Ruby
Erubis::Eruby.new(str, :pattern=>'[% %]')
## command-line
$ erubis -p '[% %]' file.eruby
copyright(c) 2009 kuwata-lab.com all rights reserved.
8
Use Hash or Object instead of Binding
example of using Hash example of using Object
hash = { @title = "Example"
:title => "Example", @items = [1, 2, 3]
:items => [1, 2, 3], } erubis =
erubis = Erubis::Eruby.new(str)
Erubis::Eruby.new(str) puts erubis.evaluate(self)
puts erubis.result(hash)
<h1><%= title%></h1> <h1><%= @title%></h1>
<% for x in items %> <% for x in @items %>
<% end %> <% end %>
copyright(c) 2009 kuwata-lab.com all rights reserved.
9
Enhancer
‣ Ruby modules which enhances Erubis features
## do HTML escape <%= %> in default
module EscapeEnhancer
def add_expr(src, code, indicator)
if indicator == '='
src << " _buf<<escapeXml(#{code})"
elsif indicator == '=='
src << " _buf<<(#{code}).to_s;"
end
end It is easy to override Erubis features
because internal of Erubis is splitted into
end many small methods.
copyright(c) 2009 kuwata-lab.com all rights reserved.
10
Enhancer (cont')
### Enhance which prints into stdout
### (you can use print() in statements)
module StdoutEnhancer use _buf=$stdout
def add_preamble(src) instead of _buf=""
src << "_buf = $stdout;"
end
def add_postamble(src)
src << "n""n"
end use "" (empty string)
end instead of _buf.to_s
copyright(c) 2009 kuwata-lab.com all rights reserved.
11
Usage of Enhancer
All you have to do is to include
### Ruby or extend ehnacer modules
class MyEruby < Erubis::Eruby
include Erubis::EscapeEnhancer
include Erubis::PercentLineEnhancer
end
puts MyEruby.new(str).result(:items=>[1,2,3])
Specify names with ','
### command-line
$ erubis -E Escape,Percent file.eruby
copyright(c) 2009 kuwata-lab.com all rights reserved.
12
Standard Enhancers
‣ EscapeEnhancer : escape html in default
‣ PercentLineEnhancer : recognize lines starting with '%' as
embedded statements
‣ InterporationEnhancer : use _buf<<"#{expr}" for speed
‣ DeleteIndentEnhancer : delete HTML indentation
‣ StdoutEnhancer : use _buf=$stdout instead of _buf=""
‣ ... and so on (you can show list of all by erubis -h)
copyright(c) 2009 kuwata-lab.com all rights reserved.
13
Context Data
‣ You can specify data to pass into template
file (context data) in command-line
### command-line
$ erubis -c '{arr: [A, B, C]}' template.eruby # YAML
$ erubis -c '@arr=%w[A B C]' template.eruby # Ruby
<% for x in @arr %> <li>A</li>
<li><%= x %></li> <li>B</li>
<% end %> <li>C</li>
copyright(c) 2009 kuwata-lab.com all rights reserved.
14
Context Data File
‣ Load '*.yaml' or '*.rb' as context data file
$ erubis -f data.yaml template.eruby # YAML
$ erubis -f data.rb template.eruby # Ruby
data.yaml data.rb
title: Example @title = "Example"
items: @items =
- name: Foo [ {"name"=>"Foo"},
- name: Bar {"name"=>"Bar"}, ]
copyright(c) 2009 kuwata-lab.com all rights reserved.
15
Debug Print
‣ <%=== expr %> represents debug print
<%=== @var %> No need to write the
same expression twice
### Ruby code
$stderr.puts("*** debug: @var=#{@var.inspect}")
### Result
*** debug: @var=["A", "B", "C"]
copyright(c) 2009 kuwata-lab.com all rights reserved.
16
Support Other Programming Langs
‣ PHP, Java, JS, C, Perl,Scheme (only for convertion)
<% for (i=0; i<n; i++) { %> (example of C)
<li><%= "%d", i %>
<% } %> same format as printf()
#line 1 "file.ec" (output of erubis -xl c file.ec)
for (i=0; i<n; i++) {
fputs("<li>", stdout); fprintf(stdout, "%d", i);
fputs("n", stdout); }
copyright(c) 2009 kuwata-lab.com all rights reserved.
17
Conslution
‣ Erubis is very functional and extensible
• HTML escape in default
• Changing embedded pattern
• Enhancer
• Context data and file
• Debug print
• Support PHP, Java, JS, C, Perl, and Scheme
copyright(c) 2009 kuwata-lab.com all rights reserved.
18
Part 2. Issues about eRuby
and solutions by Erubis
copyright(c) 2009 kuwata-lab.com all rights reserved.
19
Issue : local variables can be changed
‣ When using binding(), local variables can be
non-local
• Difficult to find if exists
i=0 ### file.erb
str = File.read('file.erb') <% for i in 1..3 %>
ERB.new(str).result(binding) <li><%= i %></li>
p i #=> 3 <% end %>
Changed insidiously!
copyright(c) 2009 kuwata-lab.com all rights reserved.
20
Cause of the issue
‣ binding() passes all local variables into
template file
• It is impossible to pass only variables which you
truly want to pass
• It is hard to recognize what variables are passed
b = Bingind.new This is ideal
b[:title] = "Example" but impossible...
b[:items] = [1, 2, 3]
copyright(c) 2009 kuwata-lab.com all rights reserved.
21
Solution by ERB
‣ Nothing, but the author of ERB introduced a
solution to define custom Struct
• http://d.hatena.ne.jp/m_seki/20080528/1211909590
Foo = Struct.new(:title, :items)
class Foo
def env; binding(); end
end
ctx = Foo.new("Example", [1,2,3])
ERB.new(str).result(ctx.env)
copyright(c) 2009 kuwata-lab.com all rights reserved.
22
Solution by Erubis
‣ Use Hash instead of Binding
It is very clear what
erubis.result(:items=>[1, 2, 3]) data are passed!
def result(b=TOPLEVEL_BINDING)
if b.is_a?(Hash)
s = b.collect{|k,v| "#{k}=b[#{k.inspect}];"}.join
b = binding()
eval s, b Set hash values as local vars
end with creating new Binding
return eval(@src, b)
end
copyright(c) 2009 kuwata-lab.com all rights reserved.
23
Solution by Erubis (cont')
‣ Use Object instead of Binding
@items = [1, 2, 3]; <% for x in @items %>
erubis.evaluate(self) <% end %>
def evaluate(ctx) Convert Hash values into
if ctx.is_a?(Hash) instance variables
hash = ctx; ctx = Object.new
hash.each {|k,v|
ctx.instance_variable_set("@#{k}", v) }
end
return ctx.instance_eval(@src)
end copyright(c) 2009 kuwata-lab.com all rights reserved.
24
Issue : cost of convertion and parsing
ERB
1. 8.6 Erubis::Eruby
ERB
1. 8.7 Erubis::Eruby
ERB
1.9.1 Erubis::Eruby
0 10 20 30
(sec)
Costs of parsing and Execution
convertion are higher Parsing(by eval)
than of execution Convertion(eRuby to Ruby)
copyright(c) 2009 kuwata-lab.com all rights reserved.
25
Solution by ERB
‣ Convertion cost : nothing
‣ Parsing cost : helper to define method
• Usage is much different from normal usage
class Foo
extend ERB::DefMethod
def_erb_method('render', 'template.erb')
end
print Foo.new.render
copyright(c) 2009 kuwata-lab.com all rights reserved.
26
Solution by Erubis
‣ Convertion cost : cache Ruby code into file
• 1st time : save converted Ruby code into *.cache file
• 2nd time : read ruby code from *.cache file
eruby = Erubis::Eruby.load_file("file.eruby")
print eruby.result()
Available even in CGI
copyright(c) 2009 kuwata-lab.com all rights reserved.
27
Solution by Erubis (cont')
‣ Parsing cost : keep ruby code as Proc object
• The same way to use
• Almost the same speed as defining method
instance_eval can take a
def evaluate(ctx) Proc object as argument
@proc ||= eval(@src) instead of string (ruby code)
ctx.instance_eval(@proc)
end
copyright(c) 2009 kuwata-lab.com all rights reserved.
28
Issue: extra line breaks
‣ eRuby outpus extra line breaks
• Big problem for non-HTML text
Extra line break <ul>
<ul>
<% for x in @list %> <li>AAA</li>
<li><%= x %></li>
<% end %> <li>BBB</li>
</ul>
Extra line break
</ul>
copyright(c) 2009 kuwata-lab.com all rights reserved.
29
Solution by ERB
‣ Provides various trim mode
• ">" : removes LF at the end of line
• "<>" : removes LF if "<%" is at the beginning of line
and "%>" is at the end of line
• "-" : removes extra spaces and LF around
"<%-" and "-%>"
• "%" : regard lines starting with "%" as embedded
statements
• "%>", "%<>", "-" : combination of "%" and
">"/"<>"/"-"
ERB.new(str, nil, "%<>")
copyright(c) 2009 kuwata-lab.com all rights reserved.
30
Solution by Erubis
‣ Change operation between embedded
statement and expression
• <% stmt %> : remove spaces around it
• <%= expr %> : do nothing (leave as it is)
<ul> Remove! <ul>
<% for x in @list %> AAA
<%= x %> BBB
<% end %> CCC
</ul> Leave as it is </ul>
copyright(c) 2009 kuwata-lab.com all rights reserved.
31
Comparison of solutions
ERB Erubis
eRuby spec
compatible
× spec) (compatible)
(extends
Spec
simplicity
× opts) (only one rule)
(too much
Easy to
implement
× (very easy)
(complicated)
copyright(c) 2009 kuwata-lab.com all rights reserved.
32
Hint to think
‣ "Extra line breaks" problem has been
recognized since early times
• [ruby-list:18894] extra LF in output of eRuby
‣ Nobody hit on the idea of changing
operations between stmts and exprs
• Everybody looks <% %> and <%= %> as same
• It is important to recoginize two things which
looks to be the same things as different things
copyright(c) 2009 kuwata-lab.com all rights reserved.
33
Issue : Escape HTML in default
‣ <%= expr %> should be HTML escaped in
default!
• But eRuby is not only for HTML but also for all
of text file
• However security is the most important thing
‣ How to do when not to escape?
copyright(c) 2009 kuwata-lab.com all rights reserved.
34
Solution by ERB
‣ Nothing for officially
‣ Unofficial solution
• Define a certain class which represents HTML string
(not to escape) separately from String class
• http://www2a.biglobe.ne.jp/~seki/ruby/erbquote.html
copyright(c) 2009 kuwata-lab.com all rights reserved.
35
Solution by Erubis
‣ Enhance embedded pattern and Erubis class
• Fast, and easy to implement
eruby = Erubis::Eruby.new(str, :escape=>true)
# or eruby = Erubis::EscapedEruby.new(str)
puts eruby.evaluate(ctx)
Hi <%= @name %>! # with escape
Hi <%== @name %>! # without escape
copyright(c) 2009 kuwata-lab.com all rights reserved.
36
Issue : hard to find syntax error
<% unless @items.blank? %>
<table>
<tbody>
<% @items.each do |item| %>
<tr class="item" id="item-<%=item.id%>">
<td class="item-id"><%= item.id %></td>
<td class="item-name">
<% if item.url && !item.url.empty? %>
<a href="<%= item.url %>"><%=item.name%></a>
<% else %>
<span><%=item.name%></span>
<% end %>
</td>
</tr> • HTML and Ruby code are mixed
<% end %> • It is hard to recognize corresponding
</tbody>
</table>
'end' (because 'do' and 'end' can be
<% end %> separated 100 lines for example)
copyright(c) 2009 kuwata-lab.com all rights reserved.
37
Solution by ERB
‣ Nothing but '-x' option
$ erb -x foo.eruby
_erbout = ''; unless @items.blank? ;
_erbout.concat "n"
_erbout.concat "<table>n"
_erbout.concat " <tr class="record">n"
$ erb -x foo.eruby | ruby -wc
Syntax OK
copyright(c) 2009 kuwata-lab.com all rights reserved.
38
Solution by Erubis
‣ Provides a lot of command-line options
• -x : show Ruby script
• -X : suppress to print HTML
• -N : print line numbers
• -U : unify consecutive empty lines into a line
• -C : remove consecutive empty lines (compact)
• -z : check template syntax
copyright(c) 2009 kuwata-lab.com all rights reserved.
39
$ cat foo.eruby
<% unless @items.blank? %>
<table>
<% @items.each_with_index do|x, i| %>
<tr class="record">
<td><%= i +1 %></td>
<td><%=h x %></td>
</tr>
<% end %>
</table>
<% end %>
copyright(c) 2009 kuwata-lab.com all rights reserved.
40
-x : show Ruby script
$ erubis -x foo.eruby
_buf = ''; unless @items.blank?
_buf << '<table>
'; @items.each_with_index do|x, i|
_buf << ' <tr class="record">
<td>'; _buf << ( i +1 ).to_s; _buf << '</td>
<td>'; _buf << (h x ).to_s; _buf << '</td>
</tr>
'; end
_buf << '</table>
'; end
_buf.to_s
copyright(c) 2009 kuwata-lab.com all rights reserved.
41
-X : suppress to print HTML
$ erubis -X foo.eruby
_buf = ''; unless @items.blank?
@items.each_with_index do|x, i|
_buf << ( i +1 ).to_s;
_buf << (h x ).to_s;
end
end
_buf.to_s
copyright(c) 2009 kuwata-lab.com all rights reserved.
42
-N : print line numbers
$ erubis -XN foo.eruby
1: _buf = ''; unless @items.blank?
2:
3: @items.each_with_index do|x, i|
4:
5: _buf << ( i +1 ).to_s;
6: _buf << (h x ).to_s;
7:
8: end
9:
10: end
11: _buf.to_s
copyright(c) 2009 kuwata-lab.com all rights reserved.
43
-U : unifiy consecutive empty
lines into a line
$ erubis -XNU foo.eruby
1: _buf = ''; unless @items.blank?
3: @items.each_with_index do|x, i|
5: _buf << ( i +1 ).to_s;
6: _buf << (h x ).to_s;
8: end
10: end
11: _buf.to_s
copyright(c) 2009 kuwata-lab.com all rights reserved.
44
-C : remove empty lines
(compact)
$ erubis -XNC foo.eruby
1: _buf = ''; unless @items.blank?
3: @items.each_with_index do|x, i|
5: _buf << ( i +1 ).to_s;
6: _buf << (h x ).to_s;
8: end
10: end
11: _buf.to_s
copyright(c) 2009 kuwata-lab.com all rights reserved.
45
Issue : expr can contain statements
embed return value of
helper method by <%= %>
block contains
statements
<%= form_for :user do %>
<div>
<%= text_field :name %>
</div>
<% end %> beyond of
eRuby spec!
copyright(c) 2009 kuwata-lab.com all rights reserved.
46
Cause of Issue
<%= 10.times do %>
Hello <%= expr %> is expected
<% end %> to be completed by itself
Convert
Syntax error!
_buf = "";
_buf << ( 10.times do ).to_s;
_buf << " Hellon";
end
copyright(c) 2009 kuwata-lab.com all rights reserved.
47
Solution by ERB+Rails
‣ Change local variable (_erbout) on caller-
size from callee-side agic!!
b lack m
Append to '_erbout' from
Not use <%= %> internal of form_for()
<% form_for do %> _erbout = ""
Hello form_for do
<% end %> _erbout.concat("Hello")
end
copyright(c) 2009 kuwata-lab.com all rights reserved.
48
Solution by Erubis+Merb
‣ Extend parser of Erubis
Recognize blok in
embedded expr
<%= form_for do %> @_buf << (form_for do;
Hello @_buf << "Hellon"
<% end =%> end);
Introduce end-of- Change _buf into
block notation instance variable
copyright(c) 2009 kuwata-lab.com all rights reserved.
49
Discussion
‣ Extend spec of eRuby
‣ Not use black magic (kool!)
‣ Available only for helper method, in fact
• It is required to manipulate @_buf from internal
of helper method
‣ Difficult to provide general solution
copyright(c) 2009 kuwata-lab.com all rights reserved.
50
Conslusion
‣ A lot of issues around eRuby!
• Extra line breaks
• Local variables are not local
• Difficult to specify context variable
• Large cost for convertion and parsing
• HTML escape in default
• Difficult to find syntax error
• Can't embed return value of method with block
copyright(c) 2009 kuwata-lab.com all rights reserved.
51
Part 3. Future of template
system
copyright(c) 2009 kuwata-lab.com all rights reserved.
52
Template and Programming
‣ Template is also program code
<ul> print "<ul>n"
<% for x in @a %> for x in @a
<li><%=x%></li> Equiv. print "<li>#{x}</li>n"
<% end %> end
</ul> print "</u>n"
Possible to apply programming techniques
or concepts to template system
copyright(c) 2009 kuwata-lab.com all rights reserved.
53
Template and Method
Template is a kind of
method definition
<ul> s = File.read('foo.eruby')
<li><%=x%></li> e = Erubis::Eruby.new(s)
</ul> puts e.evaluate(:x=>1)
Context data is actual
Rendering template is a argument for method
kind of method invokation
copyright(c) 2009 kuwata-lab.com all rights reserved.
54
Template and formal argument
‣ Formal arguments may be necessary for
template
<%#ARGS: items, name='guest' %>
Hello <%= name %>!
<% for x in items %>
<li><%=x%></li> • Clear context variables
<% end %> • Available default value
copyright(c) 2009 kuwata-lab.com all rights reserved.
55
Template and modularity
‣ Also HTML should be Small & Many, not
Single & Large
• Same as principle of method definition
<html> <html> Be benefit for
<body> designer!
<body> </body>
<h1><%=@title%></h1> </html>
<ul id="menulist">
split
<% for x in @items %> <h1><%=@title%></h1>
<li><%=x%></li> <ul id="menulist">
</ul>
<% end %>
</ul> <% for x in @items %>
</body> <li><%= x %></li>
</html> <% end %>
copyright(c) 2009 kuwata-lab.com all rights reserved.
56
Template and Object-Oriented
‣ Template inheritance in Django
Parent template
....
Available to overwrite
{% block pagetitle %}
or add contents
<h1>{{title}}</h1> before/after
{% endblock %} (method override)
....
copyright(c) 2009 kuwata-lab.com all rights reserved.
57
Template and Aspect-Oriented
‣ Weave some code into other program
• Similar to layer of Photoshop
<table>
"for x in @a" •Enable to split HTML
<tr> and presentation
<td> "print x" logics
•Available to insert a
</tr> logic into several
"end" points (DRY)
</table>
copyright(c) 2009 kuwata-lab.com all rights reserved.
58
Template and Data Type
‣ End is coming to escape HTML in view layer
• forget to escape, helper method argument, ...
‣ HTML should be different data type from String
(http://www.oiwa.jp/~yutaka/tdiary/20051229.html)
• No need to take care to escape or not
• Prior art : str and unicode in Python
• "HTML + String" should be String? or HTML?
• Other escaping also should be considered (ex. SQL)
copyright(c) 2009 kuwata-lab.com all rights reserved.
59
Conslusion
‣ Template is also programming code
‣ Available to apply programming techniques
and concepts into template system
• Formal argument, Inheritance, AOP, and so on
‣ Template system is still on developing stage
copyright(c) 2009 kuwata-lab.com all rights reserved.
60
Bibliography
‣ Introduction to Template System (in Japanese)
• http://jp.rubyist.net/magazine/?0024-TemplateSystem
• http://jp.rubyist.net/magazine/?0024-TemplateSystem2
‣ Erubis
• http://www.kuwata-lab.com/erubis/
‣ Benchmarks of many template systems
• http://www.kuwata-lab.com/tenjin/
copyright(c) 2009 kuwata-lab.com all rights reserved.
61
one more thing
copyright(c) 2009 kuwata-lab.com all rights reserved.
62
Tenjin - template engine replacing eRuby
‣ Both ERB and Erubis are out of date
• They are merely text-processor
• Less features as template engine
‣ Tenjin : replacer of ERB/Erubis
• Designed and implemented as template engine from the
beginning
• Provides a lot of features required for template engines
- layout template, partial template, and so on
• http://www.kuwata-lab.com/tenjin/
copyright(c) 2009 kuwata-lab.com all rights reserved.
63
thank you
copyright(c) 2009 kuwata-lab.com all rights reserved.
64
(This is presentation slide for RubyKaigi 2009)
Er more
(This is presentation slide for RubyKaigi 2009)
Erubis is very fast and extensible implementation of eRuby. In this slides, I show you features of Erubis, and issues related to eRuby and solution by Erubis. Also I show you some ideas about the future of template system. less
0 comments
Post a comment