エラー発生箇所を自動的にエディタで開く EditorKicker

はじめに

EditorKicker とは、Ruby スクリプトでエラーが発生したときに、その発生箇所をエディタで自動的に開いてくれるツールです。 これを使うと、アプリケーション開発時におけるターンアラウンドタイムを短くし、また手動でファイルを開く手間を減らせます。

EditorKicker は Ruby on Rails と CGI に対応しています。 またエディタはデフォルトで TextMate と Emacs をサポートしていますが、設定により任意のエディタが起動可能です。

なお本記事は MacOS X と Linux で動作を確認していますが、Windows や cygwin では未検証です。

使い方

本稿では、Ruby on Rails での使い方と、CGI スクリプトでの使い方を紹介します。 また自分の好きなエディタを指定する方法も紹介します。

Ruby on Rails での使い方

EditorKicker は Ruby on Rails 用のプラグインを用意していますので、それをインストールしましょう。 インストールは、script/plugin install を実行するだけです。

 $ ruby script/plugin install \
   http://github.com/kwatch/editorkicker.git/ruby/plugin/rails/editorkicker

インストールしたら、起動するエディタのコマンドを環境変数 $EDITOR_KICKER に設定します。 何も設定しない場合は、TextMate がインストールされていれば mate コマンドを、そうでなければ emacsclient を使うようになっています。

 ### TextMate の場合
 $ export PATH=$PATH:/Applications/TextMate.app/Contents/Resources
 $ export EDITOR_KICKER="mate -l %s '%s'"
 ### Emacs の場合
 $ export EDITOR_KICKER="emacsclient -n +%s '%s'"
 ### その他 (MacOS X の場合)
 $ export EDITOR_KICKER="open -a アプリ名 '%2$s'"

またエディタとして Emacs を使う場合は、M-x server-start を実行してください。 そうしないと、emacsclient コマンドが正常に実行できません。

ここまできたら、Ruby on Rails サーバを development モードで起動してください。

 $ ruby script/server -p 3000

動作を確認するために、Controller や View に「nil+1」や「1/0」のようなコードをわざと入れてみてください。 ブラウザでアクセスすると、エディタがそのファイルを自動的に開き、エラーが起こった行にカーソルが移動していることを確認してください。

なお EditorKicker プラグインは、develpment モードでのみ動作するようになっており、production モードでは読み込まれないようになっています。 そのため、vendor/plugins 以下にインストールされたファイルをリポジトリにコミットしても、production モードには何の影響も与えません。 安心してコミットしてください。

CGI/mod_ruby での使い方

CGI や mod_ruby で EditorKicker を使うには、CGI-Exception も同時にインストールする必要があります。 CGI-Exception は、CGI スクリプトでエラーが発生したときに、それをブラウザ画面に表示するツールです。 ちょうど PHP のようにエラーがブラウザ画面に表示されるため、わざわざ Web サーバのログを確認する必要がなくなります*1

CGI-Exception は EditorKicker をサポートしており、CGI スクリプトでエラーが発生したときに EditorKicker が require されていれば、EditorKicker を使ってエディタを起動するようになっています。

EditorKicker や CGI-Exception のインストールは、RubyGems を使うか、または手動で行ないます。 手動で行なう場合は、ファイルを以下からダウンロードしてください。

実際のインストール手順は以下のようになります。

 ### RubyGems を使う場合
 $ sudo gems install cgi-exception editorkicker
 ### 手動で行なう場合
 $ cd /tmp
 $ tar xzf cgi-exception-0.3.0.tar.gz
 $ cd cgi-exception-0.3.0/
 $ sudo ruby install.rb
 $ cd /tmp
 $ tar xzf editorkicker-0.1.0.tar.gz
 $ cd editorkicker-0.1.0/
 $ sudo ruby install.rb

なお CGI スクリプトで使う場合は、RubyGems を使わないでインストールことをお薦めします。 理由は、RubyGems をロードするコストが CGI スクリプトにとって致命的に重いからです。

また Emacs をお使いの場合は、M-x server-start を実行してください。 そのあと、例えば /tmp/emacs501/server のようなソケットファイルが作成されますので、この所有者を CGI スクリプトの実行ユーザ (Apache をお使いなら Apache の実行ユーザ) に変更します*2

 $ sudo chown -R /tmp/emacs501

ここまできたら、あとは CGI スクリプトで 'cgi-exception' と 'editor_kicker' を require し、ENV['EDITOR_KICKER'] にエディタ起動コマンドを設定してください。

サンプルを見てみましょう。 たとえば以下のサンプルでは、15 行目に初期化されていないローカル変数が使われているため、ここでエラーになるはずです。 実際に実行してみて、エディタが起動してスクリプトファイルが開き、カーソルが 15 行目に移動したことを確認してください。 また emacsclient コマンドの -s オプションでソケットファイルを指定していることに注意してください*3

#!/usr/bin/env ruby
require 'cgi'
require 'cgi-exception'
## localhost のときだけ editor_kicker.rb を読み込む
if ENV['SERVER_NAME'] == 'localhost'
  {{*require 'editor_kicker'*}}
  {{*ENV['EDITOR_KICKER'] = \*}}
    {{*"emacsclient -n -s /tmp/emacs501/server +%s '%s'"*}}
end
##
cgi = CGI.new
print cgi.header('text/html; charset=UTF-8')
print "<h1>EditorKicker Example</h1>\n"
print "<p>Hello #{user}!</p>\n"   # ここでエラー!
print "<p>unreachable</p>"

FastCGI をお使いの場合は、以下を参考にしてください。

#!/usr/bin/env ruby
require 'cgi'
require 'fcgi'
require 'cgi_exception'

