at posts/single.html

tDiary2.2 for Ruby1.9.0

昨年末から tDiary2.2 を Ruby1.9.0 で動かそうとしている。

なんとか形になったので、思い切ってこの日記を Ruby1.9.0 で動かすように切り替えた。 フッタの「Powered by Ruby version 1.9.0」に注目。 一応動いているけど、正直、何が起こっても不思議じゃない。 何かあればツッコミしてもらえると嬉しいなぁ。 ちなみに、ちゃんと Ruby1.8 とも共用できるようになっているはず。

方向性は以下の通り。

  • 一つのコードで Ruby1.8 と Ruby1.9 に両対応する
  • 非互換の部分は極力 compatible.rb でカバーする
  • Ruby1.9 は --encoding=Binary で動作させる

自分が使っているプラグインは一通り動いたけど、それ以外のは分かんない。

インストール方法

  • tdiary-full-2.2.0.tar.gz を取得する
  • tdiary-core-2.2.0.patch と tdiary-plugin-2.2.0.patch を当てる
  • compatible.rb を misc/lib に入れる
  • 起動オプションに --encoding=Binary を付ける
  • cache ディレクトリの *.parser を削除 (PStore は基本的に互換性ないはず)

これらのファイルを CodeRepos に置きたいんだけど、どこに置けばいいかなぁ。

以下、変更点の説明。

compatible.rb

Ruby1.9 で無くなったメソッドを復活させる。

unless "".respond_to?('to_a')
  class String
    def to_a
      [ self ]
    end
  end
end

unless "".respond_to?('each')
  class String
    alias each each_line
  end
end

class String
  def method_missing(name, *args, &block)
    each_line.__send__(name, *args, &block)
  end
end

次に Ruby1.8 で存在しないメソッドを追加。こっちは Ruby 1.8 で動かすために必要。

unless "".respond_to?('force_encoding')
  class String
    def force_encoding(encoding)
      self
    end
  end
end

unless "".respond_to?('bytesize')
  class String
    alias bytesize size
  end
end

unless "".respond_to?('ord')
  class String
    def ord
      self[0]
    end
  end
end

最後に tDiary に特化した変更。

# Ruby1.9でNKF::nkfを呼ぶと文字列のencodingが変わってしまう。
# そのため、encodingがBinaryの環境で動かすと
# "character encodings differ" エラーとなる。
begin
  require 'nkf'
  module NKF
    alias :_nkf :nkf
    def nkf(option, src)
      r = _nkf(option, src)
      r.force_encoding('Binary')
    end
    module_function :nkf, :_nkf
  end
rescue
end

# 日本語を含むツッコミを入れると diary.last_modified が String になる (原因不明)
# (PStore 保存前は Time だが, 保存後に String となる)
# 暫定的に String だったら Time へ変換する
module TDiary
  class WikiDiary
    def last_modified
      if @last_modified.class == String
        @last_modified = Time.at(0)
      end
      @last_modified
    end
  end
end

index.rb

$defout を $stdout に変更。 $defout は Ruby1.6 との後方互換性のためで、1.8でも非推奨

Index: index.rb
===================================================================
--- index.rb  (revision 97)
+++ index.rb  (working copy)
@@ -5,7 +5,7 @@
 # Copyright (C) 2001-2006, TADA Tadashi <sho@spc.gr.jp>
 # You can redistribute it and/or modify it under GPL2.
 #
-BEGIN { $defout.binmode }
+BEGIN { $stdout.binmode }
 $KCODE = 'n'

 begin

update.rb

index.rb と同じ。

Index: update.rb
===================================================================
--- update.rb (revision 97)
+++ update.rb (working copy)
@@ -5,7 +5,7 @@
 # Copyright (C) 2001-2003, TADA Tadashi <sho@spc.gr.jp>
 # You can redistribute it and/or modify it under GPL2.
 #
-BEGIN { $defout.binmode }
+BEGIN { $stdout.binmode }
 $KCODE = 'n'

      if pos == 'detail' then

tdiary.rb

ちょこっと変更あり。

  • compatible.rb をロード
  • instance_variables.each が文字列でなくシンボルを返すように変更になったので to_s で文字列に変換
  • eval で宣言したローカル変数が外側で読めないので、 eval 実行時にインスタンス変数への格納まで実施
Index: tdiary.rb
===================================================================                                                           --- tdiary.rb (revision 97)
+++ tdiary.rb (working copy)
@@ -11,6 +11,7 @@

 $:.insert( 1, File::dirname( __FILE__ ) + '/misc/lib' )

+require 'compatible'
 require 'uri'
 begin
@@ -466,6 +467,7 @@
      load

      instance_variables.each do |v|
