I’ve been using directory-local-variables for a while now, and written about them before
I currently have two problems with DLV:
(dir-locals-set-class-variables 'read-only
'((nil . ((buffer-read-only t)))))
(dir-locals-set-directory-class "~/Library/Preferences" 'read-only nil)
phpunit-program needs to be $ROOT/bin/phpunit. This doesn’t map well onto the class/directory split of DLV; I have to define a class for every directory, since the values are dependent on the path. You also end up duplicating a lot of long paths inside the directory-class.Here’s my current solution, dir-locals.
(defmacro absolute-dirname (path)
"Return the directory name portion of a path.
If PATH is local, return it unaltered.
If PATH is remote, return the remote diretory portion of the path."
`(cond ((tramp-tramp-file-p ,path)
(elt (tramp-dissect-file-name ,path) 3))
(t ,path)))
(defmacro dir-locals (dir vars)
"Set local variables for a directory.
DIR is the base diretory to set variables on.
VARS is an alist of variables to set on files opened under DIR,
in the same format as `dir-locals-set-class-variables' expects."
`(let ((name (intern (concat "dir-locals-"
,(md5 (expand-file-name dir)))))
(base-dir ,dir)
(base-abs-dir ,(absolute-dirname dir)))
(dir-locals-set-class-variables name ,vars)
(dir-locals-set-directory-class ,dir name nil)))
It takes two arguments, a directory and a list of variables. The directory-class is autogenerated, so you don’t need to worry about it. Here’s how you’d use it:
(dir-locals "~/Projects/hello-world"
'((nil . ((user-mail-address . "ian@foo.com")))))
Much better.
Within your varible list, you have two special variables you can use: base-dir and base-abs-dir. These contain the root of the directory the variables apply to, and the local directory part of that, respectively.
We need this because of the way Emacs invokes processes. If you’re visiting a remote file, Emacs will spawn external processes on the remote system. To set a DLV for a remote file, the root needs to be in the form /ssh:user@host:/path/to/root. If you try to invoke a program like that, it won’t work; you need just the directory part of the path, and this is what’s available in base-abs-dir.
On the other hand, if you want per-project TAGS files, you’ll need the full path to the remote system.
Here’s how you’d use it:
(dir-locals "~/Projects/hello-world"
`((nil . ((user-mail-address . "ian@foo.com")
(tags-table-list . '(,(concat base-dir "/TAGS.gz")))))
(php-mode . ((phpunit-program . ,(concat base-abs-dir
"/bin/phpunit"))))))
The important thing to note here is the different syntax used for the variables. Rather than using a regular quote, we use a backquote, and a comma where the special variables are used.
And that’s that. Now I have one-(or two-)line setting of variables, with access to the base directory.
Discussion