まちゅダイアリー
2007-12-28 (金)
■ Ruby 1.9.0 で tDiary を動かしてみる(トップページだけ)
Ruby 1.9.0 で非互換が増えたって話だけど、実際のところどれくらい影響があるかよく分かんない。 ってことで、ちょうど tDiary 2.2 もリリースされたことだし、無謀にも tDiary を Ruby 1.9.0 で動かそうとしてみた。 とりあえず、トップページが表示されるところまではできた。 でも、編集とかプラグイン系はダメっぽい。
以下、トップページを動かすまでの記録。
$defout
まず、普通に設置してみると、いきなり Internal Server Error が出た。 どうも、 $defout.binmode がダメっぽい。irb で実験。Ruby1.8.5だと $binmode は IO オブジェクトが返ってくる。
irb(main):001:0> $defout => #<IO:0x8120a7c>
Ruby1.9.0だと nil が返ってくる。
irb(main):001:0> $defout => nil
ってことで、 index.rb から2行を削除。 $KCODE はともかく、 $defout を削除していいのかなぁ…。
@@ -5,8 +6,6 @@
# Copyright (C) 2001-2006, TADA Tadashi <sho@spc.gr.jp>
# You can redistribute it and/or modify it under GPL2.
#
-BEGIN { $defout.binmode }
-$KCODE = 'n'
begin
if FileTest::symlink?( __FILE__ ) then
文字コードの指定
次に出たのがこのエラー。
(tdiary.conf):138: invalid multibyte char
Ruby1.9 から文字列がエンコーディング情報を持つようになった…ってどこかで読んだ。 文字コードが EUC-JP だよと Ruby に伝えていないから、不明なマルチバイト文字だよって言っているんだろうなぁ。
Ruby1.9 の m17n に関する情報ってどこでまとまっているんだろ? いろいろググってたら、2007-12-16 - diary of a madman で文字コード関連の情報を発見。
ファイルの先頭付近に "# -*- encoding: UTF-8 -*-" と書いておくと、そのファイルのデフォルトエンコーディングを指定できる。
コマンドラインから --encoding=UTF-8 といった指定も可能。
最初は tdiary.conf の先頭に "# -*- encoding: UTF-8 -*-" と書いたんだけど、すべてのマルチバイト文字が含まれるファイル (特にプラグインの日本語リソース) で同じ指定が必要になりそうな気配だったので、 --encoding=EUC-JP を指定することにした。 index.rb の先頭を修正。
-#!/usr/bin/env ruby +#!/home/machu/local/bin/ruby --encoding=EUC-JP
instance_variables の戻り値が文字列からシンボルへ
次に出たエラーがこれ。
undefined method `sub!' for :@cgi:Symbol (NoMethodError) /home/machu/www/www.machu.jp/diary2/tdiary.rb:469:in `block in initialize'
tdiary.rb にて instance_variables の返り値を処理するところでのエラーみたい。
また irb を使って試してみる。 Ruby1.8.5 だと、インスタンス変数名は文字列として返ってくる。
irb(main):001:0> @a = @b = 0 => 0 irb(main):002:0> instance_variables => ["@b", "@a"]
Ruby1.9.0 だと、文字列ではなくシンボルとして返ってくる。
irb(main):001:0> @a = @b = 0 => 0 irb(main):002:0> instance_variables => [:@b, :@a]
シンボルに sub! をしていたのでエラーになっていたと。 なので、 sub! の直前でシンボルを文字列に変換した。 こんなんでいいのだろうか?
@@ -466,6 +466,7 @@
load
instance_variables.each do |v|
+ v = v.to_s
v.sub!( /@/, '' )
instance_eval( <<-SRC
def #{v}
複数文字コードの混在
次のエラーはこれ。
Plugin error in '00default.rb'. invalid multibyte escape: /[\x80-\xff]/ (plugin/00default.rb):615: invalid multibyte escape: /[\x80-\xff]/
該当の行の処理は 00default.rb の以下のコード。
mail_header = comment_mail_mime( @conf.to_mail( mail_header ) ).join( "\n " ) if /[\x80-\xff]/ =~ mail_header
メールを送ろうとしているから、 mail_header の文字コードは ISO-2022-JP なんだろう。 EUC-JP で使われていないコードを指定しているからエラーがでているのかな? 正規表現で使用している [\x80-\xff] が NG っぽい。 原因が分からなかったので、とりあえずここはコメントアウトした。
…ダメじゃん。
文字列中のバックスラッシュ
こんどはこれ。
TDiary::PluginError Plugin error in '05referer.rb'. (plugin/ja/05referer.rb):34: invalid multibyte char
ソースコード中のバックスラッシュの扱いがよくないみたい。
置換文字列中で「\\1」のような「\数字」で利用できます
以下のようにすれば動いた。
置換文字列中で「\\\\1」のような「\\数字」で利用できます
String#to_a がなくなった
今度は以下のエラー。
TDiary::PluginError Plugin error in '50sp.rb'. undefined method `to_a' for "/home/machu/www/www.machu.jp/diary2/misc/plugin":String (plugin/50sp.rb):4:in `block in load_plugin'
該当ソースは以下の通り。最後の to_a で失敗している。
@sp_path = ( @conf["#{SP_PREFIX}.path"] || "#{TDiary::PATH}/misc/plugin" ).to_a
よく分からないので、 irb で実験してみよう。 まずは Ruby 1.8.5。 String#to_a で配列が返ってくる。
irb(main):001:0> "abc".to_a => ["abc"]
次に Ruby 1.9.0。 String#to_a はないよ、って言われた。
irb(main):001:0> "abc".to_a NoMethodError: undefined method `to_a' for "abc":String
ってことで、以下のようにソースを修正。なんか間違っている気がする…。
@sp_path = @conf["#{SP_PREFIX}.path"] || [ "#{TDiary::PATH}/misc/plugin" ]
変数の文字コードを指定
次がこれ。 ようやくプラグインの実行 (eval_src) のところまでたどり着いたみたい。
ArgumentError character encodings differ(TDiary::Plugin#eval_src):10:in `concat' (TDiary::Plugin#eval_src):10:in `block in eval_src' /home/machu/www/www.machu.jp/diary2/tdiary.rb:753:in `eval'
これまたエンコーディングが違うよ、と。 どうも、 eval の対象文字列 (src) の文字コードが ASCII と判定されているみたい。 ちょっとやっつけで、 eval の直前で src の文字コードを EUC-JP と指定したらエラーがでなくなった。
@@ -747,6 +748,7 @@
@subtitle_procs.taint
@section_leave_procs.taint
return Safe::safe( secure ? 4 : 1 ) do
+ src.force_encoding('EUC-JP')
eval( src, binding, "(TDiary::Plugin#eval_src)", 1 )
end
end
ページが途中で切れる
これでようやくトップページが表示されたんだけど、ソースをみるとページが途中で切れている。
どうも index.cgi で指定している Content-Length の長さが正しくないみたい。 Ruby 1.9.0 では String#size がバイト数ではなく文字数を返すようになったからだろうなぁ。 String#size の代わりに String#bytesize を使うように修正。
@@ -78,7 +81,7 @@
body = ''
else
head['charset'] = conf.encoding
- head['Content-Length'] = body.size.to_s
+ head['Content-Length'] = body.bytesize.to_s
end
head['Pragma'] = 'no-cache'
head['Cache-Control'] = 'no-cache'
これでようやくトップページが表示された。 でも、日記を編集しようとするとパーサ周りでまたエラーが出てる。
undefined method `collect' for "日記のテスト":String (NoMethodError) /home/machu/www/www.machu.jp/diary2/tdiary/tdiary_style.rb:202:in `block in to_html4'
こっちは時間かかりそうだなぁ…。とりあえずここまで。
追記
そんなに時間かからなかった。 String#collect の仕様変更について、 eigenclass - Changes in Ruby 1.9より引用。
No longer an Enumerable
String is not Enumerable anymore. Use #each_line instead of #each, and #lines (see below) to iterate over the lines.
ここに書かれているように、 each_line を使えば Enumerable が取れる。 tdiary_style.rb のエラーが出ている箇所を body.collect から body.each_line.collect に書き換えれば OK 。
@@ -199,7 +199,7 @@
r << %Q[<p>#{section.body.collect{|l|l.chomp.sub( /^[ ]/e, '')}.join( "</p>\n<p>" )}</p>\n]
else
r << %Q[<p><%= subtitle_proc( Time::at( #{date.to_i} ), nil ) %>]
- r << %Q[#{section.body.collect{|l|l.chomp.sub( /^[ ]/e, '' )}.join( "</p>\n<p>" )}</p>]
+ r << %Q[#{section.body.each_line.collect{|l|l.chomp.sub( /^[ ]/e, '' )}.join( "</p>\n<p>" )}</p>]
end
r << %Q[<%= section_leave_proc( Time::at( #{date.to_i} ) ) %>\n]
r << %Q[</div>]
んで、今は以下のエラーがでてる。
ArgumentError character encodings differ (erb):66:in `concat' (erb):66:in `do_eval_rhtml' /home/machu/local/lib/ruby/1.9.0/erb.rb:743:in `eval' /home/machu/local/lib/ruby/1.9.0/erb.rb:743:in `result' /home/machu/www/www.machu.jp/diary2/tdiary.rb:1094:in `do_eval_rhtml'
TDiaryBase#do_eval_rhtml でエラーが出てる。 また文字エンコーディングが違うよと。 うーん。そろそろ行き当たりばったりでつぶしていくのは辛いかな?
追記2
defaultio.rb と 05referer.rb にて String#each を String#each_line へと置き換えると、マルチバイト文字を含まない日記は更新できるようになった。これも String が Enumerable じゃなくなった影響。
@@ -103,7 +103,7 @@
headers, body = ::TDiary::parse_tdiary( l )
ymd = headers['Date']
next unless body
- body.each do |r|
+ body.each_line do |r|
count, ref = r.chomp.split( / /, 2 )
next unless ref
yield( ref.chomp, count.to_i )
でも、マルチバイト文字を含むと前述の character encodings differ が発生するのは変わらず。 うーん。 Web ブラウザから送信した日記データと、日記のテンプレート (skel/*.rhtml) の文字エンコーディングが一致していないのかな? でも、本体だけなら出口が見えてきた。
試しに以下の URL に設置してみた。そのうち消すかも。 ちゃんと "Powered by Ruby version 1.9.0" だよ。
■ Ruby1.9 で --encoding オプションはつけないほうが幸せ?
tDiary2.2 + Ruby1.9.0 でマルチバイト文字を含む日記を入力すると "character encodings differ" が発生する問題。 いろいろ試してみたけど、どうも ERB がテンプレートを解釈しようとしたときに起きているみたい。
以下のサンプルで再現してみた。ソースコードは EUC-JP で書いている。
require 'erb'
data = "データ"
rhtml = "テンプレート + <%= data %>"
puts "data: #{data.encoding}"
puts "rhtml: #{rhtml.encoding}"
puts ERB::new(rhtml).result(binding)
起動時の引数で文字コードを指定しなければ、特に問題なく動作する。 このときのエンコーディングは ASCII-8BIT として扱われている。
$ ~/local/bin/ruby test.rb data: ASCII-8BIT rhtml: ASCII-8BIT テンプレート + データ
デフォルトエンコーディングを EUC-JP にすると、 "character encodings differ" が発生する。
$ ~/local/bin/ruby --encoding=EUC-JP test.rb
data: EUC-JP
rhtml: EUC-JP
(erb):1:in `concat': character encodings differ (ArgumentError)
from (erb):1:in `<main>'
from /home/machu/local/lib/ruby/1.9.0/erb.rb:743:in `eval'
from /home/machu/local/lib/ruby/1.9.0/erb.rb:743:in `result'
from test.rb:12:in `<main>'
data と rhtml の encoding 指定を変えた結果は以下の通り。
| data (ASCII-8BIT) | data (EUC-JP) | |
| rhtml (ASCII-8BIT) | ○ | × |
| rhtml (EUC-JP) | ○ | × |
ここから推測するに、 ERB に渡したテンプレート (rhtml) は、 encoding 情報が欠落して内部では ASCII-8BIT として扱われているんじゃないかな。 これって正しい挙動?それともバグ? さて、どうしたものか。 --encoding=EUC-JP を指定して、 ERB に渡すときだけ ASCII-8BIT として扱う? でもそれだと、マルチバイトを含む正規表現を使っていた場合にうまく動かなくなりそう。
■ Ruby 1.9.0 の ERB ではテンプレートの encoding が欠落する
やっぱりさっきの予想は的中。以下のソースで確認した。
require 'erb'
data = "データ"
rhtml = "テンプレート + <%= data %>"
erb = ERB.new(rhtml)
puts "data: #{data.encoding}"
puts "rhtml: #{rhtml.encoding}"
puts "erb.src: #{erb.src.encoding}"
puts erb.result(binding)
このプログラムを --encoding=ETC-JP オプションをつけて実行する。
$ ~/local/bin/ruby --encoding=EUC-JP test.rb
data: EUC-JP
rhtml: EUC-JP
erb.src: ASCII-8BIT
(erb):1:in `concat': character encodings differ (ArgumentError)
from (erb):1:in `<main>'
from /home/machu/local/lib/ruby/1.9.0/erb.rb:743:in `eval'
from /home/machu/local/lib/ruby/1.9.0/erb.rb:743:in `result'
from test.rb:14:in `<main>'
ERB.new の引数で渡したときは EUC-JP なのに、内部でコンパイルすると ASCII-8BIT になっている。 それで、 ASCII-8BIT のソースを eval で実行するときに、 EUC-JP な data を埋め込もうとしてエラーになると。
ERB の仕組みが分かっていないけど、とりあえず以下のパッチで対処した。
$ diff -u erb.rb.org erb.rb
--- erb.rb.org Fri Dec 28 23:05:02 2007
+++ erb.rb Fri Dec 28 23:05:18 2007
@@ -689,6 +689,7 @@
compiler = ERB::Compiler.new(trim_mode)
set_eoutvar(compiler, eoutvar)
@src = compiler.compile(str)
+ @src.force_encoding(str.encoding)
@filename = nil
end
今度は期待した動作になったよ。これで先に進めるかな。
$ ~/local/bin/ruby --encoding=EUC-JP test.rb data: EUC-JP rhtml: EUC-JP erb.src: EUC-JP テンプレート + データ

あまりの非互換っぷりに吹いたw<br>こりゃ、1.8/1.9両対応なんて無理ですかねぇ?
これはひどい
$defout ハ $stdout ニスレバヨイノデハ<br><br>http://doc.loveruby.net/refm/api/view/method/Kernel/v/defout
移行ツールをOSSで作れば皆Happyになると思う。<br>差分一覧みたいなのがあれば、意外と簡単そうだけど
>ただただしさん<br>String#to_a, String#each を勝手に定義しないと両対応はダメっぽいですね。<br><br>>しばたさん<br>1.9.1が出る頃には互換性が向上していることを勝手に期待してます。<br><br>>トオルさん<br>なるほど!$defoutは非推奨だったのですね。<br>マニュアル読んで出直してきます。<br><br>>kenzさん<br>動的言語なので移行の自動化は大変かもと思ってます。
考え直して<br>そもそもインタプリタだから「移行」という考え自体がスマートじゃない<br>↓<br>動的に1.8で書けば1.8で動いて1.9で書けば1.9で動くのが理想<br>↓<br>そもそもRubyに限る必要はないな<br>↓<br>Pythonで書けばPythonで、C#で書けばC#で動けばよりベター<br>↓<br>それってCLRじゃん・・・ って結論になってしまいました。<br>やっぱMSは偉大
JythonもJRubyもJavascriptも既に動かせるJavaVM涙目w
まさかこんなとこでCLRとJavaVMの対決になるとはw<br><br>>kenzさん<br>1台のサーバに1.8と1.9の両方を入れておけばいいって話?<br>それじゃいつまでもtDiaryが1.9に移行できないです><<br><br>>sageさん<br>移行以上にJRubyとCRuby1.9の行く先が気になりますね。
互換性が高くなることはないんじゃないかと思っています。 > 1.9.1<br>以下の記事を読んで、そう思いました。<br><br>http://www.atmarkit.co.jp/news/200712/25/ruby.html<br><br>まつもと いえいえ、逆にこれをちゃんと伝えていただきたいんです。1.9は安定版じゃなくて開発版です。“ブリーディング・エッジ”(Bleeding Edge)といって血がどばどば出ている状態のものです。そのために1.8系を用意しているということです。1.9の荒削りな部分が丸くなるには年単位で時間がかかると思うんです。そのときまでは1.8が運用されていると思います。もし何かの事情で1.9をエンタープライズで使いたいということであれば、自己責任でやっていただくしかない。
>koujiさん<br>確かに、「1.9系の安定版は1.9.9か、またはそれ以前かそれ以降のバージョン」ですね。<br>http://d.hatena.ne.jp/takahashim/20071228#p1<br>でも、少しずつでも向上すればいいですね。