GetText.update_pofilesでハマる。問題編

Ruby-GetText-Package開発者向けドキュメント – よたらぼ 保管庫

現在、ヒアドキュメントも使うことができません。これは将来的にはサポートされるかもしれません。

結論はこうなんだけど、ググるまえにソースを読み始めてこの結論にたどり着いてしまったので使った時間を成仏させるために顛末を記す。

Ruby on RailsでRuby-GetText-Packageを使う – よたらぼ 保管庫

次にメンテナンスのためのコードをRakefileに追記します。

ここで紹介されているタスクをつかってpoを作っているのだけれど、application_helper.rbにあるメソッドのヒアドキュメントで使っているGetText._が拾われていないことに気がついた。

で、「もしかしてバグか?見つけて直してパッチ送ったりしたらかっこいいんじゃないかオレすごいなオレさすがオレやるなオレよしやろうすぐやろういますぐright now!」などと不純な動機でよくわからない興奮状態に入りすぐさまgrepをかける。

% cd /usr/local/ruby/lib/ruby/gems/1.8/gems/gettext-1.10.0
% grep -r "def update_pofiles" *
lib/gettext/utils.rb:  def update_pofiles(textdomain, files, app_version, po_root = "po", refpot = "tmp.pot")

見つけた。ここしかない。
lib/gettext/utils.rb

119   def update_pofiles(textdomain, files, app_version, po_root = "po", refpot = "tmp.pot")
120     rgettext(files, refpot)
121     msgmerge_all(textdomain, app_version, po_root, refpot)
122     File.delete(refpot)
123   end

rgettextってメソッドを呼んでいるけどこのファイルにはない。しかし

 17 require 'gettext/rgettext'
18 require 'gettext/rmsgfmt'

なんてことをしている。こっちか。
lib/gettext/rgettext.rb

259   def rgettext(targetfiles = nil, out = STDOUT)
260     RGetText.run(targetfiles, out)
261     self
262   end

runだ。

233       if out.is_a? String
234 > File.open(File.expand_path(out), "w+") do |file|
235 >   file.puts generate_pot_header
236 >   file.puts generate_pot(parse(targetfiles))
237 > end
238       else
239 > out.puts generate_pot_header
240 > out.puts generate_pot(parse(targetfiles))
241       end

generate_potの中でファイルを作っているのに違いない。ここでタブとスペースのインデントが混じっているのが気になるが華麗にスルー。

 95     def generate_pot(ary) # :nodoc:
96 require 'pp'; pp ary
97       str = ""

ベタなコードを仕込んでrake update_po。msgidとmsgstrを含んだ配列(=抽出後)が流れる。ちがう、この前だ。
generate_potに渡す配列を作っているparseがあやしい。名前もまさにそのものだし。

165 >   @ex_parsers.each do |klass|
166 >     if klass.target?(file)
167 >       ary = klass.parse(file, ary)
168 >       break
169 >     end
170 >   end

target?で判断してパーサに委譲してるのか。パーサはどこだ?

 31     @ex_parsers = []
32     [
33       ["glade.rb", "GladeParser"],
34       ["erb.rb", "ErbParser"],
35       ["active_record.rb", "ActiveRecordParser"],
36 #      ["ripper.rb", "RipperParser"],
37       ["ruby.rb", "RubyParser"] # Default parser.
38     ].each do |f, klass|
39       begin
40 > require "gettext/parser/#{f}"
41 > @ex_parsers << GetText.const_get(klass)
42       rescue
43 > $stderr.puts _("'%{klass}' is ignored.") % {:klass => klass}
44 > $stderr.puts $! if $DEBUG
45       end
46     end

この内のどれか、というか拾われなかったのがapplication_helper「.rb」の中なのでたぶんRubyParserの中。でもせっかくなのでErbParserも見学することに。
lib/gettext/parser/erb.rb

 38     def target?(file) # :nodoc:
39       @config[:extnames].each do |v|
40 > return true if File.extname(file) == v
41       end
42       false
43     end

なるほど

 17     @config = {
18       :extnames => ['.rhtml']
19     }

たしかに「.rhtml」しか見ないことになっている。そして本命のRubyParserへ。
lib/gettext/parser/ruby.rb

138     def target?(file)  # :nodoc:
139       true # always true, as default parser.
140     end

殿は任せろ的な姿勢が頼もしい。いざ

 68     def parse(file, targets = [])  # :nodoc:
69       lines = IO.readlines(file)
70       parse_lines(file, lines, targets)
71     end

まだ先か。

 84       begin
85 > rl.parse do |tk|
86 require 'pp'; pp tk
87 >   case tk
88 >   when RubyToken::TkIDENTIFIER, RubyToken::TkCONSTANT

なんだかグルグル回してるところを見つけたのでまたベタなコードをはさむ。ちなみにrlはこれ。

 74       file = StringIO.new(lines.join + "\n")
75       rl = RubyLexX.new
76       rl.set_input(file)
77       rl.skip_space = true
78       #rl.readed_auto_clean_up = true

そしてrake update_po > foobar.txtでこんなのが出てきた。核心きたぁー
foobar.txt

 1909 #<RubyToken::TkASSIGN:0xb6daed6c @char_no=18, @line_no=44, @seek=1430>
1910 #<RubyToken::TkIDENTIFIER:0xb6da87b4
1911  @char_no=20,
1912  @line_no=44,
1913  @name="_",
1914  @seek=1432>
1915 #<RubyToken::TkLPAREN:0xb6da1fe0 @char_no=22, @line_no=44, @seek=1434>
1916 #<RubyToken::TkSTRING:0xb6d9aec0
1917  @char_no=23,
1918  @line_no=44,
1919  @seek=1435,
1920  @value="You have been logged out.">
1921 #<RubyToken::TkRPAREN:0xb6d94570 @char_no=50, @line_no=44, @seek=1462>
1922 #<RubyToken::TkNL:0xb6d8e1c0 @char_no=51, @line_no=44, @seek=1463>

これがさっきのcaseをグルグルと通っていたわけだ。

 88 >   when RubyToken::TkIDENTIFIER, RubyToken::TkCONSTANT
89 >     if ID.include?(tk.name)
90 >       target = :normal
91 >     elsif PLURAL_ID.include?(tk.name)
92 >       target = :plural
93 >     else
94 >       target = nil
95 >     end

こんなことをしてるので、ここが起点になるみたいだ。ちなみにIDとPLURAL_IDは

 64     ID = ['gettext', '_', 'N_', 'sgettext', 's_']
65     PLURAL_ID = ['ngettext', 'n_', 'Nn_', 'ns_', 'nsgettext']

うん、間違いない。さっきのfoobar.txtに照らして追ってみると

  1. RubyToken::TkIDENTIFIERまたはRubyToken::TkCONSTANTを見つけて
  2. nameがIDかPLURAL_IDに含まれていれば
  3. つぎに現れるRubyToken::TkSTRINGのvalueをmsgidとして取り出す

ということになる。
出現順というだけでTkIDENTIFIERやTkCONSTANTとTkSTRINGの間には何ら関連がないことに注目。

・・・つづく。

[ref.] GetText.update_pofilesでハマる。解答編

タイトルとURLをコピーしました