個人ブログ、会社ブログとも Octopress を使っている。
Octopress を使ってて思うのは「Emacs から記事を作成(する rake タスクを実行)/編集したい!」
それで octomacs を使ったんだけど、rvm と組み合わせて使った時にハマった。
結果だけを先に述べると、octomacs で rvm を使いたい時は、/etc/ 以下のログインシェルの設定に rvm のパスを追加するようにすれば良い。ベストな解決策では無いだろうが、とりあえず動く。
以下、奮闘記。暇な人向け。
Emacs 拡張の選定
Emacs から Octopress をゴニョニョする Emacs 拡張はパッと探した感じ、2つある。
-
- Marmalade に登録されているので、 package.el で設定できる
- 複数の Octopress プロジェクトに対応
- rvm/ido に対応
- 使えるは `octomacs-new-post’ だけ
- 結構複雑な事をやっている
gfreezy/octopress-emacs · GitHub
- very very very simple
- ゆえに外から設定とかできない
- コピーして自分で適当に書き換えて配置する必要がある(rvm化、パスの設定など)
- 一つの Octopress プロジェクトにだけ対応
- rbenv のみに対応
- ひと通りの rake タスクを Emacs から実行できる
- が、同期実行だから
rake gen_deploy
とかするとしばらく固まるしrake preview
とかrake watch
した日には Emacs 動かなくなる
- が、同期実行だから
- very very very simple
ということで個人ブログだけを管理する時には octopress-emacs を使ってたけど、会社ブログも Octopress になったし、ちょっと前に Emacs 24 化してパッケージ管理を完全に package.el に以降したから、octomacs を使うことにした。
octomacs 設定
package.el を使って rvm と octomacs をインストールしておく。(Marmalade を登録しておく必要あり)
そしたら init.el に
(require 'rvm)
(require 'octomacs)
と書いておく。
プロジェクトの設定は M-x customize-group octomacs
で Octomacs Workdir Alist
を編集するか、init.el か何かに
(custom-set-variables
'(octomacs-workdir-alist
(("my-blog" . "~/my-blog")
("kaisha-blog" . "~/kaisha-blog"))))
とか書いておけばおk。alist は `(プロジェクト選択時に出る名前 . ディレクトリパス) という形。
以上が済んだらおもむろに M-x octomacs-new-post
を叩く。プロジェクトを選んで、ポスト名を入力する。良い感じ。だが、無残なメッセージと共にコマンドは失敗する:
Unable to create post: /bin/bash: rvm: command not found
無念。
失敗する関数を特定したい
さて、原因を探る。まずやる事は、どの関数で失敗したかを知る事だ。幸い、Emacs にはあるパッケージの呼び出しのプロファイルをとる関数が用意されていたので、それを使ってみる。
M-x elp-instrument-package octomacs
してから、
M-x octomacs-new-post
してプロジェクト名を入力、ポスト名を適当に入れて、失敗する事を確認する。その後、
M-x elp-results
してやると、octomacs パッケージ内での各関数呼び出しのプロファイルが出てくる
octomacs-new-post 1 5.199352 5.199352
octomacs-new-post-interactive 1 3.722928 3.722928
octomacs-read-project 1 2.010394 2.010394
octomacs-read-post-name 1 1.712456 1.712456
octomacs-rake 1 0.926343 0.926343
octomacs-rake-with-rvm 1 0.926298 0.926298
octomacs-format-rake-task-with-args 1 2.7e-05 2.7e-05
octomacs-shell-escape-string 1 8e-06 8e-06
今回はプロファイル結果に興味はないので、関数名から怪しい所を探しだす。まー怪しい所は octomacs-rake-with-rvm
だろう。関数名上でエンターを押下でその関数定義に飛んでくれた。便利!
shell-command-to-string との出会い
こんな関数定義だった。
(defun octomacs-rake-with-rvm (directory task &optional arguments)
"Run rake task TASK with specified ARGUMENTS in DIRECTORY using rvm"
(let* ((default-directory (file-name-as-directory (expand-file-name directory)))
(rvmrc-path (rvm--rvmrc-locate directory))
(rvmrc-info (if rvmrc-path (rvm--rvmrc-read-version rvmrc-path) nil))
(rvm-command (if rvmrc-info
(concat "rvm " (mapconcat 'identity rvmrc-info "@") " do ")
"")))
(shell-command-to-string (format "%srake %s" rvm-command (octomacs-format-rake-task-with-args task arguments)))))
エラーを思い出すと
Unable to create post: /bin/bash: rvm: command not found
こんな感じなので、予想としては shell-command-to-string
が原因だろうと当たりをつけた。そうすると知りたいのは、この format
がどんな文字列を生成しているか。 shell-command-to-string
が実行される直前で実行を止めてみてみたい。
Emacs でブレークポイントを貼りたい時は edebug
なるものを使うらしい。使い方は、ブレークポイントを貼りたい関数内で C-u C-M-x
とする。その後、その関数が評価される時に実行が止まる。知っておくべきコマンドは space
でステップ実行、 e
で現在のコンテキストでの式の評価、 h
でカーソルまで実行、 q
でデバッグ終了、位。後は、ステップ実行で式が評価される度にミニバッファに結果を表示していて、実は *Message*
バッファにミニバッファの表示がスタックされている事を知っていれば、今回の目的には十分である。
さて、そんなこんなで format
が生成している文字列が分かった。こんなの:
"rvm 1.9.3@octopress do rake 'new_post[test]'"
こいつを shell-command-to-string
に渡してるらしい。
じゃあちょっとスクラッチバッファで実行してみよう。
(shell-command-to-string "rvm 1.9.3@octopress do rake 'new_post[test]'")
"rake aborted!
No Rakefile found (looking for: rakefile, Rakefile, rakefile.rb, Rakefile.rb)
/Users/koichi/.rvm/gems/ruby-1.9.3-p392@octopress/bin/ruby_noexec_wrapper:14:in `eval'
/Users/koichi/.rvm/gems/ruby-1.9.3-p392@octopress/bin/ruby_noexec_wrapper:14:in `<main>'
(See full trace by running task with --trace)
"
…うん、実行するディレクトリを指定してないから、どこで実行してるかはわからないけど、 Rakefile が見当たらないのは当たり前だよなぁ。でも、octomacs ではきちんと Rakefile のあるディレクトリに行って実行しているっぽい気がする。じゃないと、他の人がツッコミを入れると思うし、作者もそんな凡ミスしたまま Marmalade にあげないよなぁ。何かそういう仕組みがあるはず。
でもまぁとりあえず、どこで実行してるかは知りたいので、スクラッチバッファで試してみる。
(shell-command-to-string "pwd")
"/Users/koichi
"
ホームディレクトリだった。
octomacs-rake-with-rvm
にブレークポイントを貼って、その中で pwd
実行したらどうなるんだろ。
"/Users/koichi/my-blog\n"
うん、やっぱり何か、直接引数に渡さないで、外部からディレクトリを指定する仕組みがあるのね。
で、実はこれまでリファレンスを見ずに来たけど、それは shell-command-to-string
って名前から何をするか大体想像がつくから。おそらく、引数の文字列をシェルにコマンドとして渡して、結果の文字列を返すのかなーって感じ。
で、この関数の機能の想像と、渡される引数と、スクラッチバッファでの実行結果を見て2つの疑問点がわいてきた。
- 実行時にはどのシェルを使っていて、そのシェルの設定はどこから読み込んでるの?
- 外部からディレクトリを指定する方法を知りたい
ついにリファレンスを見る時が来たようだ。
かいけつ!
GNU Emacs Lispリファレンスマニュアル: Synchronous Processes
上記リファレンスを見たのだが、
Function: shell-command-to-string command この関数は、シェルコマンドとしてcommand(文字列)を実行し、 コマンドの出力を文字列として返す。
と書いてあるだけ。全く予想通りだし、それ以上の事も書いて無かった。疑問点は何も解決しない。 「ついにリファレンスを見る時が来たようだ。」とか意気込んでおいてこの結末、残念すぎる。
こうなったらやることはググったり shell-command-to-string
を使ってどんな動作をしているかを確かめるしかない。
とりあえず、
実行時にはどのシェルを使っていて、そのシェルの設定はどこから読み込んでるの?
はグーグル先生に聞いた所、 shell-command-to-string
はログインシェルを使っているらしい事が分かった。で、ログインシェルを確認したら Bash だった。エラーメッセージで、bash が使われている理由が分かった。
僕は普段 zsh を使っているので、bash には rvm の設定をしていない。だったらログインシェルを zsh にすれば解決するだろう、という事で、ログインシェルを zsh にした。が、エラーメッセージは zsh でも command not found だから、問題は別の所にあるのだと考えた。で、次の式をスクラッチバッファで評価してみた。
(shell-command-to-string "echo $PATH")
そうすると ~/.zshrc
で設定したパスが表示されていない。これで合点した。ホームディレクトリ以下の設定ファイルは読み込まないんだと…
で /etc/zshrc
に rvm のパスを設定するようにしたら、見事に動いた!これで解決したと言っても良いだろう。
ひとまずの解決策として、octomacs で rvm を使いたかったら、/etc/ 以下のログインシェルの設定に、rvm へのパスを追加すれば良い。
問題自体はこれで解決したが、残る疑問点を解決しておきたい。
Emacs Lisp の勉強
最後の疑問点はこれだ。
外部からディレクトリを指定する方法を知りたい
もしこんな指定方法があるとすれば shell-command-to-string
を使っている箇所ではどこでも同じ事をしているはず。octomacs を眺めてみたら、ある共通点があった。
* `let` の中で実行されている
* `let` で `default-directory` を設定している
完全にdefault-directory
が怪しすぎる。とりあえず、グーグル先生に聞いてみたら、英語版のリファレンスマニュアルがヒット。
Synchronous Processes - GNU Emacs Lisp Reference Manual
そしてこんな記述。
The current working directory of the subprocess is default-directory.
ビンゴー!まさしくこれだった。
shell-command-to-string
は最終的に call-process
を読んでるだろうし、間違いないだろう。
で、イマイチこの辺の理解が曖昧だったのだけれども、どうやら default-directory
は「バッファローカル変数」らしい。これは、「デフォルト変数」といういわばグローバル変数をバッファ毎に書き換えられるものらしい。(こんな理解でいいのかな)
ある関数の為に let
でスコープを作り、その中で特定の変数に任意の値を束縛して関数の動作を制御するっていうこの Lisp のスタイル、面白いなーと思う。
終わり。
参考
- Emacs Lisp デバッグ — ありえるえりあ
- Emacs Lispのソースコードデバッガ edebug を使う - (rubikitch loves (Emacs Ruby CUI Books)))“)
- emacs lispのshell-command-to-string() - no strict; no life; - no strict; no life;“)
- GNU Emacs Lispリファレンスマニュアル: Synchronous Processes
- Synchronous Processes - GNU Emacs Lisp Reference Manual
- Emacs Lisp メモ: バッファローカル変数 | anobota