Mercurial > hg > xemacs-beta
diff lisp/utils/autoload.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | bcdc7deadc19 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/utils/autoload.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,544 @@ +;;; autoload.el --- maintain autoloads in loaddefs.el. + +;;; Copyright (C) 1991, 1992, 1993, 1994 Free Software Foundation, Inc. +;;; Copyright (C) 1995 Tinker Systems and INS Engineering Corp. +;;; Copyright (C) 1996 Ben Wing. + +;; Author: Roland McGrath <roland@gnu.ai.mit.edu> +;; Keywords: maint + +;;; This program 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 program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; A copy of the GNU General Public License can be obtained from this +;;; program's author (send electronic mail to roland@ai.mit.edu) or from +;;; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA +;;; 02139, USA. +;;; + +;;; Synched up with: FSF 19.30. + +;;; Commentary: + +;; This code helps GNU Emacs maintainers keep the loaddefs.el file up to +;; date. It interprets magic cookies of the form ";;;###autoload" in +;; lisp source files in various useful ways. To learn more, read the +;; source; if you're going to use this, you'd better be able to. + +;;; Code: + +(defun make-autoload (form file) + "Turn FORM, a defun or defmacro, into an autoload for source file FILE. +Returns nil if FORM is not a defun, define-skeleton or defmacro." + (let ((car (car-safe form))) + (if (memq car '(defun define-skeleton defmacro)) + (let ((macrop (eq car 'defmacro)) + name doc) + (setq form (cdr form) + name (car form) + ;; Ignore the arguments. + form (cdr (if (eq car 'define-skeleton) + form + (cdr form))) + doc (car form)) + (if (stringp doc) + (setq form (cdr form)) + (setq doc nil)) + (list 'autoload (list 'quote name) file doc + (or (eq car 'define-skeleton) + (eq (car-safe (car form)) 'interactive)) + (if macrop (list 'quote 'macro) nil))) + nil))) + +(put 'define-skeleton 'doc-string-elt 3) + +(defconst generate-autoload-cookie ";;;###autoload" + "Magic comment indicating the following form should be autoloaded. +Used by \\[update-file-autoloads]. This string should be +meaningless to Lisp (e.g., a comment). + +This string is used: + +;;;###autoload +\(defun function-to-be-autoloaded () ...) + +If this string appears alone on a line, the following form will be read and +an autoload made for it. If it is followed by the string \"immediate\", +then the form on the following will be copied verbatim. If there is further +text on the line, that text will be copied verbatim to +`generated-autoload-file'.") + +(defconst generate-autoload-section-header "\f\n;;;### " + "String inserted before the form identifying +the section of autoloads for a file.") + +(defconst generate-autoload-section-trailer "\n;;;***\n" + "String which indicates the end of the section of autoloads for a file.") + +;;; Forms which have doc-strings which should be printed specially. +;;; A doc-string-elt property of ELT says that (nth ELT FORM) is +;;; the doc-string in FORM. +;;; +;;; There used to be the following note here: +;;; ;;; Note: defconst and defvar should NOT be marked in this way. +;;; ;;; We don't want to produce defconsts and defvars that +;;; ;;; make-docfile can grok, because then it would grok them twice, +;;; ;;; once in foo.el (where they are given with ;;;###autoload) and +;;; ;;; once in loaddefs.el. +;;; +;;; Counter-note: Yes, they should be marked in this way. +;;; make-docfile only processes those files that are loaded into the +;;; dumped Emacs, and those files should never have anything +;;; autoloaded here. The above-feared problem only occurs with files +;;; which have autoloaded entries *and* are processed by make-docfile; +;;; there should be no such files. + +(put 'autoload 'doc-string-elt 3) +(put 'defun 'doc-string-elt 3) +(put 'defvar 'doc-string-elt 3) +(put 'defconst 'doc-string-elt 3) +(put 'defmacro 'doc-string-elt 3) + +(defun autoload-trim-file-name (file) + ;; returns a relative pathname of FILE including the last directory. + (setq file (expand-file-name file)) + (file-relative-name file + (file-name-directory + (directory-file-name + (file-name-directory file))))) + +;;;###autoload +(defun generate-file-autoloads (file &optional funlist) + "Insert at point a loaddefs autoload section for FILE. +autoloads are generated for defuns and defmacros in FILE +marked by `generate-autoload-cookie' (which see). +If FILE is being visited in a buffer, the contents of the buffer +are used." + (interactive "fGenerate autoloads for file: ") + (let ((outbuf (current-buffer)) + (autoloads-done '()) + (load-name (let ((name (file-name-nondirectory file))) + (if (string-match "\\.elc?$" name) + (substring name 0 (match-beginning 0)) + name))) + (dofiles (not (null funlist))) + (print-length nil) + (print-readably t) ; XEmacs + (float-output-format nil) + (done-any nil) + (visited (get-file-buffer file)) + output-end) + + ;; If the autoload section we create here uses an absolute + ;; pathname for FILE in its header, and then Emacs is installed + ;; under a different path on another system, + ;; `update-autoloads-here' won't be able to find the files to be + ;; autoloaded. So, if FILE is in the same directory or a + ;; subdirectory of the current buffer's directory, we'll make it + ;; relative to the current buffer's directory. + (setq file (expand-file-name file)) + (let* ((source-truename (file-truename file)) + (dir-truename (file-name-as-directory + (file-truename default-directory))) + (len (length dir-truename))) + (if (and (< len (length source-truename)) + (string= dir-truename (substring source-truename 0 len))) + (setq file (substring source-truename len)))) + + (message "Generating autoloads for %s..." file) + (save-excursion + (unwind-protect + (progn + (set-buffer (find-file-noselect file)) + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (while (if dofiles funlist (not (eobp))) + (if (not dofiles) + (skip-chars-forward " \t\n\f") + (goto-char (point-min)) + (re-search-forward + (concat "(def\\(un\\|var\\|const\\|macro\\) " + (regexp-quote (symbol-name (car funlist))) + "\\s ")) + (goto-char (match-beginning 0))) + (cond + ((or dofiles + (looking-at (regexp-quote generate-autoload-cookie))) + (if dofiles + nil + (search-forward generate-autoload-cookie) + (skip-chars-forward " \t")) + (setq done-any t) + (if (or dofiles (eolp)) + ;; Read the next form and make an autoload. + (let* ((form (prog1 (read (current-buffer)) + (or (bolp) (forward-line 1)))) + (autoload (make-autoload form load-name)) + (doc-string-elt (get (car-safe form) + 'doc-string-elt))) + (if autoload + (setq autoloads-done (cons (nth 1 form) + autoloads-done)) + (setq autoload form)) + (if (and doc-string-elt + (stringp (nth doc-string-elt autoload))) + ;; We need to hack the printing because the + ;; doc-string must be printed specially for + ;; make-docfile (sigh). + (let* ((p (nthcdr (1- doc-string-elt) + autoload)) + (elt (cdr p))) + (setcdr p nil) + (princ "\n(" outbuf) + ;; XEmacs change: don't let ^^L's get into + ;; the file or sorting is hard. + (let ((print-escape-newlines t) + (p (save-excursion + (set-buffer outbuf) + (point))) + p2) + (mapcar (function (lambda (elt) + (prin1 elt outbuf) + (princ " " outbuf))) + autoload) + (save-excursion + (set-buffer outbuf) + (setq p2 (point-marker)) + (goto-char p) + (save-match-data + (while (search-forward "\^L" p2 t) + (delete-char -1) + (insert "\\^L"))) + (goto-char p2) + )) + (princ "\"\\\n" outbuf) + (let ((begin (save-excursion + (set-buffer outbuf) + (point)))) + (princ (substring + (prin1-to-string (car elt)) 1) + outbuf) + ;; Insert a backslash before each ( that + ;; appears at the beginning of a line in + ;; the doc string. + (save-excursion + (set-buffer outbuf) + (save-excursion + (while (search-backward "\n(" begin t) + (forward-char 1) + (insert "\\")))) + (if (null (cdr elt)) + (princ ")" outbuf) + (princ " " outbuf) + (princ (substring + (prin1-to-string (cdr elt)) + 1) + outbuf)) + (terpri outbuf))) + ;; XEmacs change: another fucking ^L hack + (let ((p (save-excursion + (set-buffer outbuf) + (point))) + (print-escape-newlines t) + p2) + (print autoload outbuf) + (save-excursion + (set-buffer outbuf) + (setq p2 (point-marker)) + (goto-char p) + (save-match-data + (while (search-forward "\^L" p2 t) + (delete-char -1) + (insert "\\^L"))) + (goto-char p2) + )) + )) + ;; Copy the rest of the line to the output. + (let ((begin (point))) + (terpri outbuf) + (cond ((looking-at "immediate\\s *$") ; XEmacs + ;; This is here so that you can automatically + ;; have small hook functions copied to + ;; loaddefs.el so that it's not necessary to + ;; load a whole file just to get a two-line + ;; do-nothing find-file-hook... --Stig + (forward-line 1) + (setq begin (point)) + (forward-sexp) + (forward-line 1)) + (t + (forward-line 1))) + (princ (buffer-substring begin (point)) outbuf)))) + ((looking-at ";") + ;; Don't read the comment. + (forward-line 1)) + (t + (forward-sexp 1) + (forward-line 1))) + (if dofiles + (setq funlist (cdr funlist))))))) + (or visited + ;; We created this buffer, so we should kill it. + (kill-buffer (current-buffer))) + (set-buffer outbuf) + (setq output-end (point-marker)))) + (if t ;; done-any + ;; XEmacs -- always do this so that we cache the information + ;; that we've processed the file already. + (progn + (insert generate-autoload-section-header) + (prin1 (list 'autoloads autoloads-done load-name + (autoload-trim-file-name file) + (nth 5 (file-attributes file))) + outbuf) + (terpri outbuf) + (insert ";;; Generated autoloads from " + (autoload-trim-file-name file) "\n") + ;; Warn if we put a line in loaddefs.el + ;; that is long enough to cause trouble. + (while (< (point) output-end) + (let ((beg (point))) + (end-of-line) + (if (> (- (point) beg) 900) + (progn + (message "A line is too long--over 900 characters") + (sleep-for 2) + (goto-char output-end)))) + (forward-line 1)) + (goto-char output-end) + (insert generate-autoload-section-trailer))) + (or noninteractive ; XEmacs: only need one line in -batch mode. + (message "Generating autoloads for %s...done" file)))) + +(defconst generated-autoload-file (expand-file-name "../lisp/prim/loaddefs.el" + data-directory) + "*File \\[update-file-autoloads] puts autoloads into. +A .el file can set this in its local variables section to make its +autoloads go somewhere else.") + +(defvar generate-autoload-dynamic-but-inefficient nil + "If non-nil, `update-file-autoloads' will always read in its files. +This allows you to bind `generated-autoload-file' in your local variables +(do you really want to do that?) but makes it very slow in updating +lots of files.") + +;;;###autoload +(defun update-file-autoloads (file) + "Update the autoloads for FILE in `generated-autoload-file' +\(which FILE might bind in its local variables)." + (interactive "fUpdate autoloads for file: ") + ;; avoid horrid horrid problems with relative filenames. + (setq file (expand-file-name file default-directory)) + (let ((load-name (let ((name (file-name-nondirectory file))) + (if (string-match "\\.elc?$" name) + (substring name 0 (match-beginning 0)) + name))) + (trim-name (autoload-trim-file-name file)) + (found nil) + (pass 'first) + (existing-buffer (get-file-buffer file))) + (save-excursion + ;; We want to get a value for generated-autoload-file from + ;; the local variables section if it's there. + (and generate-autoload-dynamic-but-inefficient + (set-buffer (find-file-noselect file))) + (set-buffer (or (get-file-buffer generated-autoload-file) + (find-file-noselect generated-autoload-file))) + (save-excursion + (save-restriction + (widen) + (while pass + ;; This is done in two passes: + ;; 1st pass: Look for the section for LOAD-NAME anywhere in the file. + ;; 2st pass: Find a place to insert it. Use alphabetical order. + (goto-char (point-min)) + (while (and (not found) + (search-forward generate-autoload-section-header nil t)) + (let ((form (condition-case () + (read (current-buffer)) + (end-of-file nil)))) + (cond ((and (eq pass 'first) + (string= (nth 2 form) load-name)) + ;; We found the section for this file. + ;; Check if it is up to date. + (let ((begin (match-beginning 0)) + (last-time (nth 4 form)) + (file-time (nth 5 (file-attributes file)))) + (if (and (or (null existing-buffer) + (not (buffer-modified-p existing-buffer))) + (listp last-time) (= (length last-time) 2) + (or (> (car last-time) (car file-time)) + (and (= (car last-time) (car file-time)) + (>= (nth 1 last-time) + (nth 1 file-time))))) + (progn + (or noninteractive + ;; jwz: too loud in -batch mode + (message + "Autoload section for %s is up to date." + file)) + (setq found 'up-to-date)) + ;; Okay, we found it and it's not up to date... + (search-forward generate-autoload-section-trailer) + (delete-region begin (point)) + ;; if the file has moved, then act like it hasn't + ;; been found and then reinsert it alphabetically. + (setq found (string= trim-name (nth 3 form))) + ))) + ;; XEmacs change -- we organize by sub-directories + ;; so inserting new autoload entries is a bit tricky... + ((and (eq pass 'last) + (string< trim-name (nth 3 form))) + ;; We've come to a section alphabetically later than + ;; LOAD-NAME. We assume the file is in order and so + ;; there must be no section for LOAD-NAME. We will + ;; insert one before the section here. + (goto-char (match-beginning 0)) + (setq found 'new)) + ))) + (cond (found + (setq pass nil)) ; success -- exit loop + ((eq pass 'first) + (setq pass 'last)) + (t + ;; failure -- exit loop + (setq pass nil)))) + (or (eq found 'up-to-date) + ;; XEmacs -- don't do the following. If we do, then + ;; every time we update autoloads we have to search + ;; the whole file (yuck). +; (and (eq found 'new) +; ;; Check that FILE has any cookies before generating a +; ;; new section for it. +; (save-excursion +; (set-buffer (find-file-noselect file)) +; (save-excursion +; (widen) +; (goto-char (point-min)) +; (if (search-forward (concat "\n" +; generate-autoload-cookie) +; nil t) +; nil +; (if (interactive-p) +; (message file " has no autoloads")) +; t)))) + (generate-file-autoloads file)))) + (if (interactive-p) (save-buffer)) + (if (and (null existing-buffer) + (setq existing-buffer (get-file-buffer file))) + (kill-buffer existing-buffer))))) + +;;;###autoload +(defun update-autoloads-here () + "\ +Update sections of the current buffer generated by \\[update-file-autoloads]." + (interactive) + (let ((generated-autoload-file (buffer-file-name))) + (save-excursion + (goto-char (point-min)) + (while (search-forward generate-autoload-section-header nil t) + (let* ((form (condition-case () + (read (current-buffer)) + (end-of-file nil))) + (file (nth 3 form))) + ;; XEmacs change: if we can't find the file as specified, look + ;; around a bit more. + (cond ((and (stringp file) + (or (get-file-buffer file) + (file-exists-p file)))) + ((and (stringp file) + (save-match-data + (let ((loc (locate-file (file-name-nondirectory file) + load-path))) + (if (null loc) + nil + (setq loc (expand-file-name + (autoload-trim-file-name loc) + "..")) + (if (or (get-file-buffer loc) + (file-exists-p loc)) + (setq file loc) + nil)))))) + (t + (setq file (if (y-or-n-p (format "Can't find library `%s'; remove its autoloads? " + (nth 2 form) file)) + t + (condition-case () + (read-file-name + (format "Find `%s' load file: " + (nth 2 form)) + nil nil t) + (quit nil)))))) + (if file + (let ((begin (match-beginning 0))) + (search-forward generate-autoload-section-trailer) + (delete-region begin (point)))) + (if (stringp file) + (generate-file-autoloads file))))))) + +;;;###autoload +(defun update-directory-autoloads (dir) + "Run \\[update-file-autoloads] on each .el file in DIR." + (interactive "DUpdate autoloads for directory: ") + (let ((enable-local-eval nil)) + (mapcar 'update-file-autoloads + (directory-files dir t "^[^=].*\\.el$"))) + (if (interactive-p) + (save-excursion + (set-buffer (find-file-noselect generated-autoload-file)) + (save-buffer)))) + +;;;###autoload +(defun batch-update-autoloads () + "Update the autoloads for the files or directories on the command line. +Runs \\[update-file-autoloads] on files and \\[update-directory-autoloads] +on directories. Must be used only with -batch, and kills Emacs on completion. +Each file will be processed even if an error occurred previously. +For example, invoke `emacs -batch -f batch-update-autoloads *.el'." + (if (not noninteractive) + (error "batch-update-autoloads is to be used only with -batch")) + (let ((lost nil) + (args command-line-args-left) + (defdir default-directory) + (enable-local-eval nil)) ;Don't query in batch mode. + (message "Updating autoloads in %s..." generated-autoload-file) + (let ((frob (function + (lambda (file) + (condition-case lossage + (let ((default-directory defdir)) + (update-file-autoloads file)) + (error + (princ ">>Error processing ") + (princ file) + (princ ": ") + (if (fboundp 'display-error) + (display-error lossage nil) + (prin1 lossage)) + (princ "\n") + (setq lost t))))))) + (while args + (if (file-directory-p (expand-file-name (car args))) + (let ((rest (directory-files (car args) t "\\.el$"))) + (if noninteractive + (message "Processing directory %s..." (car args))) + (while rest + (funcall frob (car rest)) + (setq rest (cdr rest)))) + (funcall frob (car args))) + (setq args (cdr args)))) + (save-some-buffers t) + (message "Done") + (kill-emacs (if lost 1 0)))) + +(provide 'autoload) + +;;; autoload.el ends here