平衡点


2011/12/13 [長年日記]

_ Emacsの設定ファイルをorgで書く

この日記はEmacs Advent Calendar 2011の13日目です. 前日はid:handlenameさんのalign設定用例集でした.

勢いで申し込んでみたものの, 何を書こうか迷ってしまいました. 最近やった事といえば「Emacsの設定ファイルをorgで書く」ぐらいなので, 今日はそのお話です.

org-mode?

もはや説明不要な気もしますが org-mode は非常に高機能なアウトライン記述用のメジャーモードです. 私は最近殆ど全てのモンをorg-modeを使って記述しています.

org-modeはかなり高機能で, コードを見るたびに「こんな事もできるのか!」と驚愕することが多いです. 今日のお題はその中の「org-babel」を使ったお話です.

org-babel?

org-babelを使うとorgファイル内に別の言語のコードブロックを記述して, 出力結果をorgと一緒にアレコレすることができます.

今のところ 39 の言語に対応している(git のファイルだけみると 45 個ありますが)みたいですね.

Fortran なんか誰得感が...(オレ得?)

おもしろいのはorg-babelでEmacsの設定ファイルを記述する事ができる点です. 最近は init-loader とか使って設定ファイルを単一の.emacs.d/init.el から複数のファイルに分割している人も多いと思いますが, init.el あたりには org の load-path 設定あたりを書いておいて, 他の設定ファイルは org で書く, とかできるわけです.

つまり, 今まで適宜コメントを付記して書いていたemacs-lispファイルを org + org-babel で文章主体で書けるようになるわけです.

org-babel で emacs を設定する?

やることは簡単です. init.el で (require 'org-install) した後で org-babel-load でファイルを読むだけです. 例えば init.el に

(require 'org-install)
(defvar org-startup-dir (concat user-emacs-directory "site-start.d/"))
(org-babel-load-file (expand-file-name "init.org" org-startup-dir))

とか書いておいて, ~/.emacs.d/site-start.d/init.org に設定を記述していきます.

例えば ~/.emacs.d/site-start.d/ 以下に設定ファイルを集めている場合は init.org に

#+TITLE: Emacs の基本設定
#+OPTIONS: toc:2 num:nil ^:nil
* 言語の設定
...
* 外部ファイルで設定する
** org  で書かれた設定ファイルを読み込む関数
#+begin_src emacs-lisp
(defun my:load-org-file (file)
 "load org file"
 (org-babel-load-file (expand-file-name file org-startup-dir)))
#+end_src
...

という関数を定義しておくと,

(my:load-org-file "ddskk.org")

で ~/.emacs.d/site-start.d/ddskk.org が読み込まれます.

org-babel-load-file?

org-babel-load-file は ob-tangle.el で定義されていて

(defun org-babel-load-file (file)
  "Load Emacs Lisp source code blocks in the Org-mode FILE.
This function exports the source code using
`org-babel-tangle' and then loads the resulting file using
`load-file'."
  (interactive "fFile to load: ")
  (flet ((age (file)
              (float-time
               (time-subtract (current-time)
                              (nth 5 (or (file-attributes (file-truename file))
                                         (file-attributes file)))))))
    (let* ((base-name (file-name-sans-extension file))
           (exported-file (concat base-name ".el")))
      ;; tangle if the org-mode file is newer than the elisp file
      (unless (and (file-exists-p exported-file)
                   (> (age file) (age exported-file)))
        (org-babel-tangle-file file exported-file "emacs-lisp"))
      (load-file exported-file)
      (message "loaded %s" exported-file))))

となっています. つまり

  1. 指定された org ファイルの basename を取得
  2. 取得した basename + ".el" と元の org ファイルのタイムスタンプを比較
  3. .el のタイムスタンプが古かったら, org 中の #+begin_src emacs-lisp 〜 #+end_src 部分をファイル名.el というファイルに抽出する
  4. タイムスタンプが新しい or .el の抽出を行なったら .el を読む

ということをしています.

org-babel-load-file + byte-compile

というわけで org で設定ファイルを記述できるようになりました. これで

  • 「この設定なんだったっけ?」

とか

  • 「私の設定はこんな感じです」

とかがやりやすくなりました(違

でも org-babel-load-file は .el の抽出までしかやってくれません. .el があったら byte-compile したくなるのが人情です. また make 一発で全て byte-compile したいですよね?

というわけで(毎度 ad hoc に)ちょっと弄ってみました. 以下を ~/.emacs.d/init.el に書いておきます.

;;; org-babel
;;
;; Emacs の設定は org-mode で記述.
;;
(require 'org-install)
(defun my:org-babel-tangle-and-compile-file (file)
  "export emacs-lisp and byte-compile from org files (not load).
   originally ob-tangle.el"
  (interactive "fFile to load: ")
  (flet ((age (file)
              (float-time
               (time-subtract (current-time)
                              (nth 5 (or (file-attributes (file-truename file))
                                         (file-attributes file)))))))
    (let* ((base-name (file-name-sans-extension file))
           (exported-file (concat base-name ".el"))
           (compiled-file (concat base-name ".elc")))
      ;; tangle if the org-mode file is newer than the elisp file
      (unless (and (file-exists-p compiled-file)
                   (> (age file) (age compiled-file)))
        (org-babel-tangle-file file exported-file "emacs-lisp")
        (byte-compile-file exported-file)))))

(defun my:org-babel-load-file (file)
  "load after byte-compile"
  (interactive "fFile to load: ")
  (my:org-babel-tangle-and-compile-file file)
  (load (file-name-sans-extension file)))

(defvar org-settings-dir (concat user-emacs-directory "site-start.d/"))
(defun my:load-org-file (file)
  "org-settings-dir 以下から my-org-babel-load-file"
  (my:org-babel-load-file (expand-file-name file org-settings-dir)))

これで

  • 設定ファイルの byte-compile 時には my:org-babel-tangle-and-compile-file
  • 設定ファイルの読み込み時は (my:load-org-file "hogehoge")
    • hogehoge.org の#+begin_src emacs-lisp 〜 #+end_src 部分を hogehoge.el に抽出
    • hogehoge.el を byte-compile -> hogehoge.elc を生成
    • hogehoge.elc を load

となりました. まとめて byte-compile する時には Makefile あたりに

%.elc: %.org
      $(EMACS) --batch -l $(HOME)/.emacs.d/init.el --no-site-file \
        --eval '(mapc (lambda (x) (my:org-babel-tangle-and-compile-file (symbol-name x))) (quote ($(CURDIR)/$<)))'

とでも書いておくと良いでしょう.

まとめ?

というわけで「Emacs の設定ファイルを org で書く」というお話でした. 誰得感がありますが, コメントだけだと辛い場合に org で説明を書けるのは良いですね. またアウトライナーですから長くなりがちな解説部分を折り畳んで表示できるのも気にいっています.

最近, Emacs の設定を見直すと同時に org に変更 + html 化を進めています. 今日書いた内容は

にあります.

github にも up してあります. github は org も html で表示してくれるみたいですが, 欲を言えば org-babel 部分を適宜 syntax highlight するなり, code block として表示してくれるようになると良いのですけれどね.

明日はid:mhayashi1120の予定です.