■ 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( "

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

<p>" )}</p>

] else r << %Q[<p><%= subtitle_proc( Time::at( #{date.to_i} ), nil ) %>] - r << %Q[#{section.body.collect{|l|l.chomp.sub( /^[ ]/e, '' )}.join( "</p>

<p>" )}</p>] + r << %Q[#{section.body.each_line.collect{|l|l.chomp.sub( /^[ ]/e, '' )}.join( "</p>

<p>" )}</p>] end r << %Q[<%= section_leave_proc( Time::at( #{date.to_i} ) ) %>

] 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" だよ。