eintr: Files List

 
 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--"
      t
      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 ‘..’ (“dot 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 (SeeAccumulate), 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, add 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)