読者です 読者をやめる 読者になる 読者になる

ディレクトリ上位方向へ進む際のイディオム

emacs elisp

今開いているバッファに関連するファイルが特定のプロジェクトに
含まれるかなどを調べるとき、ディレクトリを上位方向へ辿り、
特定のファイルが含まれるかを調べるということがあるかと思いますが、
elispでどう書くのがいいのかわからなかったので、標準ライブラリの
コードを調べました。


それについて示します。

file-name-directoryと directory-file-nameを組み合わせる

file-name-directory関数と directory-file-name関数を交互に使って
いけば良いです。directory-file-nameで末尾のスラッシュを取り除き、
file-name-directoryで親ディレクトリを求めています。末尾がスラッシュ
だと file-name-directoryは同じ名前を返すので、それだけ使って
上へ上へと辿ることはできません。


以下にサンプルコードを示します。

(defun test (start)
  (let ((dir (file-name-as-directory start))
        (prev ""))
    (while (not (string= dir prev))
      (message "%s" dir)
      (setq prev dir
            dir (file-name-directory (directory-file-name dir))))))

(test "/home/syohei/program/elisp/")
実行結果
/home/syohei/program/elisp/
/home/syohei/program/
/home/syohei/
/home/
/


gitなんかだと, gitコマンドで現在がリポジトリ内かどうか調べる
ことなので不要ですが、Perlプロジェクトやら Rubyプロジェクト内に
いるかどうかの判定には使えるんじゃないでしょうか。

具体例

具体的に書くとしたら以下のような感じでしょうか.
(Windowsだと動くかどうかわかりません)

(defun my/exists-p (dir targets)
  (let ((default-directory dir))
    (loop for target in targets
          if (file-exists-p target)
          return target)))

(defun my/find-to-root (start targets)
  (let ((prev "")
        (dir (file-name-as-directory start)))
    (while (and (not (string= dir prev))
                (not (my/exists-p dir targets)))
      (setq prev dir
            dir (file-name-directory (directory-file-name dir))))
    (if (string= prev "/")
        "cucumber"
      (concat "bundle exec cucumber"))))

(my/find-to-root "/home/syohei/tmp/hoge" '("Gemfile" "Rakefile"))