All about Erubis (English)
-
Upload
kwatch -
Category
Technology
-
view
5.167 -
download
2
description
Transcript of All about Erubis (English)
copyright(c) 2009 kuwata-lab.com all rights reserved.
All about Erubis
makoto kuwata <[email protected]>http://www.kuwata-lab.com/
RubyKaigi2009
And the future of template system
1
copyright(c) 2009 kuwata-lab.com all rights reserved.
I have something to say at first...
‣Thank you for all staff of RubyKaigi!
‣Thank you for all audience who join this session!
2
copyright(c) 2009 kuwata-lab.com all rights reserved.
Agenda
‣Part 1. Features of Erubis
‣Part 2. Issues about eRuby and solutions by Erubis
‣Part 3. Future of template system
3
copyright(c) 2009 kuwata-lab.com all rights reserved.
Part 1. Features of Erubis
4
copyright(c) 2009 kuwata-lab.com all rights reserved.
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...
5
copyright(c) 2009 kuwata-lab.com all rights reserved.
Basically Usage
require 'rubygems'require 'erubis'str = File.read('template.eruby')eruby = Erubis::Eruby.new(str)print eruby.result(binding())
Ruby program:
$ erubis template.eruby$ erubis -x template.eruby $ erubis -z template.eruby
command-line:
# syntax check# convert into Ruby
# execute
# if need
6
copyright(c) 2009 kuwata-lab.com all rights reserved.
HTML Escape in Default
str =<<END<%= var %><%== var %>ENDeruby = Erubis::Eruby.new(str, :escape=>true)puts eruby.result(:var=>"<B&B>")
<B&am;><B&B>
<%= %> ... WITH escaping,<%== %> ... WITHOUT escaping
output:User can choose escape or not escape in default (choosability)
7
copyright(c) 2009 kuwata-lab.com all rights reserved.
Changing Embedded Pattern
‣ ex : use '[% %]' instead of '<% %>'
[% for x in @list %] <li>[%= x %]</li>[% end %]
## RubyErubis::Eruby.new(str, :pattern=>'\[% %\]')## command-line$ erubis -p '\[% %\]' file.eruby
You must escape regexp meta characters by backslash!
8
copyright(c) 2009 kuwata-lab.com all rights reserved.
Use Hash or Object instead of Binding
hash = { :title => "Example", :items => [1, 2, 3], }erubis = Erubis::Eruby.new(str)puts erubis.result(hash)
@title = "Example"@items = [1, 2, 3]erubis = Erubis::Eruby.new(str)puts erubis.evaluate(self)
example of using Hash example of using Object
<h1><%= title%></h1><% for x in items %><% end %>
<h1><%= @title%></h1><% for x in @items %><% end %>
9
copyright(c) 2009 kuwata-lab.com all rights reserved.
Enhancer
‣Ruby modules which enhances Erubis features
## do HTML escape <%= %> in defaultmodule EscapeEnhancer def add_expr(src, code, indicator) if indicator == '=' src << " _buf<<escapeXml(#{code})" elsif indicator == '==' src << " _buf<<(#{code}).to_s;" end endend
It is easy to override Erubis features because internal of Erubis is splitted into many small methods.
10
copyright(c) 2009 kuwata-lab.com all rights reserved.
Enhancer (cont')
### Enhance which prints into stdout### (you can use print() in statements)module StdoutEnhancer def add_preamble(src) src << "_buf = $stdout;" end def add_postamble(src) src << "\n\"\"\n" endend
use _buf=$stdout instead of _buf=""
use "" (empty string) instead of _buf.to_s
11
copyright(c) 2009 kuwata-lab.com all rights reserved.
Usage of Enhancer
### Rubyclass MyEruby < Erubis::Eruby include Erubis::EscapeEnhancer include Erubis::PercentLineEnhancerendputs MyEruby.new(str).result(:items=>[1,2,3])
### command-line$ erubis -E Escape,Percent file.eruby
All you have to do is to include or extend ehnacer modules
Specify names with ','
12
copyright(c) 2009 kuwata-lab.com all rights reserved.
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)
13
copyright(c) 2009 kuwata-lab.com all rights reserved.
Context Data
‣You can specify data to pass into template file (context data) in command-line
<% for x in @arr %><li><%= x %></li><% end %>
### command-line$ erubis -c '{arr: [A, B, C]}' template.eruby # YAML$ erubis -c '@arr=%w[A B C]' template.eruby # Ruby
<li>A</li><li>B</li><li>C</li>
14
copyright(c) 2009 kuwata-lab.com all rights reserved.
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
title: Exampleitems: - name: Foo - name: Bar
@title = "Example"@items = [ {"name"=>"Foo"}, {"name"=>"Bar"}, ]
data.yaml data.rb
15
copyright(c) 2009 kuwata-lab.com all rights reserved.
Debug Print
‣<%=== expr %> represents debug print
<%=== @var %>
### Ruby code$stderr.puts("*** debug: @var=#{@var.inspect}")
### Result*** debug: @var=["A", "B", "C"]
No need to write the same expression twice
16
copyright(c) 2009 kuwata-lab.com all rights reserved.
Support Other Programming Langs
‣PHP, Java, JS, C, Perl,Scheme (only for convertion)
<% for (i=0; i<n; i++) { %><li><%= "%d", i %><% } %>
#line 1 "file.ec" for (i=0; i<n; i++) { fputs("<li>", stdout); fprintf(stdout, "%d", i); fputs("\n", stdout); }
(output of erubis -xl c file.ec)
(example of C)
same format as printf()
17
copyright(c) 2009 kuwata-lab.com all rights reserved.
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
18
copyright(c) 2009 kuwata-lab.com all rights reserved.
Part 2. Issues about eRuby and solutions by Erubis
19
copyright(c) 2009 kuwata-lab.com all rights reserved.
Issue : local variables can be changed
‣When using binding(), local variables can be non-local
• Difficult to find if exists
i = 0str = File.read('file.erb')ERB.new(str).result(binding)p i #=> 3
### file.erb<% for i in 1..3 %><li><%= i %></li><% end %>
Changed insidiously!
20
copyright(c) 2009 kuwata-lab.com all rights reserved.
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.newb[:title] = "Example"b[:items] = [1, 2, 3]
This is ideal but impossible...
21
copyright(c) 2009 kuwata-lab.com all rights reserved.
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(); endendctx = Foo.new("Example", [1,2,3])ERB.new(str).result(ctx.env)
22
copyright(c) 2009 kuwata-lab.com all rights reserved.
Solution by Erubis
‣Use Hash instead of Binding
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 end return eval(@src, b)end
Set hash values as local vars with creating new Binding
erubis.result(:items=>[1, 2, 3])It is very clear what
data are passed!
23
copyright(c) 2009 kuwata-lab.com all rights reserved.
Solution by Erubis (cont')
‣Use Object instead of Binding
def evaluate(ctx) if ctx.is_a?(Hash) hash = ctx; ctx = Object.new hash.each {|k,v| ctx.instance_variable_set("@#{k}", v) } end return ctx.instance_eval(@src)end
@items = [1, 2, 3]; erubis.evaluate(self)
<% for x in @items %><% end %>
Convert Hash values into instance variables
24
copyright(c) 2009 kuwata-lab.com all rights reserved.
Issue : cost of convertion and parsing
ERBErubis::Eruby
ERBErubis::Eruby
ERBErubis::Eruby
0 10 20 30 40
ExecutionParsing(by eval)Convertion(eRuby to Ruby)
1.8.6
1.8.7
1.9.1
(sec)
Costs of parsing and convertion are higher than of execution
25
copyright(c) 2009 kuwata-lab.com all rights reserved.
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')endprint Foo.new.render
26
copyright(c) 2009 kuwata-lab.com all rights reserved.
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
27
copyright(c) 2009 kuwata-lab.com all rights reserved.
Solution by Erubis (cont')
‣Parsing cost : keep ruby code as Proc object
• The same way to use
• Almost the same speed as defining method
def evaluate(ctx) @proc ||= eval(@src) ctx.instance_eval(@proc)end
instance_eval can take a Proc object as argument instead of string (ruby code)
28
copyright(c) 2009 kuwata-lab.com all rights reserved.
Issue: extra line breaks
‣ eRuby outpus extra line breaks
• Big problem for non-HTML text
<ul><% for x in @list %> <li><%= x %></li><% end %></ul>
<ul>
<li>AAA</li>
<li>BBB</li>
</ul>
Extra line break
Extra line break
29
copyright(c) 2009 kuwata-lab.com all rights reserved.
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, "%<>")
30
copyright(c) 2009 kuwata-lab.com all rights reserved.
Solution by Erubis
‣Change operation between embedded statement and expression
• <% stmt %> : remove spaces around it
• <%= expr %> : do nothing (leave as it is)
<ul> AAA BBB CCC</ul>
<ul><% for x in @list %> <%= x %><% end %></ul> Leave as it is
Remove!
31
copyright(c) 2009 kuwata-lab.com all rights reserved.
Comparison of solutions
ERB Erubis
eRuby spec compatible
×(extends spec)
◎(compatible)
Spec simplicity
×(too much opts)
◎(only one rule)
Easy to implement
×(complicated)
◎(very easy)
32
copyright(c) 2009 kuwata-lab.com all rights reserved.
‣ "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
Hint to think
33
copyright(c) 2009 kuwata-lab.com all rights reserved.
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?
34
copyright(c) 2009 kuwata-lab.com all rights reserved.
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
35
copyright(c) 2009 kuwata-lab.com all rights reserved.
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 escapeHi <%== @name %>! # without escape
36
copyright(c) 2009 kuwata-lab.com all rights reserved.
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> <% end %> </tbody></table><% end %>
• HTML and Ruby code are mixed• It is hard to recognize corresponding
'end' (because 'do' and 'end' can be separated 100 lines for example)
37
copyright(c) 2009 kuwata-lab.com all rights reserved.
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 -wcSyntax OK
38
copyright(c) 2009 kuwata-lab.com all rights reserved.
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
39
copyright(c) 2009 kuwata-lab.com all rights reserved.
$ 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 %>
40
copyright(c) 2009 kuwata-lab.com all rights reserved.
$ 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
-x : show Ruby script
41
copyright(c) 2009 kuwata-lab.com all rights reserved.
$ 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
-X : suppress to print HTML
42
copyright(c) 2009 kuwata-lab.com all rights reserved.
$ 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
-N : print line numbers
43
copyright(c) 2009 kuwata-lab.com all rights reserved.
$ 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
-U : unifiy consecutive empty lines into a line
44
copyright(c) 2009 kuwata-lab.com all rights reserved.
$ 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
-C : remove empty lines (compact)
45
copyright(c) 2009 kuwata-lab.com all rights reserved.
Issue : expr can contain statements
<%= form_for :user do %> <div> <%= text_field :name %> </div><% end %>
embed return value of helper method by <%= %>
block contains statements
beyond of eRuby spec!
46
copyright(c) 2009 kuwata-lab.com all rights reserved.
Cause of Issue
_buf = "";_buf << ( 10.times do ).to_s;_buf << " Hello\n"; end
<%= 10.times do %> Hello<% end %>
<%= expr %> is expected to be completed by itself
Syntax error!Convert
47
copyright(c) 2009 kuwata-lab.com all rights reserved.
Solution by ERB+Rails
‣Change local variable (_erbout) on caller-size from callee-side
<% form_for do %> Hello<% end %>
Not use <%= %>
_erbout = ""form_for do _erbout.concat("Hello")end
Append to '_erbout' from internal of form_for()
black magic!!
48
copyright(c) 2009 kuwata-lab.com all rights reserved.
Solution by Erubis+Merb
‣Extend parser of Erubis
<%= form_for do %> Hello<% end =%>
@_buf << (form_for do;@_buf << "Hello\n" end);
Introduce end-of- block notation
Change _buf into instance variable
Recognize blok in embedded expr
49
copyright(c) 2009 kuwata-lab.com all rights reserved.
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
50
copyright(c) 2009 kuwata-lab.com all rights reserved.
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
51
copyright(c) 2009 kuwata-lab.com all rights reserved.
Part 3. Future of template system
52
copyright(c) 2009 kuwata-lab.com all rights reserved.
Template and Programming
‣Template is also program code
<ul><% for x in @a %> <li><%=x%></li><% end %></ul>
print "<ul>\n"for x in @aprint "<li>#{x}</li>\n"endprint "</u>\n"
Equiv.
Possible to apply programming techniques or concepts to template system
53
copyright(c) 2009 kuwata-lab.com all rights reserved.
Template and Method
<ul> <li><%=x%></li></ul>
Template is a kind of method definition
s = File.read('foo.eruby')e = Erubis::Eruby.new(s)puts e.evaluate(:x=>1)
Rendering template is a kind of method invokation
Context data is actual argument for method
54
copyright(c) 2009 kuwata-lab.com all rights reserved.
Template and formal argument
‣ Formal arguments may be necessary for template
<%#ARGS: items, name='guest' %>Hello <%= name %>!<% for x in items %> <li><%=x%></li><% end %>
•Clear context variables•Available default value
55
copyright(c) 2009 kuwata-lab.com all rights reserved.
Template and modularity
‣Also HTML should be Small & Many, not Single & Large
• Same as principle of method definition
<html> <body> <h1><%=@title%></h1> <ul id="menulist"> <% for x in @items %> <li><%=x%></li> <% end %> </ul> </body></html>
<html> <body> </body></html>
<h1><%=@title%></h1> <ul id="menulist"> </ul>
<% for x in @items %> <li><%= x %></li> <% end %>
Be benefit for designer!
split
56
copyright(c) 2009 kuwata-lab.com all rights reserved.
Template and Object-Oriented
‣Template inheritance in Django
....{% block pagetitle %}<h1>{{title}}</h1>{% endblock %}....
Parent template
Available to overwrite or add contents before/after(method override)
57
copyright(c) 2009 kuwata-lab.com all rights reserved.
Template and Aspect-Oriented
‣Weave some code into other program
• Similar to layer of Photoshop
<table>
<tr>
<td>
</tr>
</table>
"for x in @a"
"end"
"print x"
•Enable to split HTML and presentation logics
•Available to insert a logic into several points (DRY)
58
copyright(c) 2009 kuwata-lab.com all rights reserved.
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)
59
copyright(c) 2009 kuwata-lab.com all rights reserved.
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
60
copyright(c) 2009 kuwata-lab.com all rights reserved.
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/
61
copyright(c) 2009 kuwata-lab.com all rights reserved.
one more thing
62
copyright(c) 2009 kuwata-lab.com all rights reserved.
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/
63
copyright(c) 2009 kuwata-lab.com all rights reserved.
thank you
64