Ruby言語やLinuxのネタが多いです。
September 03, 2006 [おもひで]
■ [tDiary] amazon.rb入れ替え
9月からAmazonの何やらの仕様が変わり、昔のプラグインのままだとうまくAmazonの商品ページに飛んでくれないとのことなので、amazon.rbとja/amazon.rbを入れ替えた。amazonのキャッシュデータを削除して再度動かしたところ、無事に動いている模様。
September 05, 2006 [おもひで]
■ [Ruby] Module.nesting
例えば、Foo::Bar::Baz というモジュールがあった場合に、[Foo::Bar::Baz, Foo::Bar, Foo]という感じに外へ外へと返してくれるメソッドはないかなぁ、と思ってリファレンスマニュアルを探したら、Module.nestingを発見。そういえば、そんなメソッドもあったなぁ、と思って試してみた・・・。んだけど、2番目の戻り値が想定外だった。同じじゃないのか。ふーむ。
$ cat test.rb
module Foo
module Bar
module Baz
p Module.nesting
end
end
end
module Foo::Bar::Baz
p Module.nesting
end
$ ruby test.rb
[Foo::Bar::Baz, Foo::Bar, Foo]
[Foo::Bar::Baz]
$ ruby -v
ruby 1.8.4 (2005-12-24) [i686-linux]
■ [Ruby] Module.nesting(2)
injectを使って、自前で実装してみた。split(/::/)とせずにREG_NESTに代入して使い回すのは高速化の第一歩だよねー(後で追記:これ、うそ。下の方で検証結果あり)。
$ cat test.rb
REG_NEST = /::/
def nesting(klass)
klass.name.split(REG_NEST).inject([]) {|ret, v|
if ret.size == 0
ret << eval(v)
else
ret << eval("#{ret.last}::#{v}")
end
}.reverse
end
module Foo
module Bar
module Baz; end
end
end
require 'benchmark'
Benchmark.bm(9){ |x|
x.report("nesting"){
10000.times{
nesting(Foo::Bar::Baz)
nesting(Foo::Bar)
nesting(Foo)
}
}
}
$ ruby test.rb
user system total real
nesting 1.260000 0.220000 1.480000 ( 1.520556)
$ cat test.rb
REG_NEST = /::/
def nesting(klass)
ret = []
klass.name.split(REG_NEST).each do |v|
ret << ((ret.size == 0) ? eval(v) : ret.last.const_get(v))
end
ret.reverse
end
module Foo
module Bar
module Baz; end
end
end
require 'benchmark'
Benchmark.bm(9){ |x|
x.report("nesting"){
10000.times{
nesting(Foo::Bar::Baz)
nesting(Foo::Bar)
nesting(Foo)
}
}
}
$ ruby test.rb
user system total real
nesting 0.730000 0.160000 0.890000 ( 0.939074)
$ cat test.rb
REG_NEST = /::/
def nesting(klass)
ret = []
ary = klass.name.split(REG_NEST)
while(v = ary.shift)
ret.unshift(((ret.size == 0) ? eval(v) : ret[0].const_get(v)))
end
ret
end
module Foo
module Bar
module Baz; end
end
end
require 'benchmark'
Benchmark.bm(9){ |x|
x.report("nesting"){
10000.times{
nesting(Foo::Bar::Baz)
nesting(Foo::Bar)
nesting(Foo)
}
}
}
$ ruby test.rb
user system total real
nesting6 0.680000 0.070000 0.750000 ( 0.773468)
■ もっと速い方法ある?
■ [Ruby] 正規表現オブジェクトを定数で持つと高速化になる?
「split(/::/)とせずにREG_NESTに代入して使い回すのは高速化の第一歩」って書いたけど、これはホントか?と思ってちょっとやってみたらほとんど関係なかった。遅くなるときすらある。そういうものか。
$ cat test3.rb
require 'benchmark'
REGEXP = /::/
Benchmark.bm(8){ |x|
x.report("regexp1"){100000.times{"ABC::DEF".split(/::/)}}
x.report("regexp2"){100000.times{"ABC::DEF".split(REGEXP)}}
}
$ ruby test3.rb
user system total real
regexp1 0.690000 0.070000 0.760000 ( 0.790722)
regexp2 0.710000 0.070000 0.780000 ( 0.805602)
これは勉強になった。
$ cat test.rb
def nesting(klass)
ret = []
ary = klass.name.split(/::/)
while(v = ary.shift)
ret.unshift(((ret.size == 0) ? eval(v) : ret[0].const_get(v)))
end
ret
end
module Foo
module Bar
module Baz; end
end
end
require 'benchmark'
Benchmark.bm(9){ |x|
x.report("nesting"){
10000.times{
nesting(Foo::Bar::Baz)
nesting(Foo::Bar)
nesting(Foo)
}
}
}
$ ruby test.rb
user system total real
nesting 0.650000 0.070000 0.720000 ( 0.746397)
お、微妙に速い。
■ [Ruby] if文と三項演算子はどっちが速い?
こちらも心配になったので計ってみた。Ruby的には条件演算子と言うべきなのかな。
$ cat test4.rb
require 'benchmark'
Benchmark.bm(3){ |x|
x.report("if"){100000.times{if true; a = 1 else; a = 2;end}}
x.report("?:"){100000.times{a = true ? 1 : 2}}
}
$ ruby test4.rb
user system total real
if 0.090000 0.060000 0.150000 ( 0.166605)
?: 0.060000 0.090000 0.150000 ( 0.163028)
ちょっと三項演算子の方が速いかな。何回かやるとifの方が速くなる場合もあるけど、平均取ると三項演算子の方が速いっぽい。
$ cat test5.rb
require 'benchmark'
Benchmark.bm(8){ |x|
x.report("each"){a = []; 10000.times{(0..100).each {|v| a << v}}}
x.report("inject"){10000.times{(0..100).inject([]){|a, v| a << v}}}
}
$ ruby test5.rb
user system total real
each 1.380000 1.180000 2.560000 ( 2.593192)
inject 11.740000 1.820000 13.560000 ( 13.631210)
September 06, 2006 [おもひで]
■ [Ruby] 正規表現オブジェクトを定数で持つと高速化になる?(2)
昨日の結果がちょっと信じられなかったので、ひょっとしたら長い正規表現オブジェクトであればインスタンス生成に時間がかかるかもしれない、と思い、長めの正規表現で再度チャレンジしてみた。
$ cat test.rb
require 'benchmark'
str = "http://www.google.com/"
REGEXP = /^http:\/\/.*(72.14|64.233|66.102|216.239|google).*q=cache:([^:]*):(.*?)(\s|\\+)([^&]*).*/
Benchmark.bm(8){ |x|
x.report("regexp"){1000000.times{/^http:\/\/.*(72.14|64.233|66.102|216.239|google).*q=cache:([^:]*):(.*?)(\s|\\+)([^&]*).*/ =~ str}}
x.report("regexp2"){1000000.times{REGEXP =~ str}}
}
$ ruby test.rb
user system total real
regexp 0.910000 0.640000 1.550000 ( 1.589382)
regexp2 1.180000 0.610000 1.790000 ( 1.812263)
あり!?差が広がったぞ。なんだかインタプリタ内でうまいことチューニングされてるってことなのかな・・・。
September 10, 2006 [おもひで]
■ [Ruby] rmsgmergeとmsgmerge
日経ソフトウェア10月号のRuby on Railsの紹介記事でRuby-GetText-Packageを1ページにまるっと使って紹介して頂いています。ありがとうございました。
1点、"GNU gettextが無い場合はrmsgmergeを使う"という部分がありましたが、ちょっと補足しておきたいと思います。
rmsgmergeはGNU gettextに含まれるツールの1つであるmsgmergeのRuby実装版で、古いpoファイルと新しいpoファイルを(単純にではなくいろいろと器用に)マージしてくれるというものです。
あまり私が紹介したことのないrmsgmergeを取り上げて頂いたのはうれしいような気もしないでもないですが、実は今まで紹介しなかった(あるいはmsgmergeを呼び出しているGetText.update_pofilesメソッドの中でrmsgmergeを代わりに呼び出していない)のには、2つ理由があります。
まず1点目としては、rmsgmergeはmsgmergeの持つ非常に重要な機能であるfuzzyマージ機能をサポートできていないことです。これは、「似たようなmsgidにすでに翻訳文字列があった場合、それをとりあえず翻訳文字列としてセットした上でfuzzyマークを付けてくれる」というものです。すでにupdatepoタスクを繰り返された方は"fuzzy"とついたmsgid/msgstrを見て便利だなぁ、と感じて頂けたのではないかと思います。
2点目は、一般に開発に使われるOSでmsgmerge(GNU gettext)をインストールするのはそんなに大変ではない、ということです。多少のディスクスペースは取りますが、fuzzyがサポートされる利点を考えるとインストールすべきです。
Linux等のフリーUNIX系ではおそらく最初から入っているでしょうし、入っていない場合でも追加パッケージにあると思います。MS Windowsの場合は、Glade/GTK+ for WindowsにあるDevelopment Environmentを使うと良いと思います。これはインストーラ付きでパスなども自動で設定してくれるので便利ですよ。
#ついでにruby-gtk2のバイナリパッケージをインストールするとMS WindowsでRuby/GTK2が使えるようにあるという特典付きです;p。
繰り返しになりますが、"fuzzy"マージ機能という便利な機能を使うために、GNU gettextをインストールすることを強く推奨しますが、もし、どうしてもGNU gettextがインストールできない、ということであれば、紹介されているようにrmsgmergeを使うこともできます。その場合は環境変数のMSGMERGE_PATHにrmsgmergeコマンドのパスを指定します。
ホントはrmsgmergeがmsgmergeと同等の機能になればまさにGNU gettextが不要になるんですけどねー。あ、msginitは必要か。ボクの中での優先度は高くないです。誰か、rmsgmergeにfuzzyマージ機能を追加してくれるって人いませんか?
■ [Rails] rails edgeでRuby-GetTextが動かない件
こういうことのようでした。助かったー。次のリリースに間に合ったよ。
それにしても、append_featuresってdeprecatedなの!?
September 13, 2006 [おもひで]
■ [Ruby] rails edgeでRuby-GetTextが動かない件(3)
ActiveRecordでsave!をするとSystemStackErrorするというレポートが。うーん、確かに再現する・・・。どうもvalidates_系のメソッドが変な風にループしている模様。
ということで、やっぱりまだ動かないみたいです・・・。しくしく。
■ [Ruby] Ruby-GetText-Package-1.8.0の変更点
気を取り直して、Ruby-GetText-1.8.0の変更点を紹介します。長文失礼。
#後で見直して保管庫に移す予定。
(一般)(Rails)に分けて記述します。(Rails)がRails向け、(一般)は一般的なものです。
■ (1) GetText.bindtextdomain_to(klass, domain) (一般)
例えば、RailsでActiveRecordを使わないんだけど便利なモデルクラス(FooModel)、というのがプラグインで提供されていたとします。プラグインの方には手を加えずにFooModelを継承する全てのクラスで"myapp"ドメインを適用したい場合は以下のようにします。
app/controllers/application.rb class ApplicationController < ActionController::Base bindtextdomain_to(FooModel, "myapp") init_gettext "myapp" end
app/models/user.rb
class User < FooModel
def foo
_("user name")
end
end
これの利点は、各サブクラス内でGetText向けの設定(include GetTextとか、bindtextdomain)を一切せずにGetTextのメソッドを使えることです。
後はupdatepoタスクでこのモデルを指定すると(通常、ワイルドカードでModel配下を指定していると思いますので意識する必要は無いかも)"user name"がmsgidとなってmyapp.poに反映されるのでそれを翻訳します。
■ (2) init_gettextで指定したドメイン名をActiveRecord::Validationsにバインドするようになった (Rails)
これは(1)に似ているのですが、例えば、ActiveFormはActiveRecord::Validationsをincludeしているため、そのままmyappドメインがバインドされることになります。したがって、特に何の指定もせずに以下のようにGetTextのメソッドが使えるようになります。
class Search < ActiveForm
attr_accessor :foo
N_("Search|Foo")
validates_length_of :foo, :minimum => 10
end
ActiveForm以外でもActiveRecord::Validationsをincludeしているプラグイン等は同様に扱うことができます。なお、この機能を単体で利用することを想定して、ActiveRecord周りのコードをgettext/rails.rbからgettext/active_record.rbに移しました。
■ (3) init_gettextでのロケール指定パスの変更 (Rails)
今まで、init_gettextはデフォルトで{RAILS_ROOT}/locale/をロケールパスとしていたのですが、init_gettextをfoo/app/controllers/bar_controller.rbの中で呼び出している場合、foo/localeがロケールパスになるようになりました。
これは主にRails Enginesで使用することを想定しています。例えばUserEnginesでは以下のようなファイル構成になります。
{RAILS_ROOT}/vendor/plugins/login_engine
+app/controllers/user_controller.rb
+po/
+locale/
+Rakefile
+その他
上記のuser_controller.rbは以下のように書きます。
class UserController < ApplicationController
init_gettext "login_engine"
:
:
def home
:
:
@fullname = _("Not logged in...")
Rakefileはちょっとだけ工夫が必要です。
desc "Create mo-files for L10n"
task :makemo do
GetText.create_mofiles(true, "po", "locale")
end
$: << "../../../"
GetText::ActiveRecordParser.init(:db_yml => "../../../config/database.yml")
desc "Update pot/po files to match new version."
task :updatepo do
GetText.update_pofiles("login_engine", Dir.glob("{app,lib}/**/*.{rb,rhtml}"),
"login_engine x.x.x")
end
UserEnginesではActiveRecordを使うのですが、database.ymlはアプリケーションと同じモノを使うため、それを指定する必要があります。ここはもうちょっと改善の余地があるかな。
いずれにせよ、上記のようにEnginesでは、通常のRailsアプリケーションとほとんど同様にGetText化することができます。
なお、上記を元にしたUserEnginesはもうちょっとモディファイしてEnginesのメーリングリストにポストする予定です。実はUserEnginesはローカライズ要望が多く、私のところにどう実装すれば良いの?という質問が結構きています。いちいち回答するのが面倒くさくなってきているので、今後はこれを参考にして、と言えて楽になるかなぁとちょっと思ってます。
■ (4) beforge/after_init_gettextの追加 (Rails)
before/after_filterでも大丈夫な場合が多いのですが、こちらを使うとユーザのリクエスト毎に発生するGetTextの初期化処理(ロケールの取得と使用するTextdomainの選択)を行う前後に処理を追加することができます。
例えば、QUERY_STRINGの"foo"パラメータにロケールが指定されている場合はそれを使い、指定されていない場合は日本語を使う、というような場合は以下のようにします。
class ApplicationController < ActionController::Base
before_init_gettext :foo
def foo
GetText.locale = cgi.params["foo"] ? "ja" : cgi.params["foo"]
end
init_gettext "myapp"
DBアクセスやセッションにあるユーザ情報から、使用するロケールを取得する、というような場合にこの機能を使うことができると思います。
after_init_gettextはロケールが決まった後に呼び出されます。他のローカライズライブラリを併用するときにそのライブラリにロケール情報を渡す時に使うことを想定しています。
class ApplicationController < ActionController::Base
after_init_gettext :ya_l10n
def ya_l10n
YetAnotherL10n.set_locale(GetText.locale)
end
init_gettext "myapp"
■ (5) オプションパーサを指定することができるようになった (一般)
poファイルでメッセージを抽出するためのパーサを独自に追加できるようになりました。パーサモジュールの書き方ですが、まず、target?, parseという2つのメソッドを実装し、パーサをGetText::RGetTextに登録する、という感じです。こんな感じ。これをRakefileで使う場合は、require 'testparser'としてからupdatepoタスクを呼び出せば良いでしょう。
■ (6) rgettextに-d, -rオプションを追加した
-dはデバッグオプション、-rで先読みしたいライブラリを指定します。
前述のオプションパーサのメールでも触れていますが、-rは主にオプションパーサを指定することを想定しています。
$ rgettext -r testparser foo.rb -o foo.pot
September 14, 2006 [おもひで]
■ [Ruby] Ruby-GetTextを使うとrake でRDocタスクが失敗する
と質問がありました。これは以下のようにすると回避できます。
desc "Update pot/po files."
task :updatepo do
require 'gettext/utils' #Here!
GetText.update_pofiles("blog", Dir.glob("{app,lib,bin}/**/*.{rb,rhtml}"), "blog 1.0.0")
end
desc "Create mo-files"
task :makemo do
require 'gettext/utils' #Here!
GetText.create_mofiles(true, "po", "locale")
end
つまり、'gettext/utils'をupdatepo/makemoタスクのいずれかでしか動かさない、というものです。
■ これ、Ruby-GetText側で何らかの対応しようと思ったんですけど、やめました。
調べてるときに気づいたのですが・・・RDocの実装、ひどすぎです。今回の問題はRubyのソースに含まれるrdoc/parsers/parse_rb.rbにあると断言します。なぜって、これ、irbに含まれるライブラリのコピペですYO!
せめてサブクラス作ってオーバライドするなりすれば良いのに、なぜ、思いっきりコピペなんだ・・・・。
irbのパーサを使っているGetTextをrdocタスクと一緒にrequireすると、どちらか片方が定数値の重複定義エラーになるわけです。
以下のようにするだけで簡単に再現します。
% irb
irb(main):001:0> require 'rdoc/rdoc'
/usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:31: warning: already initialized constant EXPR_BEG
/usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:32: warning: already initialized constant EXPR_MID
/usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:33: warning: already initialized constant EXPR_END
/usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:34: warning: already initialized constant EXPR_ARG
/usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:35: warning: already initialized constant EXPR_FNAME
/usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:36: warning: already initialized constant EXPR_DOT
/usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:37: warning: already initialized constant EXPR_CLASS
/usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:262: warning: already initialized constant TokenDefinitions
/usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:267: warning: already initialized constant TkReading2Token
/usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:268: warning: already initialized constant TkSymbol2Token
NameError: uninitialized constant RubyToken::AlreadyDefinedToken
from /usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:273:in `def_token'
from /usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:300
from /usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:299:in `each'
from /usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:299
from /usr/lib/ruby/1.8/rdoc/rdoc.rb:8:in `require'
from /usr/lib/ruby/1.8/rdoc/rdoc.rb:8
from (irb):2:in `require'
from (irb):2
from /usr/lib/ruby/1.8/rdoc/parsers/parse_rb.rb:1275
irb(main):002:0>
irbのパーサ使わずにripper使うのが王道、ってことなのかもしれないけど、せめて、標準ライブラリは同時に全てを呼び出してもバッティングしない(まぁ、どうしても避けられないパターンはあるかもしれないけど)ようにすべきだと思うんだけどな・・・。
■ ひとまず、Ruby-GetTextとしては、タスク毎にrequire 'gettext/utils'することを推奨としますか・・・。ちょっと釈然としないけど。
September 17, 2006 [おもひで]
■ [Rails] Ruby-GetText-Package HOWTO for Rails Engines
Rails Engines向けのHOWTOを書いてみました。英語ですが読んでやってください。
一番下にLoginEngine v1.0.2をローカライズしたものを固めて添付しておきました。
September 25, 2006 [おもひで]
■ [Rails] rails edgeでRuby-GetTextが動かない件(4)
今度こそ直ったと思います。もし、edgeでRuby-GetTextを使いたい方がいらっしゃいましたら、CVS版をトライしてみてください。
#lib/gettext/active_record.rbだけ入れ替えれば動くと思います。
#コンパイル等が面倒くさい方は1.8.0のgemをインストールして上書きしてください。
September 29, 2006 [おもひで]
■ [Misc] Googleマップ
新しいGoogleマップすごいなー、と思ってたんだけど、日本向けのローカライゼーションを相当がんばってくれたからなのね。なんだか感謝の気持ちなのです。
September 30, 2006 [おもひで]
■ [Misc] BetaBrite
RedHanded経由。このライブラリはBetaBriteというLEDパネルを操作するものみたいなんだけど、DRbを使った例が面白い。
まずはgemでbetabriteをダウンロードする。んでもって上記Blogにもあるサンプルを実行。送信するメッセージだけYotabananaに。
require 'drb'
require 'rubygems'
require 'betabrite'
DRb.start_service()
obj = DRbObject.new(nil, "druby://eviladmins.org:9000")
File.open("out.jpg", "wb") { |a|
a.write obj.write_simple("Yotabanana")
}
んで、右の画像が結果。
なんと、メッセージを作者のAaron Patterson氏が持つサーバに送るとそれを彼のBetaBriteに写しだし、webcamで写真を撮って送り返してくれるのだ!
おもしろーい。
P.S. これ、時間帯によって背景の明るさとか違うのかなー。
P.S.2. 日本語はダメでした。当たり前か<ってか試すなよ(苦笑)。

▲ 咳 [おもしろーい。サービスはリモートにあるので、require 'drb'だけで大丈夫ですよ。この写真の暗さ、良いですね..]
▲ むとう [require 'betabrite'がいらないということですね。 なるほどー。サーバにライブラリがあればクライアン..]