at posts/single.html

Erubis を使ってみる (2) … ERB と Erubis の挙動の違い

先日の続き。 tDiary で Erubis を動かすとエラーになる件を調べてみた。

エラーの箇所は TDiaryBase::do_eval_html (tdiary.rb の1129行目付近) 。

r = ERB::new( rhtml.untaint ).result( binding )
r = ERB::new( r ).src

ソースを読むと、(理由は分からないけど) tDiary はERBを2回変換しているみたい。 そこで、一度目の変換の結果をファイルに出力して、 ERB と Erubis での変換結果の違いを比べてみた。 結果はこれ。

 <div class="day">
-<h2><%= title_proc( Time::at( 1224428400 ), "" ) %></h2>
+<h2><%= title_proc( Time::at( <%=@date.to_i%> ), "" ) %></h2>
 
 <div class="body">
-<%= body_enter_proc( Time::at( 1224428400 ) ) %>
+<%= body_enter_proc( Time::at( <%=@date.to_i%> ) ) %>

Erubis を使った場合は、 @date.to_i が変換されずに残っている。 これがエラーの原因みたい。

ERB と Erubis の挙動の違い

どうやら「<%%=」の挙動が違うようなので、サンプルプログラムで問題を切り分け。

str = "<%%= Time::at( <%= @date.to_i %> %>"

@date = Time.now
puts ERB.new(str).result(binding)
puts Erubis::Eruby.new(str).result(binding)
puts Erubis::TinyEruby.new(str).result(binding)

結果は以下の通り。

ERB は内側の @date.to_i を変換している。 変換結果は有効な eRuby フォーマットなので、もう一度 eRuby を通しても大丈夫。

<%= Time::at( 1227412425 ) %>

Erubis::Eruby は内側の @date.to_i をそのままにする。 変換結果は eRuby フォーマットとして不適切なので、もう一度 eRuby を通すとエラーになる。

<%= Time::at( <%= @date.to_i %> ) %>

Erubis::TinyEruby はコンパイルエラーになった。

example2.rb:13: compile error (SyntaxError)
example2.rb:13: syntax error, unexpected tIVAR, expecting $end
_buf = '';%= Time::at( <%= @date.to_i ; _buf << ' ) %>';
                                ^       from example2.rb:13

eRuby の仕様としては、どの挙動が正解なんだろうか。

  1. Erubis のバグなので、解消されれば tDiary でも Erubis がそのまま動くようになる。
  2. 仕様は不定なので、 tDiary の実装を変えないと Erubis は動かせない。

調べてみた (ERB)

Rubyinst Magazine - 標準添付ライブラリ紹介 【第 10 回】 ERBに説明があった。

<%% … 「<%」をその場に挿入 (「<% ... %>」の中ではそのまま)

「<% ... %>」や「<%%」の挙動は明記されているけど、「<%% ... %>」の挙動は明記されていない。 単純に考えると、「<%%」は「<%」に置き換わるだけなので、内部の「<% ... %>」は変換するのが正しいように思えるけど。うーん。

調べてみた (Erubis)

Erubis の動作はErubis User's Guideに書かれていた。

Since 2.6.0, '<%% %>' and '<%%= %>' are converted into '<% %>' and '<%= %>' respectively. This is for compatibility with ERB.

ERB との互換性のために 2.6.0 から対応したと。 互換性が目的だったら、パッチを送れば ERB と同じ動作になるように修正される可能性が高いかな。

追記

そんなに簡単な問題じゃないかも。 Erubis は <% ... %> を String#scan でマッチさせて処理しているみたい。 <%%= ... <%= ... %> ... %> のようにネストした構造に対応するには難しいのかも。 ERB はどうやってるんだろ。