タイトルの通り、モジュール名を読んでモジュールのソースファイルを開く 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 も同じ戦略を取っているようだけど