(define -ayalog '())

括弧に魅せられて道を外した名前のないプログラマ

あるディレクトリ以下のファイルを全てフォーマットし直したい

僕は普段 Emacs を使っている。しかし、最近 Cursive*1 を使うこともある。
それで何が困るかというと Cursive はちょっとイケてないので、ちゃんと Clojure(Script)? をフォーマットすることができない*2Clojure のスタイルガイドだと例えば with-* マクロのときは 2 スペース分インデントするとかあるんだけど、それを Cursive でよしなにするの結構めんどくさいというか、独自で with-* を定義してもそれをデフォルトで綺麗にインデントしてくれない。だからそのままコミットすると困ったことになるわけですね。

じゃあコミット前に自動でどうにかしたい、と思うわけです。賢明な読者なら既に気付いていると思うけど、そう Emacs Lisp を使えばいいんです。

スクリプト言語としてのEmacs Lisp

ということでこんな感じのスクリプトを書いた。

;; 重くなるけど、 Emacs の初期化ファイルがないとそもそも Clojure モードとかない( script 実行用の初期化ファイルをある程度軽量化版で作れば問題なさそうだけど、時間の都合で割愛)
(load (locate-user-emacs-file "init"))

(defun directory-files-recursive (direcotry match)
  (let* ((files '())
         (current-directory-list
          (directory-files direcotry t)))
    (while current-directory-list
      (let ((f (car current-directory-list)))
        (cond
         ((and (file-regular-p f)
               (file-readable-p f)
               (string-match match f))
          (setq files (cons f files)))

         ((and (file-directory-p f)
               (file-readable-p f)
               (not (string-equal ".." (substring f -2)))
               (not (string-equal "." (substring f -1))))
          (setq files (append files (directory-files-recursive f match))))
         (t)))

      (setq current-directory-list (cdr current-directory-list)))
    files))

(defun re-format-file (file)
  (find-file file)
  ;; だいたい拡張子に紐付いたモードに自動的になるはずなので問題ないけど、必要があればこの位置に (clojure-mode) とか入れたらいいと思う
  (indent-region (point-min) (point-max))
  (save-buffer))

(let* ((dir-name (car argv))
       (match (cadr argv))
       (matched-files (directory-files-recursive (car argv) (cadr argv))))
  (mapcar 're-format-file matched-files))

これを ~/.emacs.d/scripts とかディレクトリ適当に作ってその中に保存しておいて

emacs -Q --script ~/.emacs.d/script/re-format.el ~/some/clojure/proj/dir "\\.clj$"

こう呼びだせばいい感じにしてくれると。これをコミットフックあたりで実行すると幸せになれそう。

(あるいはコミットフック使う前提なら、コミットする対象のファイルのパスを全部送ってフォーマットし直すようなスクリプトを書く方が効率よいですね)

*1:IntelliJ IDEA

*2:先日話した発表でも言及したけど