Load a directory of Emacs Lisp files

[Originally published on ttlaxia.net, I think, date uncertain, but reposted 2011-05-20 Fri]

[Note 2018: I think these days, this sort of function is available as `load-dir' in one or the other of the Emacs package repositories]

As a result of a recent discussion on the emacs-devel list I was inspired to try improving a function I use to load a whole directory full of Emacs Lisp files at once. I have had use for this kind of thing in the past. The Emacs developers seem to have only one use in mind for this. The idea is that you use the ridiculous “customize” interface to define a directory that will be loaded on Emacs startup, then forget about it. The poor dumb Emacs user never has to so much as put a call to the function that loads the directory into his .emacs file. That would be too hard.

Anyway, I sent along a ‘load-dir’ function but it was summarily ignored. But I thought I would post it here for what it might be worth. One of my own common uses is when testing a new library I've downloaded and I don't want to go through any complex fiddlings to test it, so I just use `load-dir` to load all the files and see what happens. That's right. Living on the edge. In any case, that is why for me, interactive use with directory completion is essential, and at the same time, I want to have a call to it in code not need me to pass it any irrelevant arguments. That is why the argument handling in the function is a bit peculiar. But the end result is very convenient to use for me.

If you use it and find a bug, let me know.

(defvar file-loadable-regexp
  "\\." "\\\\."
  (let (string
        (suffix-list (get-load-suffixes)))
    (concat (car suffix-list) "$"
            (dolist (extension (cdr suffix-list) string)
              (setq string (concat "\\|" extension "$" string))))))
  "Regular expression that matches any file name with a file
extension returned by `get-load-suffixes'.")

(defun file-loadable-p (file)
 "Return t if FILE is an Emacs lisp file.
More precisely, return t if the file name extension matches
(string-match file-loadable-regexp file))

(defun load-dir (&optional directory recurse)
 "Load all Emacs Lisp files in DIRECTORY.

Load files whose file name satisfies predicate `file-loadable-p'.
Non-interactively, DIRECTORY must be specified.  If both compiled
and uncompiled versions of the same file exist, only load the
compiled file.  If optional argument RECURSE is non-nil, (or,
interactively, with prefix argument) recursively load
 (interactive "P")
 ;; The idea here is to allow a prefix arg to specify recursion, but
 ;; also to read from the minibuffer the directory name; yet in
 ;; non-interactive use to only need the one directory-name argument,
 ;; as in: (load-dir "~/foo")
 (let* ((recurse (if recurse recurse (when current-prefix-arg t)))
        (directory (if (stringp directory) directory
                     (when (called-interactively-p 'any)
                        (concat (if recurse "Recursively l" "L")
                                "oad all Emacs lisp files from directory: ")
                        default-directory default-directory t)))))
   ;; For non-interactive use
   (when (not (called-interactively-p 'any))
     (unless directory
       (error "Must specify a directory to when called non-interactively")))
   (unless (file-directory-p directory)
     (error "%s is not a directory" directory))
   (let ((file-list
          (directory-files (expand-file-name directory)
                           t directory-files-no-dot-files-regexp)))
     (dolist (file file-list)
          ;; This will include gzipped elisp files
          (file-loadable-p file)
          ;; Ignore symlinks to nonexistent targets.
          (file-exists-p file)
          ;; Don't try to load directies whose names end in ".el"
          ;; etc., as if they were files.  Note that we do not
          ;; predicate on "permission denied" problems, instead
          ;; letting things fail in that case so the user knows.
          (not (file-directory-p file))
          ;; If there exist both compiled and uncompiled versions of
          ;; the same library, only load the compiled one.  (This is
          ;; why we let-bind the `file-list'.)  This could perhaps be
          ;; factored out, and currently still double-loads gzipped
          ;; libraries.
          (not (and (string= (file-name-extension file t) ".el")
                     (concat (file-name-sans-extension file) ".elc")
         (load file))
        ((and (file-directory-p file)
         (load-dir file t)))))))


Popular Posts