Rails3 で RDoc ドキュメント生成時に invalid byte sequence in UTF-8 エラーが出た場合の対応

日本語でコメントを記述したプログラムを利用して RDoc ドキュメントを生成するときに、
下記のようなエラーが出て途中で止まってしまうことがあります。

$ rdoc -c UTF-8 -U
Parsing sources...
Before reporting this, could you check that the file you're documenting
compiles cleanly--RDoc is not a full Ruby parser, and gets confused easily if
fed invalid programs.

The internal error was:

        (ArgumentError) invalid byte sequence in UTF-8

uh-oh! RDoc had a problem:
invalid byte sequence in UTF-8

run with --debug for full backtrace

これは、ドキュメントを生成する際に RDoc の内部で 1024byte 毎にファイルを区切っていて、
1024byte の部分がちょうど日本語 UTF-8 の 3byte の中になってしまった場合にエラーになるようです。
実験として、下記のような model を作成してみました。

# coding: utf-8
#
#= エラー再現確認用モデル
#
# 1024byte の部分が日本語だった場合の実験用モデル
#


class Mail < ActiveRecord::Base
  #
  # 1024byte の部分が、日本語 UTF8 の 1 文字の途中だった場合は
  # rdoc -c UTF-8 -U を実行した際にエラーとなって rdoc が生成できないみたい。
  # この実験のために日本語をたくさん書いてエラーを再現してみようと思います。
  # ここまで書いてまだ半分の 512byte まで到達していなくてちょっとびっくり。
  # 意味の無い文書を長く書くことも結構難しいですね。
  # ここで色々なことを書いておけば SEO でヒットするキーワードが多くなるかなとか
  # 適当なことを考えながら淡々と文章を書き続けています。
  #
  # あとちょっとで 1024byte にまで届きそう!
  # がんばれ俺!負けるな俺!
  # この行がちょうど 1024byte になるのでここで打ち止め!
end

そして /usr/local/lib/ruby/gems/1.9.1/gems/rdoc-2.5.11/lib/rdoc/parser.rb の
def self.binary? に下記のような修正を加えてみます。

  def self.binary?(file)
    s = File.read(file, 1024) or return false
    set_encoding(s)

    p s # この行を状況確認用に追加

    if s[0, 2] == Marshal.dump('')[0, 2] then
      true
    elsif file =~ /erb\.rb$/ then
      false
    elsif s.scan(/<%|%>/).length >= 4 || s.index("\x00") then
      true
    elsif 0.respond_to? :fdiv then
      s.count("^ -~\t\r\n").fdiv(s.size) > 0.3
    else # HACK 1.8.6
      (s.count("^ -~\t\r\n").to_f / s.size) > 0.3
    end
  end

そして rdoc -c UTF-8 -U を実行すると下記のようなエラーで止まります。
(読みやすいように改行を加えています)

"# coding: utf-8\n#\n#= エラー再現確認用モデル\n#\n# 1024byte の部分が日本語だった場合の実験用モデル
\n#\n\n\nclass Mail < ActiveRecord::Base\n  #\n  # 1024byte の部分が、日本語 UTF8 の 1 文字の途中だっ
た場合は\n  # rdoc -c UTF-8 -U を実行した際にエラーとなって rdoc が生成できないみたい。\n  # この実験
のために日本語をたくさん書いてエラーを再現してみようと思います。\n  # ここまで書いてまだ半分の
512byte まで到達していなくてちょっとびっくり。\n  # 意味の無い文書を長く書くことも結構難しいですね 。\n
# ここで色々なことを書いておけば SEO でヒットするキーワードが多くなるかなとか\n # 適当なことを考えながら
淡々と文章を 書き続けています。\n  #\n  # あとちょっとで 1024byte にまで届きそう!\n  # がんばれ俺!
負けるな俺!\n  # この行がちょうど 1024byte になるのでここで打ち止\xE3"
Before reporting this, could you check that the file you're documenting
compiles cleanly--RDoc is not a full Ruby parser, and gets confused easily if
fed invalid programs.

The internal error was:

        (ArgumentError) invalid byte sequence in UTF-8

uh-oh! RDoc had a problem:
invalid byte sequence in UTF-8

run with --debug for full backtrace

rdoc 生成時の app/models/mail.rb のファイルサイズは 1034byte です。
なので utf-8 を 1 文字 3byteで最後から逆算していくと、
下記のように "打ち止め!" の "め" の部分がちょうど 1024byte 目から始まってしまっています。

byte       | char
----------------
1015〜1017 | 打
1018〜1020 | ち
1021〜1023 | 止
1024〜1026 | め
1027〜1029 | !
1030       | \n
1031       | e
1032       | n
1033       | d
1034       | \n

というわけで、こういったケースはプログラム内の先頭や途中に適当に半角文字を追加して、
日本語が 1024byte にぶつからないように微調整してあげることでエラーが出ないようになります。

あぁ、日本語ってめんどくさい、、、