プロジェクトの site-lisp を *load-path* に追加する

xyzzy の拡張書くときは ~/work/<プロジェクト名> にこんな風にファイルが配置される。

  • ~/work/<プロジェクト名>/
    • README.md
    • site-lisp/
      • <プロジェクト名>.l
      • <プロジェクト名>/
        • packages.l
        • util.l
        • ...

最初は <プロジェクト名>.l に全部書いてるんだけど、何度も書いては捨ててるうちに毎回書くもの(パッケージ定義とかユーティリティとかある程度形が決まってきたものとか)を別ファイルに分割していくので、この時点では主に <プロジェクト名>.l で色々やってる。

~/work/<プロジェクト名>/site-lisp/*load-path* に追加される形を想定しているので、各ファイルのモジュール名はこうなる。

  • ~/work/<プロジェクト名>/site-lisp/<プロジェクト名>.l
    • --> "<プロジェクト名>"
  • ~/work/<プロジェクト名>/site-lisp/<プロジェクト名>/packages.l
    • --> "<プロジェクト名>/packages"
  • ~/work/<プロジェクト名>/site-lisp/<プロジェクト名>/util.l
    • --> "<プロジェクト名>/util"

なので <プロジェクト名>.l から他のファイルを require している。

(eval-when (:execute :compile-toplevel :load-toplevel)
  (require "<プロジェクト名>/packages.l")
  (require "<プロジェクト名>/util.l")
  ...)

諸々の事情(Windows Update など)で xyzzy を再起動したりした時には、~/work/<プロジェクト名>/*load-path* に登録されていないので、こいつらを見つけられなくなって、全部のファイルを順番に手動でロードしたり repl から手動で *load-path* に追加したりしてたんだけど、めんどくなったので勝手に *load-path* へ追加するようにした。

(in-package :editor)

(defun provide-module-name ()
  (save-excursion
    (goto-char (point-min))
    (when (scan-buffer "^(provide \"\\([^\"]+\\)" :regexp t)
      (format nil "~A.l" (match-string 1)))))

(defun module-root-pathname (pathname module-name)
  (let ((path (reverse (split-string pathname "/")))
        (name (reverse (split-string module-name "/"))))
    (while name
      (unless (string= (car path) (car name))
        (error "モジュール名とファイル名が一致しません: ~S" module-name))
      (setf path (cdr path)
            name (cdr name)))
    (format nil "~{~A/~}" (reverse path))))

(defun add-module-root-to-load-path ()
  (let ((root (module-root-pathname (get-buffer-file-name) (provide-module-name))))
    (pushnew root *load-path* :test #'path-equal)))

(defconstant +original-require-function+ #'require)

(defun require (module-name &optional pathname)
  (handler-case
      (funcall +original-require-function+ module-name pathname)
    (error (e)
      (if (and (typep e 'simple-error)
               (string= (princ-to-string (simple-error-format-string e))
                        "ファイルが見つかりません"))
        (progn
          (add-module-root-to-load-path)
          (funcall +original-require-function+ module-name pathname))
        (error e)))))

今編集してる lisp ファイルを保存したときに(コンパイルして)ロードしてるので、現在のバッファでモジュール名とか探してる。他のところからロードしたときは使えねーわ。