おんがえしの blog

作ったプログラムと調べた技術情報

mrubyソースコード検索を作りました

f:id:tuto0621:20130929123940p:plain

http://mrubysearch.ongaeshi.me/

RubyKokubanを作るにあたってmrubyのソースやmgem(mrubyのRubyGemsみたいなやつ)に登録されたソースコードを簡単に読めるようにしたいなあ、と思い作ってみました。

一日一回レポジトリを最新に更新してインデックスの再構築を行っています。新しいmgemが追加された時は今の所手動で対応しています(そのうち自動化したい所です)。

自分が追加した(もしくは検索したい)mgemが追加されていない!って人はリクエスト頂ければ追加するので教えて下さい。 mgemじゃなくてもmrubyに関係がありそうなソースコードでしたら追加しますので是非。

※ 個人サイトのため突然停止したりするかもしれません、ご了承下さい。

使い方の例1 - "!"付きのメソッドはどうやってバインドするの?

例えばgsub!のような!マークのついたメソッドはどうやってバインドするのか?を知りたいとします。mrubyでは メソッド名を文字列で渡す必要があるのでそこを手がかりにします。!"で検索してみましょう(文字列の終端に引っかかることを期待しています)。

!" in root -mruby search

f:id:tuto0621:20130929124240p:plain

少し下に行くとswapcase!関数をバインドしてる箇所が見つかりました。

mruby-string-ext/src/string.c#173

f:id:tuto0621:20130929124250p:plain

mrb_str_swapcase_bangという関数名でバインドされていますね。!は _bangという名前でバインドされているようです。

他のライブラリではどうなっているか調べてみます。

'_bang' in root - mruby search

  • reverse! -> mrb_ary_reverse_bang
  • capitalize! -> mrb_str_capitalize_bang

も_bangという名前になっているので、よく使われているルールだと思ってよさそうです。

使い方の例2 - Cの関数内からyieldしたいのだけど

ブロック構文を使いたくなったので調べることに。おそらくeachという名前のメソッドはブロック構文を使っているのでそれを探してみます。

'each mrb_define_' in root - mruby search

eachだけでも探せるけどより検索精度を上げるためにmrb_define_キーワードを重ねています。いくつか関数を探してmrb_leapmotion_devicelist_each関数を詳しく読む事に決めました。

mruby-leapmotion/src/Leap.cpp#1067

static mrb_value
mrb_leapmotion_devicelist_each(mrb_state *mrb, mrb_value self)
{
  mrb_leapmotion_devicelist_t *data =
    static_cast<mrb_leapmotion_devicelist_t*>(mrb_data_get_ptr(mrb, self, &mrb_leapmotion_devicelist_type));
  if (data == NULL) {
    return mrb_nil_value();
  }
  mrb_value block;
  mrb_get_args(mrb, "&", &block);
  std::for_each (data->obj->begin(), data->obj->end(),
    mrb_each_func_t<Leap::DeviceList::const_iterator::value_type>(mrb, block));
  return self;
}

mrb_get_args_でブロックらしきものを"&"で取っているので間違いなさそうです。C++のコードなのでSTLを使っています。mrb_each_func_tから始まるテンプレート関数が処理の本体のようです。mrb_each_func_tは同じファイル内にあります。

template <typename T> struct mrb_each_func_t {
private:
  mrb_state *mrb_;
  mrb_value &block_;

public:
  mrb_each_func_t(mrb_state *mrb, mrb_value &block) : mrb_(mrb), block_(block) {}
  ~mrb_each_func_t() {}

  mrb_value operator () (T const &elem) const {
    return mrb_yield(mrb_, block_, mrb_leapmotion_obj_make(mrb_, elem));
  }
};

operator ()内にある mrb_yield がブロックを呼び出す関数のようです!長い道のりでしたが、

  1. mrb_get_args(mrb, "&", &block); でブロックを受け取る
  2. mrb_yield(mrb, block, value) で受け取ったブロックを実行

すればよいことがmurbyソースコード検索を使って分かりました!

その他の基本的な使い方はヘルプをどうぞ。

内部の話

構成

groongaがストレージエンジンも兼ねるのでMySQL等は使っていません。

groongaのCentOSへのインストール

先にgroonga-develをインストールしておくとrroongaのインストールが短く終わります。

 $ yum install groonga-devel -y
 $ gem install rroonga

Milkodeのサーバーへのセットアップ

milkode-webを使いました、bundlerで管理出来るのでとっても便利です。

milkode-web - Milkode web application bundler template for apache passenger https://github.com/y-ken/milkode-web

くわしくはgroonga(rroonga)を利用したソースコード全文検索エンジン"Milkode"をApache Passengerで軽快に動かす方法をどうぞ。

レポジトリ一覧の取得

mrubyのレポジトリはGitHubから追加します。

$ milk add  https://github.com/mruby/mruby

mgemのレポジトリ一覧は以下のようなスクリプトを使って取得しています。

# Create mgem repository list

require 'yaml'

mgem_dir = File.join(ENV['HOME'], '.mgem')
mgem_list = Dir.glob(File.join(mgem_dir, 'mgem-list/*.gem'))

repositories = mgem_list.map do |mgem|
  YAML.load(File.read(mgem))['repository']
end

repositories.each do |repo|
    puts "milk add #{repo} -p git"
end

コマンド文字列が生成されるので後はコピペするなりシェルスクリプトにまとめてサーバー上で実行します。

$ ruby mklist.rb 
milk add https://github.com/schmurfy/host-stats.git -p git
milk add https://github.com/cremno/mruby-allegro.git -p git
milk add https://github.com/ppibburr/mruby-allocate.git -p git
.
.