+       v = v.to_s
        v.sub!( /@/, '' )
        instance_eval( <<-SRC
          def #{v}
@@ -631,11 +633,10 @@
        cgi_conf.untaint unless @secure
        def_vars = ""
        variables.each do |var| def_vars << "#{var} = nil\n" end
-       eval( def_vars )
+       extend_vars = 'variables.each do |var| eval "@#{var} = #{var} if #{var} != nil" end'
        Safe::safe( @secure ? 4 : 1 ) do
-         eval( cgi_conf, binding, "(TDiary::Config#cgi_conf)", 1 )
+         eval( def_vars + cgi_conf + extend_vars, binding, "(TDiary::Config#cgi_conf)", 1 )
        end
-       variables.each do |var| eval "@#{var} = #{var} if #{var} != nil" end
      rescue IOError, Errno::ENOENT
      end
    end

misc/lib/hikidoc.rb

String#[] は文字コードではなく文字を返すようになったので、 ord メソッドを呼ぶように変更。 Ruby1.8 環境の場合は compatible.rb で対応している。

Index: misc/lib/hikidoc.rb
===================================================================
--- misc/lib/hikidoc.rb (revision 109)
+++ misc/lib/hikidoc.rb (working copy)
@@ -476,7 +476,7 @@

   def escape_meta_char( text )
     text.gsub( META_CHAR_RE ) do |s|
-      '&#x%x;' % s[1]
+      '&#x%x;' % s[1].ord
     end
   end

plugin/ja/05referer.rb

前に書いたとおり、エスケープの数の問題。

Index: plugin/ja/05referer.rb
===================================================================
--- plugin/ja/05referer.rb  (revision 97)
+++ plugin/ja/05referer.rb  (working copy)
@@ -31,7 +31,7 @@
  <p>→<a href="#{h @update}?referer=volatile" target="referer">既存設定はこちら</a></p>                                                                               
  <p><textarea name="only_volatile" cols="70" rows="10">#{h @conf.only_volatile2.join( "\n" )}</textarea></p>
  <h3 class="subtitle">#{label_referer_table}</h3>
- #{"<p>リンク元リストのURLを、特定の文字列に変換する対応表を指定できます。1件につき、URLと表示文字列を空白で区切って指定します。正規表現が使えるので、URL中に現れた「(&#12316;)」は、置換文字列中で「\\1」のような「\数字」で利用できます。</p>" unless @conf.mobile_agent?}
+ #{"<p>リンク元リストのURLを、特定の文字列に変換する対応表を指定できます。1件につき、URLと表示文字列を空白で区切って指定します。正規表現が使えるので、URL中に現れた「(&#12316;)」は、置換文字列中で「\\\\1」のような「\\数字」で利用できます。</p>" unless @conf.mobile_agent?}                                                                      き、URLと表示文字列を空白で区切って指定します。正規表現が使えるので、URL中に現れた「(&#12316;)」は、置換文字列中で「\\1」のような「\数字」で利用できます。</p>" unless @con  <p><textarea name="referer_table" cols="70" rows="10">#{h @conf.referer_table2.collect{|a|a.join( " " )}.join( "\n" )}</textarea></p>
  HTML

amazon.rb

たぶん、rexmlライブラリでエラー - 橋本幸樹のいまさら日記と同じ問題。 とりあえず force_encoding で逃げる。

Index: misc/plugin/amazon.rb
===================================================================
--- misc/plugin/amazon.rb (revision 113)
+++ misc/plugin/amazon.rb (working copy)
@@ -205,6 +205,7 @@
        xml =  amazon_call_ecs( asin, id_type )
        File::open( "#{cache}/#{asin}.xml", 'wb' ) {|f| f.write( xml )}
      end
+     xml.force_encoding('UTF-8')
      doc = REXML::Document::new( xml ).root
      item = doc.elements.to_a( '*/Item' )[0]
      if pos == 'detail' then

あとは contrib の category_to_tag.rb も一部変えてる。

追記

移行のときにテストツッコミがRSSに入ったり、RSSがテスト環境にリダイレクトされたりといくつかミスっちゃった。

追記2

自分でやっといてなんだけど、よほどの覚悟がないと Ruby1.9 で動かすのはお勧めしない。 まだまだ 1.9 の非互換な変更が入るかもしれないしね。

追記3

やっぱりまだ不具合がありそう。

  • ツッコミが入った日の日記にツッコミフォームが表示されない → hide_comment_form プラグインを無効にすれば表示される
  • ツッコミフォームが表示されてもツッコミが反映されない (原因不明)
  • ツッコミを入れたら ruby の CPU 使用率が 70% 前後になり、処理が終わらない (強制停止したら makerss でストップ)

たださんからの嬉しいツッコミにお返事できないよ…。なのでここに書く。

ありがとうございます! 熱すぎるかも…と思っていたのですが、UTF8部ランチとマージされる頃にはちょうどいい位に冷めていそうですね。

追記4

ツッコミフォームが表示されない原因が分かった。 今の Ruby1.9 環境だと、ツッコミを入れたときに cache/*.parser に保存されるキャッシュの last_modified が Time でなくて String になってしまうという問題がある。 PStore に保存するときは Time なのに、取り出すときは(読めない) String になる。 原因が分からなかったので、 last_modified が String のときは Time.at(0) にしていたんだけど、この影響で古い日記と判断されてツッコミフォームが消えていた(SPAM対策で古い日記にツッコミできないようにしている)。

関連する日記