Next: , Previous: Sorting, Up: Prepare the data

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:

     ("abbrev.el"
     nil
     1
     1000
     100
     (20615 27034 579989 697000)
     (17905 55681 0 0)
     (20615 26327 734791 805000)
     13188
     "-rw-r--r--"
     nil
     2971624
     773)

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:

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 Recursive Pattern: 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-directory directory-files function takes one argument, the name of a directory.

Thus, on my system,

     (length
      (files-in-below-directory "/usr/local/share/emacs/22.1.1/lisp/"))

tells me that in and below my Lisp sources directory are 1031 ‘.el’ files.

files-in-below-directory returns a list in reverse alphabetical order. An expression to sort the list in alphabetical order looks like this:

     (sort
      (files-in-below-directory "/usr/local/share/emacs/22.1.1/lisp/")
      'string-lessp)