all about erubis (english)

64
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

Upload: kwatch

Post on 09-May-2015

5.167 views

Category:

Technology


2 download

DESCRIPTION

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

TRANSCRIPT

Page 1: 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

Page 2: All about Erubis (English)

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

Page 3: All about Erubis (English)

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

Page 4: All about Erubis (English)

copyright(c) 2009 kuwata-lab.com all rights reserved.

Part 1. Features of Erubis

4

Page 5: All about Erubis (English)

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

Page 6: All about Erubis (English)

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

Page 7: All about Erubis (English)

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

&lt;B&am;&gt;<B&B>

<%= %> ... WITH escaping,<%== %> ... WITHOUT escaping

output:User can choose escape or not escape in default (choosability)

7

Page 8: All about Erubis (English)

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

Page 9: All about Erubis (English)

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

Page 10: All about Erubis (English)

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

Page 11: All about Erubis (English)

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

Page 12: All about Erubis (English)

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

Page 13: All about Erubis (English)

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

Page 14: All about Erubis (English)

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

Page 15: All about Erubis (English)

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

Page 16: All about Erubis (English)

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

Page 17: All about Erubis (English)

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

Page 18: All about Erubis (English)

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

Page 19: All about Erubis (English)

copyright(c) 2009 kuwata-lab.com all rights reserved.

Part 2. Issues about eRuby and solutions by Erubis

19

Page 20: All about Erubis (English)

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

Page 21: All about Erubis (English)

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

Page 22: All about Erubis (English)

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

Page 23: All about Erubis (English)

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

Page 24: All about Erubis (English)

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

Page 25: All about Erubis (English)

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

Page 26: All about Erubis (English)

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

Page 27: All about Erubis (English)

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

Page 28: All about Erubis (English)

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

Page 29: All about Erubis (English)

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

Page 30: All about Erubis (English)

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

Page 31: All about Erubis (English)

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

Page 32: All about Erubis (English)

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

Page 33: All about Erubis (English)

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

Page 34: All about Erubis (English)

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

Page 35: All about Erubis (English)

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

Page 36: All about Erubis (English)

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

Page 37: All about Erubis (English)

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

Page 38: All about Erubis (English)

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

Page 39: All about Erubis (English)

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

Page 40: All about Erubis (English)

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

Page 41: All about Erubis (English)

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

Page 42: All about Erubis (English)

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

Page 43: All about Erubis (English)

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

Page 44: All about Erubis (English)

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

Page 45: All about Erubis (English)

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

Page 46: All about Erubis (English)

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

Page 47: All about Erubis (English)

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

Page 48: All about Erubis (English)

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

Page 49: All about Erubis (English)

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

Page 50: All about Erubis (English)

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

Page 51: All about Erubis (English)

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

Page 52: All about Erubis (English)

copyright(c) 2009 kuwata-lab.com all rights reserved.

Part 3. Future of template system

52

Page 53: All about Erubis (English)

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

Page 54: All about Erubis (English)

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

Page 55: All about Erubis (English)

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

Page 56: All about Erubis (English)

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

Page 57: All about Erubis (English)

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

Page 58: All about Erubis (English)

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

Page 59: All about Erubis (English)

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

Page 60: All about Erubis (English)

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

Page 61: All about Erubis (English)

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

Page 62: All about Erubis (English)

copyright(c) 2009 kuwata-lab.com all rights reserved.

one more thing

62

Page 63: All about Erubis (English)

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

Page 64: All about Erubis (English)

copyright(c) 2009 kuwata-lab.com all rights reserved.

thank you

64