書いた人: おんがえし (@ongaeshi)
はじめまして、おんがえしと申します。
この記事では私の作っている Milkode という Ruby で書かれたソースコード検索エンジンの作り方について紹介します。
Milkode の内部で使われているたくさんの gem の紹介と、そのカスタマイズ方法を中心に進めます。Ruby で一定以上の規模のアプリケーションやライブラリを作るのであれば、RubyGems を使いこなすことは、とても大切なトピックの一つではないでしょうか。
具体的には、アプリケーション内部で
を紹介していきます。みなさんが何かを作る時の一助になれば幸いです。
知らない人も多いと思いますので、簡単に紹介します。
Milkode は Ruby で書かれた行指向のソースコード検索エンジンと検索アプリです。数万オーダーのファイルから、目的のキーワードを含む 1 行を瞬時に検索することが可能です。私の手元のマシンでは 15 万ファイルほど登録しても 1〜2 秒以内で検索できます。
さらに詳しい使い方については以下のホームページや紹介記事をご覧ください。
記事内でもサンプルコードを交えながら紹介していきますが、 Milkode のソースコードを事前に Milkode に登録して (ややこしいです)、検索しながら読むとさらに理解が深まるのでおすすめです。
Milkode のインストールについては Milkode - ダウンロード を参考にしてください。
インストールしてデータベースの作成が終わりましたらソースコードを登録します。 Git レポジトリから直接ソースコードを登録することが可能です。
もしくは milkode - Kodeworld をどうぞ。
国産の全文検索エンジン Groonga を Ruby から使えるようにしたものです。Groonga データベースをストレージ&検索エンジンとして利用しています。心臓部分ですね。
色々な人に Milkode を紹介するとその検索速度の速さに驚いてもらえることが多いのですが、それは Groonga の検索性能による所がとても大きいです。
Rroonga 自体がカラムストアエンジンとして使えるので MySQL などのストレージエンジンを別途インストールする必要がないのも Rroonga を採用した理由のひとつです。 Milkode は gem でインストールするアプリケーションなので、他にインストーラーを立ち上げるようなものが増えると、どんどん敷居が上がってしまうので……。
Rroonga で検索するには事前にテーブルを定義しておく必要があります。以下は Milkode が内部で定義しているテーブルです1。ひとつのファイルを 1 レコードとして登録しています。全文検索時に転置インデックス格納用のテーブル (terms) を作る必要があるのは少し複雑ですが、それ以外は素直ではないかと思います。
/milkode/lib/milkode/database/document_table.rb:15
検索の時は select メソッドを使います。 10 万〜100 万位のレコード数でも一瞬で検索することができます。
/milkode/lib/milkode/database/document_table.rb:174
ちなみに、私自身が Rroonga をもっと簡単に使えるようにするために、GrnMini というライブラリも開発しています。カラム指定不要でデータを追加できたり、転置インデックス用のテーブルを自動で作成してくれたりします。この記事を読んで Rroonga に興味を持ってくれた方は、GrnMini のことも覚えておいてくれたら幸いです。以上宣伝でした。
サブコマンド付きのコンソールアプリを簡単に書ける gem です。以下のコードで add, update, remove の 3 つのサブコマンドを持ったアプリケーションを作成することができます。
@tomykaira さんの Pull Request #27 で実現されました。それ以前は Ruby 標準の OptionParser をむりやりハックして使っていたのですが、アプリケーションの規模が大きくなってきて OptionParser でサブコマンドを書くのが大変になっていたので大変助かりました。
thor には一点だけ不満があって、デフォルトのヘルプコマンドが
のように “コマンド help サブコマンド” の形式しか受け付けてくれないのです。私はサブコマンドのヘルプを見たい時は “milk add (ここでヘルプを見たくなる)” となることが多いので、”コマンド サブコマンド -h” の形式も受け付けるようにカスタマイズしています。
/milkode/lib/milkode/cli.rb:10
Milkode の Web アプリ部分を構成する gem です。Rails を使わない選択肢としては割とオーソドックスな方ではないかと思います。 Sinatra は README の分かりやすさで使うことを決めました。 Haml は若干書き方に癖があるのですが、慣れると erb より少ない記述量でさくさく HTML が書けるので好きです。
Sass を使うようになったのは割と最近で、相対 URL 対応のために使っています。 (それまでは生の CSS を使っていました)
/milkode/lib/milkode/cdweb/views/milkode.scss:266
背景画像のパスを相対 URL に合わせて変更しています。
‘milk web’ を実行した時に自動でブラウザを開くために使っています。 OS や使っているブラウザに依存せずに Web ページを開くことができるので便利です。以下で ‘http://localhost:9292/’ を開きます。
これで簡単に、といきたい所ですが現実はなかなかうまくいきません。 Web アプリケーションを Rack で立ち上げた後に、その URL を Launchy で起動することを考えてみます。
例えば以下のようなコードになります。
起動してみると、何かおかしいです。
仕方なく停止すると……
Web アプリ停止後にブラウザが起動してきます。困りましたね。 ‘rack_server.start’ すると Web サーバーを停止するまで次の行にいかないことが原因のようです。
そこで Milkode では、’Rack::Server#start’ にブロックを渡して、その中で Launchy を起動するようにしています。
/milkode/lib/milkode/cdweb/cli_cdweb.rb:44
これで上手くいきました。何とかなるものですね。
ソースコードの色付けをしてくれる gem です。よいライブラリを見つけるのが結構大変でした。
最初は簡単に使えそうなものをいくつか試したのですが、ruby/doc/ChangeLog-1.8.0 のようにサイズの大きなファイルを開くと重くなったりして大変でした (このサイズだと CodeRay でも結構重いですが、もっと重かったのです)。結果として CodeRay に辿り着いたのですが検索でなかなか見つけることができずに苦労しました。 ‘ruby ソースコード 色付け’ とかで検索しても簡単に出てこないんですよね……。当時The Ruby Toolbox を知っていればもっと簡単に見つけることができたと思います。
CodeRay はよくできていて使いやすいのですが、例によってアプリの価値を高めていこうとするとデフォルトの機能でばっちり上手くいくことはなかなかなないものです。特に Milkode においてはソースコードの見やすさはユーザーさんの印象を決める大切な部分のため、しつこくカスタマイズを加えています。
マッチ行をハイライトする機能自体は CodeRay 自体に用意されているのですが、デフォルトだと左の行番号が赤くなるだけで若干分かりにくいです。以下は ‘define’ で検索した場合です。
そこでコードとスタイルシートに手を加えて、マッチ行全体が色付けされるようにしています。検索した時はマッチ行周辺を中心にコードを読むことが多くなるので、地味ですが大切な修正です。
やり方としては、まず CodeRay は ‘Encoder’ というクラスを継承することで様々な形式 (HTML, Text, JSON など) で出力できるようになっているのですが、その HTML クラスを継承した HTML2 クラスを作ってアウトプットを Milkode 側で乗っ取ります。
/milkode/lib/milkode/cdweb/lib/coderay_html2.rb:14
さらに CodeRay::Encoders::HTML2::finish の中で受け取った出力をそのまま ornament_line_attr 関数に渡します。
/milkode/lib/milkode/cdweb/lib/coderay_html2.rb:23
ornament_line_attr の中で行番号のカウントと各行ごとの処理を行います。line_attr 関数に続きます。
/milkode/lib/milkode/cdweb/lib/coderay_html2.rb:49
ここまで来たらあと一息です。差し替えた CodeRay::Encoders::HTML2#line_attr の中で highlight-line クラスを指定します。
/milkode/lib/milkode/cdweb/lib/coderay_html2.rb:65
highlight-line クラスは背景色を変更しているだけです。
/milkode/lib/milkode/cdweb/public/css/coderay-patch.css:2
……長い道のりでしたが2なんとかなりました。ここまでやってできたことといえば、検索行がハイライトされる “だけ” です3。が、やっぱり使う上では大切だったりするのです。
ぱっと見の簡単さ、実装難易度、そして実際の役立ち度には相関性がないことが時折あります。なので作っている最中も「こんなに大変なことわざわざやらなくていいんじゃない?」という誘惑に何度も負けそうになりました。
困った時は Milkode を使っている側に気持ちを切り替えて (私は Milkode のヘビーユーザーでもあるのです)、率直な感想をぶちまけてみます。「いやいやマッチ行が見にくかったら不便で困るよ、っていうか使わなくなっちゃうよ」と思い直して作業を続けていきました。根気よくソースコードを読んで、上手くいかないと時は仕切り直して作戦を練り直していけばその内なんとかなることが多いです。
“hogehoge.rb#n11” の “#n11” の部分のことです。
ソースコード検索では結果から目的行に直接ジャンプする必要があるのでこちらも地味ですが必須の機能です。幸い、背景色を設定したのと同じ個所で実装できました。
背景色の時は HTML タグにクラスを付加しましたが、アンカーの場合は id を付加します。
/milkode/lib/milkode/cdweb/lib/coderay_html2.rb:64
実は英語にも対応しています。
Ruby での国際化の知識がとぼしかったので実現には時間がかかるかな、と思っていたのですが、るびまの高橋編集長による Twitter からのプルリクエストのおかげで、一瞬で終わりました。ソーシャルコーディングってすごいです。
高機能な sinatra-r18n も試しましたが、そんなに複雑な翻訳箇所もなく、高橋さんから頂いたパッチが i18n で書かれていたので、そのまま i18n で進めました。
i18n の基本的な使い方としては、en.yml, ja.yml といったファイルを用意して……
/milkode/lib/milkode/cdweb/locales/ja.yml:1
/milkode/lib/milkode/cdweb/locales/en.yml:1
“t()” 関数で展開します。シンプルですね。
/milkode/lib/milkode/cdweb/views/index.haml:28
Ruby を使ってアプリケーションを作る際にどのように gem を利用しながら、またどのような個所をカスタマイズしていったのかを紹介しました。
RubyGems はとても大きなエコシステムなのでたくさんの gem が登録されています。逆に言うとその中から有用なものを探しだすのは少しコツがいるいうことです。大切なことは「今」制作物にとって何が必要かをできるだけはっきりさせる、ということです。
Web アプリを作りたい、zip ファイルを生成したい、画像を生成したい、pdf を表示したい、を自分の言葉で書き出しましょう。さらに具体化させられるなら (例えば画像生成だったら生成したいのは .png なのか、 .jpg なのか、それとも gif アニメなのか? など) できる限り影響範囲を圧縮させられると、より検索精度が向上します。
目的を上手く言語化できたら探し始めます。その際は、汎用的な検索エンジンに頼りすぎないようにしましょう。The Ruby Toolbox を探す、Twitter で独り言をつぶやく、周りの人に聞いてみる、など他の情報源も合わせてチェックするのがおすすめです。
お好みの gem を見つけることができたら必要に応じてラッパーを書いたりカスタマイズを施していきます。他の人が作った gem は自分のやりたいことを解決するために作られたわけではないので、デフォルトの機能のままで目的を達成できることは、むしろまれです。しかし他の gem と組み合わせたり、自分で糊の役割りをするコードを書くことで大体のことは達成できます。
ここでも大切なことは「何のためにその gem を使っているのか?」という目的意識です。目的意識が明解になっていればどのアプローチが最短距離かを冷静に判断できるはずです。
カスタマイズの際は必要に応じて gem のマニュアルやコードを読む必要がありますが、0 から作り、デバッグすることを考えれば、トータルの時間としては大幅に短縮することができるでしょう。また、他人の書いたライブラリを使ったりソースを読むことは異文化に触れることでもあります。異文化を理解して使いこなせるようになった時、大きなスキルアップを実感できるでしょう。一番大切なのは新しいことに挑戦する自分を好きになって楽しんでやることです!
長くなりましたがここまで読んで頂きありがとうございました。感想等ありましたら Twitter などで教えて頂けたら嬉しいです。それでは、またどこかで。
プログラマ。職業プログラマの傍らでオープンソースのソフトウェアを作る。最近作ったのは Milkode, GrnMini, RubyKokuban など。こつこつと何かを作りながら生きていければよいと思っている。Twitter: @ongaeshi ホームページ: http://ongaeshi.me