FCGI.each do |cgi|
  begin
    print cgi.header('text/html')
    print "  <h1>Example</h1>\n"
    print "  <p>Hello #{user}</p>\n"   # エラー!
    print "  <p>unreachable</p>\n"
  rescue Exception => ex
    CGIExceptionPrinter.new(false).handle(ex)
    ## localhost のときだけ editor_kicker.rb を読み込む
    if cgi.env_table['SERVER_NAME'] == 'localhost'
      {{*require 'editor_kicker'*}}
      {{*ENV['EDITOR_KICKER'] = \*}}
        {{*"emacsclient -n -s /tmp/emacs501/server +%s '%s'"*}}
      {{*EditorKicker.handle(ex)*}}
    end
  end
end

任意のエディタを指定する

他のエディタを使いたいときは、環境変数 EDITOR_KICKER に、エディタを起動するコマンドを設定してください。 例えば NetBeans を使う場合は、以下のようにしてください。

 ## bash や zsh で指定する場合
 $ export EDITOR_KICKER='~/netbeans-6.1/bin/netbeans --open %2$s:%1$s'
 ## Ruby で指定する場合
 ENV[EDITOR_KICKER'] = '~/netbeans-6.1/bin/netbeans --open %2$s:%1$s'

ここで「%2$s」というのは、2 番目の引数を使う意味です。以下のサンプルをご覧下さい。

 irb> "netbeans --open %s:%s" % [123, 'filename.rb']
 "netbeans --open 123:filename.rb"
 irb> "netbeans --open %2$s:%1$s" % [123, 'filename.rb']
 "netbeans --open filename.rb:123"

開く対象となるファイルのパスを指定する

EditorKicker では、エディタで開くファイル・開かないファイルのパスを指定することができます。

例えば次のような SQL のエラーが発生したとします。 エラーが発生した箇所は /usr/lib/ruby/site_ruby/1.8/kwery/adapters/mysql.rb の 101 行目ですが、ここをエディタで開いてもうれしくありません。 本当の原因は、./controllers/blog_controller.rb の 308 行目で指定した SQL が間違っているからであり、ここをエディタで開いてほしいと思うでしょう。

{{*/usr/lib/ruby/site_ruby/1.8/kwery/adapters/mysql.rb:101*}}:in `prepare': \
You have an error in your SQL syntax; check the manual that corresponds \
to your MySQL server version for the right syntax to use near '-1, 10' \
at line 1 (Mysql::Error)
    from /usr/lib/ruby/site_ruby/1.8/kwery/adapters/mysql.rb:101:in `execute'
    from /usr/lib/ruby/site_ruby/1.8/kwery/query.rb:336:in `get_all'
    from {{*./controllers/blog_controller.rb:308*}}:in `do_index'
    from ./controllers/controller.rb:66:in `__send__'
    from ./controllers/controller.rb:66:in `invoke_handler'
    from ./controllers/controller.rb:50:in `handle_request'
    from /usr/local/apache2/htdocs/blog/index.cgi:16

このために、EditorKicker では環境変数 EDITOR_KICKER_INCLUDE と EDITOR_KICKER_EXCLUDE を用意しています。

ENV['EDITOR_KICKER_INCLUDE']
エディタで開きたいファイルのパスを指定する。
ENV['EDITOR_KICKER_EXCLUDE']
エディタで開きたくないファイルのパスを指定する。

今回の例なら、次のようにすれば、/usr/lib/ruby/site_ruby/1.8/kwery/adapters/mysql.rb ではなく ./controllers/blog_controller.rb が開くようになります。

## /usr/lib/ruby 以下のファイルはエディタで開かない。
ENV['EDITOR_KICKER_EXCLUDE'] = '/usr/lib/ruby'
## それ以外はエディタで開かれる

EditorKicker では、EDITOR_KICKER_EXCLUDE より EDITOR_KICKER_INCLUDE のほうが先に解釈されます。 たとえば /usr/lib/ruby 以下は開きたくないけど、/usr/lib/ruby/site_ruby/1.8/mylib 以下だけは開くようにしたい場合、以下のようにします。

## エディタで開くファイルのパス
ENV['EDITOR_KICKER_INCLUDE'] = '/usr/lib/ruby/site_ruby/1.8/mylib'
## エディタで開かないファイルのパス
ENV['EDITOR_KICKER_EXCLUDE'] = '/usr/lib/ruby'
## それ以外はエディタで開かれる

なお複数のパスを指定する場合は、UNIX や MacOS X なら「:」で、Windows なら「;」で連結してください。

まとめ

本稿では EditorKicker を使って、Ruby スクリプトでエラーが起こったときに該当箇所をエディタで自動的に開くよう設定する方法を説明しました。 また設定方法は、Ruby on Rails と CGI スクリプトの両方について説明しました。

EditorKicker を使うと、開発時のターンアラウンドタイムを削減することができます。 実際には大した削減にはならない可能性はありますが、少なくともブラウザとエディタを行き来するフラストレーションは間違いなく減らせます。

なお EditorKicker がサポートしているのは、現時点では Ruby on Rails と CGI/mod_ruby のみですが、将来的には他のフレームワークやプログラミング言語をサポートする予定です。

本稿が、みなさんの開発作業をより楽しくすることにつながれば幸いです。


*1 ローカルマシンでの開発なら tail -f error_log でもいいんですけどね。

*2 emacsclient コマンドでは、コマンドの実行ユーザとソケットファイル /tmp/emacs501/server と 所有者とが同じでないといけないようです。

*3 emacsclient コマンドの -s オプションは Emacs 22 から利用可能であり、Emacs 21 付属の emacsclient にはないようです。