理系学生日記

おまえはいつまで学生気分なのか

モジュール名からモジュールのソースファイルをシームレスに開く elisp

タイトルの通り、モジュール名を読んでモジュールのソースファイルを開く elisp を作ってみました。これによって、ソースを読みつつ気になったモジュールがあると、そのモジュールのソースもシームレスに覗くことができるようになりました。

動機

最近ヒマなときはずっと Catalyst 本を写経しているのですけど、今まで使っていなかったモジュールが山ほど出てきていて、これらのモジュールがどう実装されているのかが気になることがあります。
local::lib を使っているので、ほとんどモジュールは $HOME/perl5/lib/perl5 配下にあるのですが、Emacs でそれらのファイルをパス指定していくのはメンドいし、Module::Pluggable のように $HOME/perl5/lib/perl5/darwin-thread-multi-2level 以下にインストールされるモジュールも多いので、パス指定のメンドくささに拍車をかける。作ろうって思ったのはそういうわけです。

ソース

(defun find-module-source-file ()
  (interactive)
  (setenv "PERL5LIB" (getenv "PERL5LIB"))
  (let ((module-name (cperl-word-at-point))
        (buffer-name "*perldoc buffer*"))
    (if module-name
        (if (eq 0 (call-process "perldoc" nil buffer-name t "-l" module-name))
            (save-excursion
              (set-buffer buffer-name)
              (beginning-of-buffer)
              (find-file-read-only (buffer-substring (point-min) (progn (end-of-line) (point))))
              (kill-buffer buffer-name))
          (progn
            (message "An error occured in executing perldoc")
            (kill-buffer buffer-name)))
      (message "Cannot retrieve a module name"))))

現在のポイント付近にあるモジュールの名前を得るのには、cperl の cperl-word-at-point を使いました。最初は thing-at-point を使えば良いと思っていたのですが、Acme::LOLCAT のように "::" があると、そこで単語が区切られてしまって Acme しか取得できないとかいう状況になってしまう。

外部コマンドの出力を elisp 側で使うとき

悩んだのは、外部コマンドの出力をどうやって elisp に返すかです。

モジュール名 -> ソースファイルパスのマッピングについては、perldoc -l を使っています。つまり外部コマンドの呼び出し結果を elisp 側で使うということになります。
外部コマンドの呼び出し結果を文字列として返す関数として、shell-command-to-string がありますが、この関数は外部コマンドの成否に関わらずにその結果を返す。そのため、インストールしていないモジュールに対して perldoc を実行すると

No documentation found for "a".

なんてのが elisp 側に返ってくることになってしまいます。できればこれを避けたかったので、call-process を生で呼び、その終了ステータスで分岐処理をすることにしました。perldoc の出力はテンポラリなバッファに吐いて、その内容を読み取るという概そスマートとは程遠い方法を使っています。

with-output-to-string などを使うとスマートなのだと思いますが*1、まぁこの辺は TODO で。

*1:とはいえ、with-output-to-string も同じ戦略を取っているようだけど