Download
(require 'loadhist)
(eval-when-compile (when (< emacs-major-version 21) (require 'cl)))
(defgroup Library-Dependencies nil
"Commands to list Emacs-Lisp library dependencies."
:prefix "libreq-"
:group 'tools :group 'files
:link `(url-link :tag "Send Bug Report"
,(concat "mailto:" "drew.adams" "@" "oracle" ".com?subject=\
lib-requires.el bug: \
&body=Describe bug here, starting with `emacs -q'. \
Don't forget to mention your Emacs and library versions."))
:link '(url-link :tag "Other Libraries by Drew"
"http://www.emacswiki.org/cgi-bin/wiki/DrewsElispLibraries")
:link '(url-link :tag "Download"
"http://www.emacswiki.org/cgi-bin/wiki/lib-requires.el")
:link '(url-link :tag "Description"
"http://www.emacswiki.org/cgi-bin/wiki/LibraryDependencies#LibRequires")
:link '(emacs-commentary-link :tag "Commentary" "lib-requires")
)
(defcustom libreq-file-header
";; Features that might be required by this library:\n;;\n"
"*Header inserted by `libreq-insert-lib-requires-as-comment'."
:type 'string
:group 'Automatic-File-Header :group 'development :group 'programming)
(defun libreq-requires-tree (library &optional cumul)
"The features `require'd by LIBRARY, as a tree.
The tree structure shows library dependencies: Each feature is
represented by its name or by a list of its name followed by the
features that it explicitly requires.
Argument LIBRARY is an Emacs-Lisp file name, or file name sans
extension. This command loads LIBRARY before determining its
dependencies. This means that LIBRARY must contain (provide LIBRARY).
If it does not, an error is raised.
Function `libreq-requires-tree' calls itself recursively on its
dependencies, so an attempt is made to load all of them.
Note: If a byte-compiled (`*.elc') version of a library is
available, it is loaded, in preference to the source library -
this is the standard behavior of `load-library'. This means that
the tree of required features reflects the dependencies indicated
in the byte-compiled file, not the source file. If the
byte-compiled file is out-of-date, so will be the result of
`libreq-requires-tree'.
A required feature that was loaded successfully is represented by a
string that names the required feature.
A required file or feature that failed to load is represented by a
symbol that names the required feature.
For example: Suppose that library `doremi.el' requires `ring+' and
`mwheel', and library `ring+' requires `ring'. If `ring+' is
successfully loaded and `mwheel.el' is not, then the result is this:
(mwheel (\"ring+\" (\"ring\")))
Argument CUMUL is used only for recursive calls, to accumulate the
required features.
See also command `libreq-requires-list'.
Note that `libreq-requires-tree' and `libreq-requires-list' are
roughly the opposite of `file-dependents' in library `loadhist'."
(interactive (list (file-name-sans-extension
(file-name-nondirectory (read-file-name "Library :")))))
(if (not library)
nil
(when (stringp library) (setq library (intern library)))
(require library)
(when (and (>= emacs-major-version 22) (not (fboundp 'find-library-name)))
(require 'find-func))
(let ((libraries ())
(reqd-lib nil))
(dolist (reqd-rec (file-requires (symbol-name library)) libraries)
(setq reqd-lib (if (consp reqd-rec) (cdr reqd-rec) reqd-rec))
(if (not (featurep reqd-lib))
(push reqd-lib libraries)
(let ((reqd-lib-requires-tree
(and (not (eq library reqd-lib))
(not (member reqd-lib cumul))
(libreq-requires-tree reqd-lib (cons library cumul)))))
(if reqd-lib-requires-tree
(push (cons (symbol-name reqd-lib) reqd-lib-requires-tree)
libraries)
(push (symbol-name reqd-lib) libraries)))))
(when (interactive-p) (pp-eval-expression (quote libraries)))
libraries)))
(defun libreq-requires-list (library)
"The libraries ultimately `require'd by LIBRARY, as a flat list.
Each library (file or feature) is represented only once, and the list
is sorted.
A library is represented as for `libreq-requires-tree': a file-name
string for a successfully loaded required library, a feature-name
symbol for an unsuccessfully loaded required feature.
LIBRARY must contain (provide LIBRARY); otherwise, an error is raised.
Note that `libreq-requires-tree' and `libreq-requires-list' are
essentially the opposite of `file-dependents' in library `loadhist'."
(interactive (list (file-name-sans-extension
(file-name-nondirectory (read-file-name "Library :")))))
(let ((libraries
(sort (libreq-remove-duplicates
(libreq-flatten (libreq-requires-tree library)))
#'string-lessp)))
(when (interactive-p) (pp-eval-expression (quote libraries)))
libraries))
(defun libreq-insert-lib-requires-as-comment (library)
"Insert a comment listing all libraries ultimately required by LIBRARY.
See also `libreq-requires-list' and `libreq-requires-tree'."
(interactive (list (file-name-sans-extension
(file-name-nondirectory (read-file-name "Library:")))))
(let ((requires (libreq-requires-list library))
(comment-style 'plain))
(save-excursion
(beginning-of-line)
(insert libreq-file-header)
(if (not requires)
(insert ";; None\n;;\n")
(let ((beg (point))
(fill-column (- fill-column 4)))
(mapc (lambda (feat) (insert (format "`%s', " feat))) requires)
(backward-delete-char 2)
(insert ".\n")
(let ((left-margin 2)) (fill-region-as-paragraph beg (point)))
(comment-region beg (point) 2))
(insert ";;\n")))))
(defun libreq-flatten (list)
"Flatten LIST, returning a list with the atoms in LIST at any level.
Also works for a consp whose cdr is non-nil."
(cond ((null list) nil)
((atom list) list)
(t
(let ((old list)
(new ())
item)
(while old
(if (atom old)
(setq item old
old nil)
(setq item (car old)
old (cdr old)))
(while (consp item)
(if (cdr item)
(setq old (cons (cdr item) old)))
(setq item (car item)))
(setq new (cons item new)))
(reverse new)))))
(when (and (fboundp 'cl-puthash) (not (fboundp 'puthash)))
(defalias 'puthash 'cl-puthash))
(if (fboundp 'puthash)
(defun libreq-remove-duplicates (sequence &optional test)
"Copy of SEQUENCE with duplicate elements removed.
Optional arg TEST is the test function. If nil, test with `equal'.
See `make-hash-table' for possible values of TEST."
(setq test (or test #'equal))
(let ((htable (make-hash-table :test test)))
(loop for elt in sequence
unless (gethash elt htable)
do (puthash elt elt htable)
finally return (loop for i being the hash-values in htable collect i))))
(defun libreq-remove-duplicates (list &optional use-eq)
"Copy of LIST with duplicate elements removed.
Test using `equal' by default, or `eq' if optional USE-EQ is non-nil."
(let ((tail list)
new)
(while tail
(unless (if use-eq (memq (car tail) new) (member (car tail) new))
(push (car tail) new))
(pop tail))
(nreverse new))))
(provide 'lib-requires)