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に照らして追ってみると
- RubyToken::TkIDENTIFIERまたはRubyToken::TkCONSTANTを見つけて
- nameがIDかPLURAL_IDに含まれていれば
- つぎに現れるRubyToken::TkSTRINGのvalueをmsgidとして取り出す
ということになる。
出現順というだけでTkIDENTIFIERやTkCONSTANTとTkSTRINGの間には何ら関連がないことに注目。
・・・つづく。