Emacsのauto-insertでテンプレートを自動生成する

プログラムの単体テストを書く場合や、各種フレームワークを使用してプログラムを書く場合などは、ある規則にそってファイル名を付けると思います。(例えば単体プログラムのソースファイル名は"モジュール名_test.py"(Pythonの場合)など)
Emacsのauto-insertモードを使って、特定の名前のファイルを作成したときに、専用のテンプレートを自動生成してくれると便利だと思い、その方法を調べてみました。

ひげぽんさんのブログとその元ネタにほぼ正解が紹介されているのですが、lispのマクロを使って色々な種類のテンプレートを簡単に作れるようにしたので、新しいエントリを書いてみました。

ここではPythonの単体テストのソースファイルの雛形を自動生成するためのElispを書いてみます。

まず、~/.emacs.d/insertにunittest_template.pyという名前のテンプレートファイルを用意します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import unittest
import %testee%

class %test_class%(unittest.TestCase):    
  def setUp(self):
    pass

  def tearDown(self):
    pass

if __name__ == '__main__':
  unittest.main()

次に、~/.emacs.elに次の設定を追加します。

(require 'autoinsert)
(auto-insert-mode)
(setq auto-insert-directory "~/.emacs.d/insert/")

(defun buffer-file-name-nondirectory ()
  (file-name-nondirectory (buffer-file-name)))
(defun python-module-name ()
  (substring (buffer-file-name-nondirectory) 0 -8))
(defun python-test-case-name ()
  (concat (capitalize (python-module-name)) "Test"))

(require 'cl)
(defmacro define-replace-function (name replacement-alist)
  `(defun ,name ()
     (loop for (string . func) in ,replacement-alist
	   do (progn
		(goto-char (point-min))
		(replace-string string (funcall func) nil)))
     (goto-char (point-max))
     (message "done.")))
(define-replace-function python-unittest-template
  '(("%testee%"     . (lambda () (python-module-name)))
    ("%test_class%" . (lambda () (python-test-case-name)))
    ))
(add-to-list 'auto-insert-alist '("_test\\.py$" . ["unittest_template.py" python-unittest-template]))

ポイントはこの部分です。

(require 'cl)
(defmacro define-replace-function (name replacement-alist)
  `(defun ,name ()
     (loop for (string . func) in ,replacement-alist
	   do (progn
		(goto-char (point-min))
		(replace-string string (funcall func) nil)))
     (goto-char (point-max))
     (message "done.")))
(define-replace-function python-unittest-template
  '(("%testee%"     . (lambda () (python-module-name)))
    ("%test_class%" . (lambda () (python-test-case-name)))
    ))
(add-to-list 'auto-insert-alist '("_test\\.py$" . ["unittest_template.py" python-unittest-template]))

まず、loopマクロを使用するため、(require 'cl)を行っています。
次に、define-replace-functionマクロを定義しています。このマクロは、関数名と(置換する文字列 . 置換後の文字列を返す関数)のリストを渡すと、テンプレート文字列を置換する関数を定義するマクロです。
続いて、定義したマクロを使用して、先ほど作成したunittest_template.pyのテンプレート文字列を置換する関数を定義しています。
最後の行では、_test.pyで終わるファイル名を作成しようとしたときに、ここで作成したテンプレートファイルと文字列置換関数を使用するようにしています。

define-replace-functionの第2引数に渡すリストを変えれば、色々なテンプレートを作成することができます。