14.9.2 Making a List of Files

The recursive-lengths-list-many-files function requires a list
of files as its argument. For our test examples, we constructed such
a list by hand; but the Emacs Lisp source directory is too large for
us to do for that. Instead, we will write a function to do the job
for us. In this function, we will use both a while loop and a
recursive call.

We did not have to write a function like this for older versions of
GNU Emacs, since they placed all the ‘.el’ files in one
directory. Instead, we were able to use the directory-files
function, which lists the names of files that match a specified
pattern within a single directory.

However, recent versions of Emacs place Emacs Lisp files in
sub-directories of the top level lisp directory. This
re-arrangement eases navigation. For example, all the mail related
files are in a lisp sub-directory called mail. But at
the same time, this arrangement forces us to create a file listing
function that descends into the sub-directories.

We can create this function, called files-in-below-directory,
using familiar functions such as car, nthcdr, and
substring in conjunction with an existing function called
directory-files-and-attributes. This latter function not only
lists all the filenames in a directory, including the names
of sub-directories, but also their attributes.

To restate our goal: to create a function that will enable us
to feed filenames to recursive-lengths-list-many-files
as a list that looks like this (but with more elements):

("./lisp/macros.el"
"./lisp/mail/rmail.el"
"./lisp/makesum.el")

The directory-files-and-attributes function returns a list of
lists. Each of the lists within the main list consists of 13
elements. The first element is a string that contains the name of the
file—which, in GNU/Linux, may be a `directory file', that is to
say, a file with the special attributes of a directory. The second
element of the list is t for a directory, a string
for symbolic link (the string is the name linked to), or nil.

For example, the first ‘.el’ file in the lisp/ directory
is abbrev.el. Its name is
/usr/local/share/emacs/22.1.1/lisp/abbrev.el and it is not a
directory or a symbolic link.

This is how directory-files-and-attributes lists that file and
its attributes:

On the other hand, mail/ is a directory within the lisp/
directory. The beginning of its listing looks like this:

("mail"
t
...
)

(To learn about the different attributes, look at the documentation of
file-attributes. Bear in mind that the file-attributes
function does not list the filename, so its first element is
directory-files-and-attributes's second element.)

We will want our new function, files-in-below-directory, to
list the ‘.el’ files in the directory it is told to check, and in
any directories below that directory.

This gives us a hint on how to construct
files-in-below-directory: within a directory, the function
should add ‘.el’ filenames to a list; and if, within a directory,
the function comes upon a sub-directory, it should go into that
sub-directory and repeat its actions.

However, we should note that every directory contains a name that
refers to itself, called ., (“dot”) and a name that refers to
its parent directory, called .. (“double dot”). (In
/, the root directory, .. refers to itself, since
/ has no parent.) Clearly, we do not want our
files-in-below-directory function to enter those directories,
since they always lead us, directly or indirectly, to the current
directory.

Consequently, our files-in-below-directory function must do
several tasks:

Check to see whether it is looking at a filename that ends in
‘.el’; and if so, add its name to a list.

Check to see whether it is looking at a filename that is the name of a
directory; and if so,

Check to see whether it is looking at . or ..; and if
so skip it.

Or else, go into that directory and repeat the process.

Let's write a function definition to do these tasks. We will use a
while loop to move from one filename to another within a
directory, checking what needs to be done; and we will use a recursive
call to repeat the actions on each sub-directory. The recursive
pattern is `accumulate'
(see Accumulate),
using append as the combiner.

Here is the function:

(defun files-in-below-directory (directory)
"List the .el files in DIRECTORY and in its sub-directories."
;; Although the function will be used non-interactively,
;; it will be easier to test if we make it interactive.
;; The directory will have a name such as
;; "/usr/local/share/emacs/22.1.1/lisp/"
(interactive "DDirectory name: ")
(let (el-files-list
(current-directory-list
(directory-files-and-attributes directory t)))
;; while we are in the current directory
(while current-directory-list
(cond
;; check to see whether filename ends in `.el'
;; and if so, append its name to a list.
((equal ".el" (substring (car (car current-directory-list)) -3))
(setq el-files-list
(cons (car (car current-directory-list)) el-files-list)))
;; check whether filename is that of a directory
((eq t (car (cdr (car current-directory-list))))
;; decide whether to skip or recurse
(if
(equal "."
(substring (car (car current-directory-list)) -1))
;; then do nothing since filename is that of
;; current directory or parent, "." or ".."
()
;; else descend into the directory and repeat the process
(setq el-files-list
(append
(files-in-below-directory
(car (car current-directory-list)))
el-files-list)))))
;; move to the next filename in the list; this also
;; shortens the list so the while loop eventually comes to an end
(setq current-directory-list (cdr current-directory-list)))
;; return the filenames
el-files-list))

The files-in-below-directorydirectory-files function
takes one argument, the name of a directory.