at posts/single.html

Ruby 1.9.0 で tDiary を動かしてみる(トップページだけ)

tDiary on Ruby 1.9.0

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

関連する日記