Emacs Etags Shortcut Functions
General notes


Always prefix your functions with the name of your package or project, e.g. tagariffic-create-tag-table and so on. Emacs has no package scoping.
Whenever possible (i.e. in the absence of mutual recursion), define your functions before using them. It's a lot clearer for the reader. It's also necessary if you ever define macros.
You're using some functions from the cl package, so you should start with (require 'cl).
The usual style for predicates in Lisp is name-p, not name? (that's Scheme).
Emacs has functions to manipulate file and directory names. Use them rather than going around concatenating slashes; this way your code will work on exotic platforms such as Windows.


Finding the tags file

Emacs 23 has a function locate-dominating-file (in files.el).


  Look up the directory hierarchy from FILE for a file named NAME.
  Stop at the first parent directory containing a file NAME,
  and return the directory.  Return nil if not found.


Your (find-parent-tags default-directory) is almost equivalent to (locate-dominating-file (buffer-file-name) "TAGS"). If you want to use the current directory rather than the directory of the current buffer's file (they're almost always the same), use (locate-dominating-file (concat (default-directory) ".") "TAGS"). locate-dominating-file stops at your home directory, too.

The code you posted does nothing with the tags-file variable. Did you mean to run the tag generation command in that directory, or failing that in the current directory? If so, this should do it:

(let ((default-directory (if tags-file (file-name-directory tags-file) default-directory)))
  (shell-command …))


From file type to shell commands

Rather than build in an exception for Haskell, the usual style would be to define an alist from extensions to commands.

(defvar tagariffic-command-alist
  '(("hs" . "… hasktags …")
    (t . "… etags …")))


However, there's a better approach than separate commands for each type; see below.

The shell commands

Your hasktags command will choke on file or directory names that contain shell special characters: \[?* and whitespace. This isn't very common in source trees, but it could happen. Generally speaking, never use command substitution to generate a list of file names. Instead, let find call the command.

find . -type f -name '*.hs' -exec hasktags --ignore-close-implementation --etags {} +


A second problem is that if there are too many files, the command you wrote will die of a command line too long error; the one I just showed will run hasktags several times with command lines of a permitted size, causing the tag file to be overwritten. As a workaround, start by creating an empty tags file and tell hasktags to append to the tags file.

: &gt;TAGS
find . -type f -name '*.hs' -exec hasktags --ignore-close-implementation -e -a {} +


An improvement to the find commands would be to ignore certain directories belonging to version control systems. Especially with svn, this could speed things up.

find . -type d \( -name .bzr -o -name .git -o -name .hg -o -name .svn -o -name CVS -o -name _darcs \) -prune -o \
       -type f …


Above I mentioned a different approach to choosing the tag generation command. Your approach assumes that a given project will be in a single language. It's easy enough to cater for mixed-language projects: build a single tags file containing the output generated by etags and hasktags.

: &gt;TAGS
find . -type d \( -name .bzr -o … \) -prune -o \
       -type f -name '*.hs' -exec hasktags -e -a {} + -o \
       -type f \( -name '*.[CScls]' -o -name '*.el' -o … \) -exec etags {} +