自分の興味の赴くままにIT技術系のネタを取りとめもなくメモっています。
Ruby言語やLinuxのネタが多いです。

September 02, 2006 [長年日記]

[Misc] リファラスパム

久しぶりに大がかりなヤツをやられた。うーん、いい加減にしてくれ・・・。


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)

[Ruby] Module.nesting(3)

いろいろ試してみた。injectよりeachの方が、evalよりもconst_getの方が、if文より3項演算子が速いみたい。これが最速?

$ 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)

[Ruby] Module.nesting(4)

eachよりwhileの方が速いか? reverseをしないようにしてみたらさらに高速化できた。

$ 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)

これは勉強になった。

[Ruby] Module.nesting(5)

上の結果を受けてREG_NEST定数を使わずに埋め込みでやってみた。

$ 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の方が速くなる場合もあるけど、平均取ると三項演算子の方が速いっぽい。

[Ruby] eachとinjectどっちが速い?

おまけ。ちょっと差がありすぎるな。使い方あってるよね?ちょっと心配だ・・・(苦笑)。

$ 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 12, 2006 [長年日記]

[Ruby] Ruby-GetText-Package-1.8.0

リリースしました。今回もいろいろと手を入れてます。

高速化したのでそれだけでも試してみる価値ありですよん。


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 21, 2006 [長年日記]

[Misc] Google Adsenseの広告につい反応してしまうオレ

なんだか転職でもしちゃおっかなー、と思う今日この頃。

Google AdsenseのRails周りの求人広告がオレを誘ってくるのでついクリックしてしまう。

考えてみれば「そのブログに関連しそうな広告を表示する」というのは、つまり「そのブログの作者が興味持つことを表示する」わけだ。

とすると、ブログの広告のターゲットは、Visitorではなく作者自身ってことか。まぁ、主要なターゲットの一人ではあるな・・・。


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")
}
BetaBrite

んで、右の画像が結果。
なんと、メッセージを作者のAaron Patterson氏が持つサーバに送るとそれを彼のBetaBriteに写しだし、webcamで写真を撮って送り返してくれるのだ!
おもしろーい。

P.S. これ、時間帯によって背景の明るさとか違うのかなー。

P.S.2. 日本語はダメでした。当たり前か<ってか試すなよ(苦笑)。

本日のツッコミ(全2件) [ツッコミを入れる]

 [おもしろーい。サービスはリモートにあるので、require 'drb'だけで大丈夫ですよ。この写真の暗さ、良いですね..]

むとう [require 'betabrite'がいらないということですね。 なるほどー。サーバにライブラリがあればクライアン..]