n行で書くテンプレートエンジン
io のやつは 50 行ぐらいだった。いい線じゃないですか!! 正規表現ないのに!!! (あるけど標準じゃない)
とかはどうでもよくて、Ruby で 40 行で ERB に似てるテンプレートエンジン (たぶんすごいだめなバグがある気がする)
class MiniERB
module Util
ESCAPES = { "&" => "&", "<" => "<" , ">" => ">"}
def escape(str)
str.to_s.gsub(/[#{ESCAPES.keys.join}]/o) {|i| ESCAPES[i] }
end
module_function :escape
end
attr_reader :src
def initialize(template)
@src = compile(template)
end
def compile(s)
ret = "_tt_string = ''¥n"
while m = s.match(/<%([^¥s]*)/)
ret << "_tt_string << #{m.pre_match.dump}¥n"
code = m.post_match.match(/(.*?)%>/)
s = code.post_match
code = code[1]
case m[1]
when "h"
ret << "_tt_string << MiniERB::Util.escape(#{code})¥n"
when "="
ret << "_tt_string << (#{code}).to_s¥n"
else
ret << "#{code}¥n"
end
end
ret << "_tt_string << #{s.dump}"
ret
end
def result(binding=TOPLEVEL_BINDING)
eval(@src, binding)
end
endsample
require "time"
entries = [
{
:title => "hoge<<<&>>>>>>>>>>",
:body => "a<a href='aa'>a</a>a",
:date => Time.now
},
{
:title => "fuaaaaa<<<",
:body => "hehe, Nice boat.",
:date => Time.now
},
]
puts MiniERB.new(DATA.read).result(binding)
__END__
<% entries.each do |entry| %>
<div class="entry">
<h2><%h entry[:title] %></h2>
<div class="body"><%= entry[:body] %></div>
<div class="info">Time: <%= entry[:date].xmlschema %></div>
</div>
<% end %>%h で htmlescape 済みの。 = で生のデータうめこみ。
できるだけ式展開するバージョン (早くなると思われる) 同じく 40 行
class MiniERBExpressionExpantion
module Util
ESCAPES = { "&" => "&", "<" => "<" , ">" => ">"}
def escape(str)
str.to_s.gsub(/[#{ESCAPES.keys.join}]/o) {|i| ESCAPES[i] }
end
module_function :escape
end
attr_reader :src
def initialize(template)
@src = compile(template)
end
def compile(s)
ret = "_tt_string = ¥""
while m = s.match(/<%([^¥s]*)/)
ret << "¥#{#{m.pre_match.dump}}"
code = m.post_match.match(/(.*?)%>/)
s = code.post_match
code = code[1]
case m[1]
when "h"
ret << "¥#{MiniERB::Util.escape(#{code})}"
when "="
ret << "¥#{(#{code}).to_s}"
else
ret << "¥"¥n#{code}¥n _tt_string << ¥""
end
end
ret << "¥" << #{s.dump}"
ret
end
def result(binding=TOPLEVEL_BINDING)
eval(@src, binding)
end
end
ベンチマーク
require "time"
data = DATA.read
entries = [
{
:title => "hoge<<<&>>>>>>>>>>",
:body => "a<a href='aa'>a</a>a",
:date => Time.now
},
{
:title => "fuaaaaa<<<",
:body => "hehe, Nice boat.",
:date => Time.now
},
]
require "benchmark"
Benchmark.bmbm do |x|
x.report("concat") { 100000.times {
MiniERB.new(data).result(binding)
} }
x.report("expexp") { 100000.times {
MiniERBExpressionExpantion.new(data).result(binding)
} }
end
__END__
<% entries.each do |entry| %>
<div class="entry">
<h2><%h entry[:title] %></h2>
<div class="body"><%= entry[:body] %></div>
<div class="info">Time: <%= entry[:date].xmlschema %></div>
</div>
<% end %>Mac 1.8.2 / 1.83GHz CoreDuo
ruby 1.8.2 (2004-12-25) [universal-darwin8.0]
Rehearsal ------------------------------------------
concat 35.230000 0.240000 35.470000 ( 36.472992)
expexp 33.690000 0.220000 33.910000 ( 35.208808)
-------------------------------- total: 69.380000sec
user system total real
concat 35.220000 0.210000 35.430000 ( 36.348813)
expexp 33.570000 0.180000 33.750000 ( 34.598484)ubuntu LTS 1.8.4 / Intel(R) Celeron(R) CPU 2.66GHz
ruby 1.8.4 (2005-12-24) [i486-linux]
Rehearsal ------------------------------------------
concat 37.250000 1.040000 38.290000 ( 38.371623)
expexp 36.270000 1.000000 37.270000 ( 42.917439)
-------------------------------- total: 75.560000sec
user system total real
concat 37.110000 0.980000 38.090000 ( 38.109001)
expexp 35.650000 1.030000 36.680000 ( 36.799591)ubuntu LTS 1.9.0
ruby 1.9.0 (2007-09-24 patchlevel 0) [i686-linux]
Rehearsal ------------------------------------------
concat 37.910000 0.060000 37.970000 ( 38.100921)
expexp 35.910000 0.010000 35.920000 ( 35.944483)
-------------------------------- total: 73.890000sec
user system total real
concat 37.870000 0.010000 37.880000 ( 37.909874)
expexp 36.040000 0.020000 36.060000 ( 37.622068)>
いちおうはやくなったようだ。誤差の範囲すぎる。
(あたまわるくて意味ないベンチした)