Find file
Fetching contributors…
Cannot retrieve contributors at this time
636 lines (456 sloc) 17.7 KB
;;; write-subdirs-el.el --- Create subdirs.el files
;; Copyright (C) 2000 by Tom Breton
;; Author: Tom Breton <>
;; Keywords: local
;; Version 1.2
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; This code creates subdirs.el files for directory trees.
;;; Motivation
;; Why do that, you ask? By way of explanation, subdirectories of
;; site-lisp automatically become part of the load-path when you start
;; Emacs. That makes site-lisp an appealing place to put Elisp
;; packages.
;; But there is more subdirs.el could do for you. For instance, it
;; could set up Info-default-directory-list and autoloads.
;; As I write this, package maintainers generally don't use
;; subdirs.el. Perhaps they mostly don't know about it. I hope
;; write-subdirs-el will help change that.
;; I have a vision of a thousand elisp packages that require no more
;; setup than being placed in site-lisp. (And sometimes a
;; command-line "make"). And I imagine a thousand package maintainers
;; who can provide easy installation for their users with little or no
;; more work then typing M-x tehom-wse-entry.
;;; Installation:
;; Place write-subdirs-el anywhere in your load-path
;; IMPORTANT: Replace your existing site-lisp/subdirs.el with
;; new-subdirs-el.el. That is, put site-lisp/subdirs.el somewhere
;; safe and rename new-subdirs-el.el to that. If you use the old
;; version and build subdirs.els with this package, some directory
;; trees (those that have no Elisp code in their root directories, but
;; do have Elisp in their subdirectories) will be unexpectedly omitted
;; from load-path.
;; If you can't load a subdirs.el that's not hanging off site-lisp,
;; it's a known bug that's fixed by new-subdirs-el.el
;;; Customizations:
;; If you want it to generate autoloads, customize
;; tehom-wse-do-autoloads-p to t.
;; WARNING: If the autoloads don't work, emacs won't start. That's
;; not under my control, it's up to the maintainers of the individual
;; packages,
;;; Entry points:
;; IMPORTANT: You will need to have read and write permissions to all
;; the directories in question. If not, nothing will happen.
;; tehom-wse-do-all-dirs-in-site-lisp will tackle every single
;; directory in site-lisp in one command. You'll probably want to run
;; this exactly once.
;; tehom-wse-make-subdirs-el will make subdirs.el for any given
;; directory tree.
;; tehom-wse-entry will make a subdirs.el for a directory in your
;; load-path. It's a little more convenient than navigating your
;; load-path manually.
;;; Testing
;; This has not been easy to test. Testing it properly requires
;; dealing with a whole test directory structure. The standard elisp
;; distribution directory could perhaps be used, but mine is
;; non-standard. I could create a whole directory tree, but that's a
;; lot of work just to test one part of this. Testing it properly
;; also requires starting emacs a lot of times. I've done that to a
;; degree, but obviously automated testing was out of the question.
;; So while I've given it what testing I could, there's a bit of a
;; cross-your-fingers-and-hope factor. PLEASE inspect any subdirs.el
;; it writes and if there are obvious problems, don't use it. (ie,
;; move it somewhere out of your load-path)
;; IMPORTANT: At worst, your system can be restored to the way it was
;; by restoring your old site-lisp/subdirs.el and erasing any
;; subdirs.el and .nosearch files in site-lisp's subdirectories. I
;; don't think that will ever be neccessary, but I wanted to point it
;; out just in case.
;;; Non-features:
;; If a subdirectory being examined itself contains a subdirs.el, that
;; directory could be handled by a call to that file, nothing more.
;; NB we'd have to exclude the directory tree root from this,
;; otherwise we could possibly call our file itself in an infinite
;; loop. This probably isn't worth doing, because no packages are so
;; deep they would get any use out of nested subdirs.el's
;; Changelog:
;; Version 1.2: Provided code in new-subdirs-el.el to get around
;; startup's inability to handle working from directories that aren't
;; already in load-path.
;; Version 1.1: Fixed the bug where Info-path was sometimes missed.
;; Changed the output to add some appropriate local variables
;; including no-update-autoloads.
;;; Code:
(require 'cl)
(require 'autoload)
(defcustom tehom-wse-do-autoloads-p nil
"Do autoloads"
:type 'boolean)
;;;; Constants
(defconst tehom-wse-filename "subdirs.el" "" )
(defconst tehom-directory-meanings-alist
("lisp" . lisp)
("contrib" . lisp)
("info" . info)
("texi" . info)
("texinfo" . info))
"The meanings of canonical directory names." )
;;Helper functions:
;;Helpers for entry points
(defun tehom-wse-get-load-path-matching (regex)
"Return a list of names in load-path that match REGEX."
for dir-name in load-path
(string-match regex dir-name)
collect dir-name))
;;Helpers for command functions
(defun tehom-force-file-to-exist (filename)
(file-exists-p filename)
(write-region "" nil filename)))
(defun tehom-remove-file-if-exists (filename)
(file-exists-p filename)
(delete-file filename)))
;;Helpers for collector functions.
(defun tehom-filter-strings-matching (string-list regex)
"Return a list of strings in STRING-LIST matching REGEX."
for string in string-list
(string-match regex string)
collect string))
(defun tehom-any-elisp (filename-list)
"Return non-nil if any names in FILENAME-LIST look like Elisp executables.
subdirs.el itself doesn't count."
;;Don't let subdirs.el make a directory look like it has Elisp.
(remove* "subdirs.el" filename-list :test #'string= )))
(tehom-filter-strings-matching filename-list-1 "\\.elc?\\'")))
;;Finding info files is iffy, but we do our best.
(defun tehom-any-info-files (filename-list)
"Return non-nil if any names in FILENAME-LIST look like info files."
(tehom-filter-strings-matching filename-list "\\.info?\\'")
(tehom-filter-strings-matching filename-list "\\-[0-9]+\\'")))
;;Used to collect source files for autoloads
(defun tehom-collect-elisp-sources (filename-list)
"Return the names from FILENAME-LIST that look like Elisp source code."
;;Don't treat subdirs.el like normal source, nor the temp files
;;emacs makes for the open subdirs.el files.
(remove* "subdirs.el$" filename-list :test #'string-match )))
(tehom-filter-strings-matching filename-list-1 "\\.el\\'")))
;;Borrowed from startup.el and encapsulated.
(defun tehom-wse-name-is-nice-directory (name)
"Return non-nil if NAME looks like a nice directory.
Nice here means that its name strts with an alphanumeric character and
it doesn't look like Elisp code."
(string-match "\\`[a-zA-Z0-9]" name)
;; Avoid doing a `stat' when it isn't necessary
;; because that can cause trouble when an NFS server
;; is down.
(not (string-match "\\.elc?\\'" name))
(file-directory-p name)))
;;tehom-wse-name-is-nice-directory needs to test base-names, but
;;tehom-wse-collect-nice-subdirectories needs to return multi-component
;;relative names. So be very careful about how we treat expansion.
(defun tehom-wse-collect-nice-subdirectories (directory)
"Collect all \"nice\" subdirectories of DIRECTORY.
DIRECTORY is relative to default-directory."
;;Generate base-names.
(filename-list (directory-files directory nil nil t))
;;Still all base-names
(remove-if-not #'tehom-wse-name-is-nice-directory filename-list)))
;;Expand each name wrt DIRECTORY, then make it relative to the
;;top directory.
#'(lambda (base-name)
(file-relative-name (expand-file-name base-name directory)))
;;Path collector functions
(defun tehom-wse-examine-homogeneous-subdirectories (directory type)
"Examine DIRECTORY and its subdirectories, treating TYPE as unchanging."
(tehom-wse-collect-nice-subdirectories directory))
(tehom-wse-examine-directory subdirectory type)))
(defun tehom-wse-examine-directory (directory type)
"Examine DIRECTORY and its subdirectories, according to TYPE."
((filename-list (directory-files directory)))
(declare (special add-to-load-path add-to-info-path))
(case type
(push directory add-to-load-path)
;;Treat all its subdirectories as lisp.
(tehom-wse-examine-homogeneous-subdirectories directory 'lisp))
(push directory add-to-info-path)
;;Treat all its subdirectories as info.
(tehom-wse-examine-homogeneous-subdirectories directory 'info))
;;Try all the possibilities. More than one can be used.
(tehom-any-elisp filename-list)
(push directory add-to-load-path))
(tehom-any-info-files filename-list)
(push directory add-to-info-path))
;;Recurse into each subdirectory.
(tehom-wse-collect-nice-subdirectories directory))
;;Classify the subdirectory if possible.
(assoc subdirectory
(tehom-wse-examine-directory subdirectory subdirectory-type)))
;;Manages the special variables add-to-load-path and add-to-info-path
(defun tehom-wse-examine-directory-tree ()
"Return what directories to add to load-path and to info path."
(add-to-load-path '())
(add-to-info-path '()))
(declare (special add-to-load-path add-to-info-path))
(tehom-wse-examine-directory "." 'unclassified)
(values add-to-load-path add-to-info-path)))
;;Output functions
(defun tehom-wse-insert-all
(add-to-load-path add-to-info-path make-autoloads-list)
;; subdirs.el
;; Generated on "
(format-time-string "%a %e %b, %Y %l:%M %p")
" by "
;; By write-subdirs-el, written by Tehom (Tom Breton)
add-to-load-path add-to-info-path make-autoloads-list))
(insert ";;Nothing to do\n")
(tehom-wse-insert-load-path add-to-load-path)
(tehom-wse-insert-info-path add-to-info-path)
(tehom-wse-insert-autoloads make-autoloads-list)))
;; Local Variables:
;; version-control: never
;; no-byte-compile: t
;; no-update-autoloads: t
;; End:
(defun tehom-wse-insert-load-path (add-to-load-path)
"Insert Elisp code to add the list ADD-TO-LOAD-PATH to load-path."
(when add-to-load-path
`(normal-top-level-add-to-load-path ',add-to-load-path)
(defun tehom-wse-insert-info-path (add-to-info-path)
"insert Elisp code to add the list ADD-TO-INFO-PATH to info path."
(dolist (path add-to-info-path)
`(add-to-list 'Info-default-directory-list
(expand-file-name ,path))
(defun tehom-wse-insert-autoloads (add-to-load-path)
"Insert autoloads."
(path add-to-load-path)
((expanded (expand-file-name path)))
(directory-files expanded)))
(expand-file-name source-file expanded))))))
;;Managing function
(defun tehom-wse-write-subdirs-el ()
"Write subdirs.el code into the current buffer.
Also manage the existence of `.nosearch'."
(add-to-load-path add-to-info-path)
(nosearch-filename ".nosearch")
(find "." add-to-load-path :test #'string=))
(if tehom-wse-do-autoloads-p
;;.nosearch, not subdirs.el, will indicate whether to put this
;;directory itself on load-path. So we remove "." if it was
;;there, and in either case we set .nosearch accordingly.
;;But we mustn't omit that from autoloads.
(setq add-to-load-path
(remove* "." add-to-load-path :test #'string= ))
(tehom-remove-file-if-exists nosearch-filename))
(tehom-force-file-to-exist nosearch-filename))
add-to-load-path add-to-info-path make-autoloads-list))))
;;Entry points
(defun tehom-wse-make-subdirs-el
(directory &optional force-autoloads force)
"Make a subdirs.el file for the given directory tree."
(interactive "DMake subdirs.el in which directory? \nP")
(string= directory "")
( (default-directory directory)
(or tehom-wse-do-autoloads-p force-autoloads))
(not (file-exists-p tehom-wse-filename)))))
(find-file tehom-wse-filename)
(defun tehom-wse-make-subdirs-el-force (directory &optional force-autoloads)
(interactive "DMake subdirs.el in which directory? \nP")
(tehom-wse-make-subdirs-el directory force-autoloads t))
(defun tehom-wse-entry (&optional force-autoloads)
"Call tehom-wse-make-subdirs-el with a directory from load-path."
(interactive "P")
(completing-read "Pick one: "
(mapcar #'list load-path))))
(string= directory "")
(tehom-wse-make-subdirs-el directory force-autoloads))))
;; It wouldn't be hard to make another entry point to pick exactly
;; subdirectories of site-lisp, if that proves more useful than
;; tehom-wse-entry. Just use completing-read on:
;; (tehom-wse-get-load-path-matching "site-lisp\.[^/]+$")
(defun tehom-wse-do-all-dirs-in-site-lisp (&optional force)
"Make subdirs.el for every immediate subdirectory of site-lisp.
If there is more than one */site-lisp in load-path, prompt for which
one to use.
This calls tehom-wse-make-subdirs-el on every immediate subdirectory
of site-lisp."
(interactive "P")
(tehom-wse-get-load-path-matching "site-lisp$"))
(case (length potential-site-lisp-roots)
(0 "")
(1 (car potential-site-lisp-roots))
(completing-read "Which site-lisp: "
(mapcar #'list potential-site-lisp-roots))))))
(unless (string= site-lisp-root "")
( ;;Important: Bind default-directory around this list-form
(default-directory site-lisp-root)
(tehom-wse-collect-nice-subdirectories ".")))
(base-directory-name directories)
(expand-file-name base-directory-name)))
(tehom-wse-make-subdirs-el directory force)))))))
;; Design notes:
;;First stage: Crawl thru directories, collecting paths
;;Second stage: Output what we have.
;;Third stage: When emacs starts up, the subdirs.els will get run.
;;What we look for when examining directories
;;Directories are classified either according to their contents or
;;according to their name.
;;Look for .el and .elc files. If seen, the directory contains Elisp
;;(and maybe more)
;;Look for files ending in .info or -[0-9]+ and consider them info
;;files. If seen, the directory contains info (and maybe more)
;;Look for a "lisp" or "contrib" subdirectories and treat them as
;;containing all Elisp
;;Look for "info", "texi", "texinfo" subdirectories and treat them as
;;containing all info
;;Simply recurse into other directories.
;;The returned paths should always be relative to the starting
;;directory. That way the directory-tree can be moved around without
;;consequence, and maintainers can generate a subdirs.el without
;;knowing the layout of the target directory structure.
;;What to write in subdirs.el
;;Write additions to load-path.
;;Write additions to Info-default-directory-list
;;Make autoloads for all .el files in any directory we're adding to
;;normal-top-level-add-subdirs-to-load-path (Not here, but important)
;; normal-top-level-add-subdirs-to-load-path adds dirs as they come
;; off the pending list. dirs with subdirs.el could
;; a) add themselves, but not be pending. Wrong, because they
;; themselves may not want to be part of load-path.
;; b) execute the subdirs.el directly. This may cause subdirs.el to
;; be executed twice, once by this, once when found in load-path.
;; c) execute it only if it's not going to be executed later, meaning
;; if it's not being put in load-path. Trust subdirs.el to not cause
;; itself problems by (wrong if .nosearch exists) putting its own
;; directory in load-path.
;;End design notes
;;; write-subdirs-el.el ends here