Mercurial > emacs
changeset 112281:697cfa263439
info-xref.el Version 3.
* lisp/info-xref.el (info-xref-check, info-xref-check-all): Move
commentary details into docstrings for better visibility.
Use compilation-mode for the results buffer.
(info-xref-output, info-xref-output-error, info-xref-with-output)
(info-xref-filename, info-xref-in-progress):
New internals for this.
(info-xref-check-list, info-xref-check-buffer)
(info-xref-check-all-custom): Use those.
(info-xref-output-buffer): Rename from info-xref-results-buffer.
(info-xref-output-heading): Rename from info-xref-filename-heading.
(info-xref-good, info-xref-bad, info-xref-xfile-alist)
(info-xref-filename-heading): Move to output managing section.
(info-xref-docstrings): New command checking "Info node `(foo)Bar'"
(info-xref-lock-file-p, info-xref-with-file): New helpers for it.
(info-xref-subfile-p): Move to generic section with those two.
(info-xref-check-node): New function split from
info-xref-check-buffer, shared by info-xref-docstrings.
(info-xref-goto-node-p): Move to a checking section with that func.
(info-xref-unavail): New counter.
(info-xref-check-node): Use it.
(info-xref-with-output): Show count of unavailables at end of output.
(info-xref-all-info-files): Exclude ".*" dotfiles. Ignore broken
symlinks. Exclude .texi files. Exclude Emacs backup files.
(info-xref-check-all-custom): Fix quietening viper-mode and
gnus-registry-install -- use setq not let so as not to unbind after load.
author | Glenn Morris <rgm@gnu.org> |
---|---|
date | Sat, 15 Jan 2011 17:59:33 -0800 |
parents | a0a140d8d84d |
children | bcf4b132f3d5 |
files | lisp/ChangeLog lisp/info-xref.el |
diffstat | 2 files changed, 474 insertions(+), 233 deletions(-) [+] |
line wrap: on
line diff
--- a/lisp/ChangeLog Sun Jan 16 01:04:22 2011 +0000 +++ b/lisp/ChangeLog Sat Jan 15 17:59:33 2011 -0800 @@ -1,3 +1,33 @@ +2011-01-16 Kevin Ryde <user42@zip.com.au> + + * info-xref.el: Version 3. + (info-xref-check, info-xref-check-all): Move commentary details + into docstrings for better visibility. + Use compilation-mode for the results buffer. + (info-xref-output, info-xref-output-error, info-xref-with-output) + (info-xref-filename, info-xref-in-progress): + New internals for this. + (info-xref-check-list, info-xref-check-buffer) + (info-xref-check-all-custom): Use those. + (info-xref-output-buffer): Rename from info-xref-results-buffer. + (info-xref-output-heading): Rename from info-xref-filename-heading. + (info-xref-good, info-xref-bad, info-xref-xfile-alist) + (info-xref-filename-heading): Move to output managing section. + (info-xref-docstrings): New command checking "Info node `(foo)Bar'" + (info-xref-lock-file-p, info-xref-with-file): New helpers for it. + (info-xref-subfile-p): Move to generic section with those two. + (info-xref-check-node): New function split from + info-xref-check-buffer, shared by info-xref-docstrings. + (info-xref-goto-node-p): Move to a checking section with that func. + (info-xref-unavail): New counter. + (info-xref-check-node): Use it. + (info-xref-with-output): Show count of unavailables at end of output. + (info-xref-all-info-files): Exclude ".*" dotfiles. Ignore broken + symlinks. Exclude .texi files. Exclude Emacs backup files. + (info-xref-check-all-custom): Fix quietening viper-mode and + gnus-registry-install -- use setq not let so as not to unbind + after load. + 2011-01-16 Juri Linkov <juri@jurta.org> * isearch.el (isearch-abort): Don't quit if search has
--- a/lisp/info-xref.el Sun Jan 16 01:04:22 2011 +0000 +++ b/lisp/info-xref.el Sat Jan 15 17:59:33 2011 -0800 @@ -5,6 +5,7 @@ ;; Author: Kevin Ryde <user42@zip.com.au> ;; Keywords: docs +;; Version: 3 ;; This file is part of GNU Emacs. @@ -23,209 +24,172 @@ ;;; Commentary: -;; This file implements some simple checking of external cross references in -;; info files, by attempting to visit the nodes specified. -;; -;; "makeinfo" checks references internal to a document, but not external -;; references, which makes it rather easy for mistakes to creep in or node -;; name changes to go unnoticed. `Info-validate' doesn't check external -;; references either. +;; This is some simple checking of external cross references in info files, +;; docstrings and custom-links by attempting to visit the nodes specified. ;; -;; `M-x info-xref-check' checks one file. When invoked from an Info-mode or -;; texinfo-mode buffer, the current info file is the default at the prompt. +;; `M-x info-xref-check' checks a single info file. See the docstring for +;; details. ;; -;; `M-x info-xref-check-all' looks at everything in the normal info path. -;; This might be a lot of files but it's a good way to check the consistency -;; of the whole system. -;; -;; Results are shown in a buffer. The format is a bit rough, but hopefully -;; there won't be too many problems normally, and correcting them is a -;; manual process anyway, a case of finding the right spot in the original -;; .texi and finding what node it ought to point to. +;; `M-x info-xref-check-all' checks all info files in Info-directory-list. +;; This is a good way to check the consistency of the whole system. ;; -;; When a target info file doesn't exist there's clearly no way to validate -;; node references within it. A message is given for missing target files -;; (once per source document), it could be simply that the target hasn't -;; been installed, or it could be a mistake in the reference. -;; -;; Indirect info files are understood, just pass the top-level foo.info to -;; `info-xref-check' and it traverses all sub-files. Compressed info files -;; are accepted too, as usual for `Info-mode'. +;; `M-x info-xref-check-all-custom' loads up all defcustom variables and +;; checks any info references in them. ;; -;; `info-xref-check-all' is rather permissive in what it considers an info -;; file. It has to be since info files don't necessarily have a ".info" -;; suffix (eg. this is usual for the emacs manuals). One consequence of -;; this is that if for instance there's a source code directory in -;; `Info-directory-list' then a lot of extraneous files might be read, which -;; will be time consuming but should be harmless. -;; -;; `M-x info-xref-check-all-custom' is a related command, it goes through -;; all info document references in customizable variables, checking them -;; like info file cross references. +;; `M-x info-xref-docstrings' checks docstring "Info node ..." hyperlinks in +;; source files (and other files). + +;;; History: + +;; Version 3 - new M-x info-xref-docstrings, use compilation-mode ;;; Code: (require 'info) - -(defconst info-xref-results-buffer "*info-xref results*" - "Name of the buffer for info-xref results.") - -;;;###autoload -(defun info-xref-check (filename) - "Check external references in FILENAME, an info document." - (interactive - (list - (let* ((default-filename - (cond ((eq major-mode 'Info-mode) - Info-current-file) - ((eq major-mode 'texinfo-mode) - ;; look for @setfilename like makeinfo.el does - (save-excursion - (goto-char (point-min)) - (if (re-search-forward - "^@setfilename[ \t]+\\([^ \t\n]+\\)[ \t]*" - (line-beginning-position 100) t) - (expand-file-name (match-string 1))))))) - (prompt (if default-filename - (format "Info file (%s): " default-filename) - "Info file: "))) - (read-file-name prompt nil default-filename t)))) - (info-xref-check-list (list filename))) +(eval-when-compile + (require 'cl)) ;; for `incf' -;;;###autoload -(defun info-xref-check-all () - "Check external references in all info documents in the usual path. -The usual path is `Info-directory-list' and `Info-additional-directory-list'." - (interactive) - (info-xref-check-list (info-xref-all-info-files))) - -;; An alternative to trying to get only top-level files here would be to -;; simply return all files, and have info-xref-check-list not follow -;; Indirect:. The current way seems a bit nicer though, because it gets the -;; proper top-level filename into the error messages, and suppresses -;; duplicate "not available" messages for all subfiles of a single document. +;;----------------------------------------------------------------------------- +;; vaguely generic -(defun info-xref-all-info-files () - "Return a list of all available info files. -Only top-level files are returned, subfiles are excluded. - -Since info files don't have to have a .info suffix, all files in the -relevant directories are considered, which might mean a lot of extraneous -things are returned if for instance a source code directory is in the path." - - (info-initialize) ;; establish Info-directory-list - (apply 'nconc - (mapcar - (lambda (dir) - (let ((result nil)) - (dolist (name (directory-files dir t)) - (unless (or (file-directory-p name) (info-xref-subfile-p name)) - (push name result))) - (nreverse result))) - (append Info-directory-list Info-additional-directory-list)))) +(defun info-xref-lock-file-p (filename) + "Return non-nil if FILENAME is an Emacs lock file. +A lock file is \".#foo.txt\" etc per `lock-buffer'." + (string-match "\\(\\`\\|\\/\\)\\.#" filename)) (defun info-xref-subfile-p (filename) "Return t if FILENAME is an info subfile. -If removing the last \"-<NUM>\" from the filename gives a file that exists, -then consider FILENAME a subfile. This is an imperfect test, we probably -should open up the purported top file and see what subfiles it says." +If removing the last \"-<NUM>\" from the filename gives a file +which exists, then consider FILENAME a subfile. This is an +imperfect test, probably ought to open up the purported top file +and see what subfiles it says." (and (string-match "\\`\\(\\([^-]*-\\)*[^-]*\\)-[0-9]+\\(.*\\)\\'" filename) (file-exists-p (concat (match-string 1 filename) (match-string 3 filename))))) +(defmacro info-xref-with-file (filename &rest body) + ;; checkdoc-params: (filename body) + "Evaluate BODY in a buffer containing the contents of FILENAME. +If FILENAME is already in a buffer then that's used, otherwise a +temporary buffer. -;; Some dynamic variables are used to share information with sub-functions -;; below. -;; -;; info-xref-filename-header - a heading message for the current top-level -;; filename, or "" when it's been printed. -;; -(defvar info-xref-xfile-alist) -;; -;; info-xref-good - count of good cross references. -;; -(defvar info-xref-good) -;; -;; info-xref-bad - count of bad cross references. -;; -(defvar info-xref-bad) -;; -;; info-xref-xfile-alist - indexed by "(foo)" with value nil or t according -;; to whether "(foo)" exists or not. This is used to suppress duplicate -;; messages about foo not being available. (Duplicates within one -;; top-level file that is.) -;; -(defvar info-xref-filename-heading) +The current implementation uses `insert-file-contents' rather +than `find-file-noselect' so as not to be held up by queries +about local variables or possible weirdness in a major mode. +`lm-with-file' does a similar thing, but it sets +`emacs-lisp-mode' which is not wanted here." + + (declare (debug t) (indent 1)) + `(let* ((info-xref-with-file--filename ,filename) + (info-xref-with-file--body (lambda () ,@body)) + (info-xref-with-file--existing + (find-buffer-visiting info-xref-with-file--filename))) + (if info-xref-with-file--existing + (with-current-buffer info-xref-with-file--existing + (save-excursion + (funcall info-xref-with-file--body))) + (with-temp-buffer + (insert-file-contents ,filename) + (funcall info-xref-with-file--body))))) + + +;;----------------------------------------------------------------------------- +;; output buffer + +(defconst info-xref-output-buffer "*info-xref results*" + "Name of the buffer for info-xref results.") + +(defvar info-xref-good 0 + "Count of good cross references, during info-xref processing.") +(defvar info-xref-bad 0 + "Count of bad cross references, during info-xref processing.") +(defvar info-xref-unavail 0 + "Count of unavailable cross references, during info-xref processing.") + +(defvar info-xref-output-heading "" + "A heading string, during info-xref processing. +This is shown if there's an error, but not if successful.") + +(defvar info-xref-filename nil + "The current buffer's filename, during info-xref processing. +When looking at file contents in a temp buffer there's no +`buffer-file-name', hence this variable.") + +(defvar info-xref-xfile-alist nil + "Info files found or not found, during info-xref processing. +Key is \"(foo)\" etc and value nil or t according to whether info +manual \"(foo)\" exists or not. This is used to suppress +duplicate messages about foo not being available. (Duplicates +within one top-level file that is.)") -(defun info-xref-check-list (filename-list) - "Check external references in info documents in FILENAME-LIST." - (pop-to-buffer info-xref-results-buffer t) - (erase-buffer) - (let ((info-xref-good 0) - (info-xref-bad 0)) - (dolist (info-xref-filename filename-list) - (let ((info-xref-filename-heading - (format "In file %s:\n" info-xref-filename)) - (info-xref-xfile-alist nil)) - (with-temp-message (format "Looking at %s" info-xref-filename) - (with-temp-buffer - (info-insert-file-contents info-xref-filename) - (goto-char (point-min)) - (if (re-search-forward "\^_\nIndirect:\n" nil t) - (let ((dir (file-name-directory info-xref-filename))) - (while (looking-at "\\(.*\\): [0-9]+\n") - (let ((subfile (match-string 1))) - (with-temp-buffer - (info-insert-file-contents - (expand-file-name subfile dir)) - (info-xref-check-buffer))) - (forward-line))) - (info-xref-check-buffer)))))) - (insert (format "done, %d good, %d bad\n" info-xref-good info-xref-bad)))) +(defvar info-xref-in-progress nil) +(defmacro info-xref-with-output (&rest body) + "Run BODY with an info-xref output buffer. +This is meant to nest, so you can wrap it around a set of +different info-xref checks and have them write to the one output +buffer created by the outermost `info-xref-with-output', with an +overall good/bad count summary inserted at the very end." + + (declare (debug t)) + `(save-excursion + (unless info-xref-in-progress + (display-buffer (get-buffer-create info-xref-output-buffer)) + (set-buffer info-xref-output-buffer) + (setq buffer-read-only nil) + (fundamental-mode) + (erase-buffer) + (insert ";; info-xref output -*- mode: compilation -*-\n\n") + (compilation-mode) + (setq info-xref-good 0 + info-xref-bad 0 + info-xref-unavail 0 + info-xref-xfile-alist nil)) + + (let ((info-xref-in-progress t) + (info-xref-output-heading "")) + ,@body) -(defun info-xref-check-buffer () - "Check external references in the info file in the current buffer. -This should be the raw file contents, not `Info-mode'." - (goto-char (point-min)) - (while (re-search-forward - "\\*[Nn]ote[ \n\t]+[^:]*:[ \n\t]+\\(\\(([^)]*)\\)[^.,]+\\)[.,]" - nil t) - (let* ((file (match-string 2)) - (node ;; Canonicalize spaces: we could use "[\t\n ]+" but - ;; we try to avoid uselessly replacing " " with " ". - (replace-regexp-in-string "[\t\n][\t\n ]*\\| [\t\n ]+" " " - (match-string 1) t t))) - (if (string-equal "()" file) - (info-xref-output "Empty filename part: %s\n" node) - ;; see if the file exists, if we haven't tried it before - (unless (assoc file info-xref-xfile-alist) - (let ((found (info-xref-goto-node-p file))) - (push (cons file found) info-xref-xfile-alist) - (unless found - (info-xref-output "Not available to check: %s\n" file)))) - ;; if the file exists, try the node - (when (cdr (assoc file info-xref-xfile-alist)) - (if (info-xref-goto-node-p node) - (setq info-xref-good (1+ info-xref-good)) - (setq info-xref-bad (1+ info-xref-bad)) - (info-xref-output "No such node: %s\n" node))))))) + (unless info-xref-in-progress + (info-xref-output "done, %d good, %d bad, %d unavailable" + info-xref-good info-xref-bad info-xref-unavail)))) + +(defun info-xref-output (fmt &rest args) + "Emit a `format'-ed message FMT+ARGS to the `info-xref-output-buffer'." + (with-current-buffer info-xref-output-buffer + (save-excursion + (goto-char (point-max)) + (let ((inhibit-read-only t)) + (insert info-xref-output-heading + (apply 'format fmt args) + "\n"))) + (setq info-xref-output-heading "") + ;; all this info-xref can be pretty slow, display now so the user sees + ;; some progress + (sit-for 0))) +(put 'info-xref-output 'byte-compile-format-like t) -(defun info-xref-output (str &rest args) - "Emit a `format'-ed message STR+ARGS to the info-xref output buffer." - (with-current-buffer info-xref-results-buffer - (insert info-xref-filename-heading - (apply 'format str args)) - (setq info-xref-filename-heading "") - ;; all this info-xref can be pretty slow, display now so the user can - ;; see some progress - (sit-for 0))) +(defun info-xref-output-error (fmt &rest args) + "Emit a `format'-ed error FMT+ARGS to the `info-xref-output-buffer'. +The error is attributed to `info-xref-filename' and the current +buffer's line and column of point." + (apply 'info-xref-output + (concat "%s:%s:%s: " fmt) + info-xref-filename + (1+ (count-lines (point-min) (line-beginning-position))) + (1+ (current-column)) + args)) +(put 'info-xref-output-error 'byte-compile-format-like t) + + +;;----------------------------------------------------------------------------- +;; node checking ;; When asking Info-goto-node to fork, *info* needs to be the current ;; buffer, otherwise it seems to clone the current buffer but then do the ;; goto-node in plain *info*. ;; -;; We only fork if *info* already exists, if it doesn't then we can create -;; and destroy just that instead of a new name. +;; We only fork if *info* already exists, if it doesn't then can create and +;; destroy just that instead of a new name. ;; ;; If Info-goto-node can't find the file, then no new buffer is created. If ;; it finds the file but not the node, then a buffer is created. Handle @@ -246,71 +210,318 @@ t) (error nil)) (unless (equal (current-buffer) oldbuf) - (kill-buffer (current-buffer)))))))) + (kill-buffer))))))) + +(defun info-xref-check-node (node) + + ;; Collapse spaces as per info.el and `help-make-xrefs'. + ;; Note defcustom :info-link nodes don't get this whitespace collapsing, + ;; they should be the exact node name ready to visit. + ;; `info-xref-check-all-custom' uses `info-xref-goto-node-p' and so + ;; doesn't come through here. + ;; + ;; Could use "[\t\n ]+" but try to avoid uselessly replacing " " with " ". + (setq node (replace-regexp-in-string "[\t\n][\t\n ]*\\| [\t\n ]+" " " + node t t)) + + (if (not (string-match "\\`([^)]*)" node)) + (info-xref-output-error "no `(file)' part at start of node: %s\n" node) + (let ((file (match-string 0 node))) + + (if (string-equal "()" file) + (info-xref-output-error "empty filename part: %s" node) + + ;; see if the file exists, if haven't looked before + (unless (assoc file info-xref-xfile-alist) + (let ((found (info-xref-goto-node-p file))) + (push (cons file found) info-xref-xfile-alist) + (unless found + (info-xref-output-error "not available to check: %s\n (this reported once per file)" file)))) + + ;; if the file exists, try the node + (cond ((not (cdr (assoc file info-xref-xfile-alist))) + (incf info-xref-unavail)) + ((info-xref-goto-node-p node) + (incf info-xref-good)) + (t + (incf info-xref-bad) + (info-xref-output-error "no such node: %s" node))))))) + + +;;----------------------------------------------------------------------------- + +;;;###autoload +(defun info-xref-check (filename) + "Check external references in FILENAME, an info document. +Interactively from an `Info-mode' or `texinfo-mode' buffer the +current info file is the default. + +Results are shown in a `compilation-mode' buffer. The format is +a bit rough, but there shouldn't be many problems normally. The +file:line:column: is the info document, but of course normally +any correction should be made in the original .texi file. +Finding the right place in the .texi is a manual process. + +When a target info file doesn't exist there's obviously no way to +validate node references within it. A message is given for +missing target files once per source document. It could be +simply that you don't have the target installed, or it could be a +mistake in the reference. + +Indirect info files are understood, just pass the top-level +foo.info to `info-xref-check' and it traverses all sub-files. +Compressed info files are accepted too as usual for `Info-mode'. + +\"makeinfo\" checks references internal to an info document, but +not external references, which makes it rather easy for mistakes +to creep in or node name changes to go unnoticed. +`Info-validate' doesn't check external references either." + + (interactive + (list + (let* ((default-filename + (cond ((eq major-mode 'Info-mode) + Info-current-file) + ((eq major-mode 'texinfo-mode) + ;; look for @setfilename like makeinfo.el does + (save-excursion + (goto-char (point-min)) + (if (re-search-forward + "^@setfilename[ \t]+\\([^ \t\n]+\\)[ \t]*" + (line-beginning-position 100) t) + (expand-file-name (match-string 1))))))) + (prompt (if default-filename + (format "Info file (%s): " default-filename) + "Info file: "))) + (read-file-name prompt nil default-filename t)))) + + (info-xref-check-list (list filename))) + +;;;###autoload +(defun info-xref-check-all () + "Check external references in all info documents in the info path. +`Info-directory-list' and `Info-additional-directory-list' are +the info paths. See `info-xref-check' for how each file is +checked. + +The search for \"all\" info files is rather permissive, since +info files don't necessarily have a \".info\" extension and in +particular the Emacs manuals normally don't. If you have a +source code directory in `Info-directory-list' then a lot of +extraneous files might be read. This will be time consuming but +should be harmless." + + (interactive) + (info-xref-check-list (info-xref-all-info-files))) + +;; An alternative for geting only top-level files here would be to simply +;; return all files and have info-xref-check-list not follow "Indirect:". +;; The current way seems better because it (potentially) gets the proper +;; top-level filename into the error messages, and suppresses duplicate "not +;; available" messages for all subfiles of a single document. + +(defun info-xref-all-info-files () + "Return a list of all available info files. +Only top level files are returned, subfiles are excluded. + +Since info files don't have to have a .info suffix, all files in +the relevant directories are considered, which might mean a lot +of extraneous things if for instance a source code directory is +in the path." + + (info-initialize) ;; establish Info-directory-list + (apply 'nconc + (mapcar + (lambda (dir) + (let ((result nil)) + (dolist (name (directory-files + dir + t ;; absolute filenames + "\\`[^.]")) ;; not dotfiles, nor .# lockfiles + (when (and (file-exists-p name) ;; ignore broken symlinks + (not (string-match "\\.te?xi\\'" name)) ;; not .texi + (not (backup-file-name-p name)) + (not (file-directory-p name)) + (not (info-xref-subfile-p name))) + (push name result))) + (nreverse result))) + (append Info-directory-list Info-additional-directory-list)))) + +(defun info-xref-check-list (filename-list) + "Check external references in info documents in FILENAME-LIST." + (info-xref-with-output + (dolist (info-xref-filename filename-list) + (setq info-xref-xfile-alist nil) + (let ((info-xref-output-heading + (format "Info file %s\n" info-xref-filename))) + (with-temp-message (format "Looking at %s" info-xref-filename) + (with-temp-buffer + (info-insert-file-contents info-xref-filename) + (goto-char (point-min)) + (if (search-forward "\^_\nIndirect:\n" nil t) + (let ((dir (file-name-directory info-xref-filename))) + (while (looking-at "\\(.*\\): [0-9]+\n") + (let ((info-xref-filename + (expand-file-name (match-string 1) dir))) + (with-temp-buffer + (info-insert-file-contents info-xref-filename) + (info-xref-check-buffer))) + (forward-line))) + (info-xref-check-buffer)))))))) + +(defun info-xref-check-buffer () + "Check external references in the info file in the current buffer. +This should be the raw file contents, not `Info-mode'." + (goto-char (point-min)) + (while (re-search-forward + "\\*[Nn]ote[ \n\t]+[^:]*:[ \n\t]+\\(\\(([^)]*)\\)[^.,]+\\)[.,]" + nil t) + (save-excursion + (goto-char (match-beginning 1)) ;; start of nodename as error position + (info-xref-check-node (match-string 1))))) + +(defvar viper-mode) ;; quieten the byte compiler +(defvar gnus-registry-install) ;;;###autoload (defun info-xref-check-all-custom () "Check info references in all customize groups and variables. -`custom-manual' and `info-link' entries in the `custom-links' list are checked. +Info references can be in `custom-manual' or `info-link' entries +of the `custom-links' for a variable. -`custom-load' autoloads for all symbols are loaded in order to get all the -link information. This will be a lot of lisp packages loaded, and can take -quite a while." +Any `custom-load' autoloads in variables are loaded in order to +get full link information. This will be a lot of Lisp packages +and can take a long time." (interactive) - (pop-to-buffer info-xref-results-buffer t) - (erase-buffer) - (let ((info-xref-filename-heading "")) + (info-xref-with-output + + ;; `custom-load-symbol' is not used, since it quietly ignores errors, but + ;; we want to show them since they mean incomplete checking. + ;; + ;; Just one pass through mapatoms is made. There shouldn't be any new + ;; custom-loads setup by packages loaded. + ;; + (info-xref-output "Loading custom-load autoloads ...") + (require 'cus-start) + (require 'cus-load) + + ;; These are `setq' rather than `let' since a let would unbind the + ;; variables after viper.el/gnus-registry.el have loaded, defeating the + ;; defvars in those files. Of course it'd be better if those files + ;; didn't make interactive queries on loading at all, to allow for + ;; programmatic loading like here. + (unless (boundp 'viper-mode) + (setq viper-mode nil)) ;; avoid viper.el ask about viperizing + (unless (boundp 'gnus-registry-install) + (setq gnus-registry-install nil)) ;; avoid gnus-registery.el querying + + (mapatoms + (lambda (symbol) + (dolist (load (get symbol 'custom-loads)) + (cond ((symbolp load) + (condition-case cause (require load) + (error + (info-xref-output "Symbol `%s': cannot require '%s: %s" + symbol load cause)))) + ;; skip if previously loaded + ((assoc load load-history)) + ((assoc (locate-library load) load-history)) + (t + (condition-case err + (load load) + (error + (info-xref-output "Symbol `%s': cannot load \"%s\": %s" + symbol load + (error-message-string err))))))))) + + ;; Don't bother to check whether the info file exists as opposed to just + ;; a missing node. If you have the code then you should have the + ;; documentation, so a wrong node name will be the usual fault. + ;; + (info-xref-output "\nChecking custom-links references ...") + (mapatoms + (lambda (symbol) + (dolist (link (get symbol 'custom-links)) + (when (memq (car link) '(custom-manual info-link)) + ;; skip :tag part of (custom-manual :tag "Foo" "(foo)Node") + (if (eq :tag (cadr link)) + (setq link (cddr link))) + (if (info-xref-goto-node-p (cadr link)) + (incf info-xref-good) + (incf info-xref-bad) + ;; symbol-file gives nil for preloaded variables, would need + ;; to copy what describe-variable does to show the right place + (info-xref-output "Symbol `%s' (file %s): cannot goto node: %s" + symbol + (symbol-file symbol 'defvar) + (cadr link))))))))) - ;; `custom-load-symbol' is not used, since it quietly ignores errors, - ;; but we want to show them (since they may mean incomplete checking). - ;; - ;; Just one pass through mapatoms is made. There shouldn't be any new - ;; custom-loads setup by packages loaded. - ;; - (info-xref-output "Loading custom-load autoloads ...\n") - (require 'cus-start) - (require 'cus-load) - (let ((viper-mode nil)) ;; tell viper.el not to ask about viperizing - (mapatoms - (lambda (symbol) - (dolist (load (get symbol 'custom-loads)) - (cond ((symbolp load) - (condition-case cause (require load) - (error - (info-xref-output "Symbol `%s': cannot require '%s: %s\n" - symbol load cause)))) - ;; skip if previously loaded - ((assoc load load-history)) - ((assoc (locate-library load) load-history)) - (t - (condition-case cause (load load) - (error - (info-xref-output "Symbol `%s': cannot load \"%s\": %s\n" - symbol load cause))))))))) +;;;###autoload +(defun info-xref-docstrings (filename-list) + ;; checkdoc-params: (filename-list) + "Check docstring info node references in source files. +The given files are searched for docstring hyperlinks like + + Info node `(elisp)Documentation Tips' + +and those links checked by attempting to visit the target nodes +as per `info-xref-check' does. + +Interactively filenames are read as a wildcard pattern like +\"foo*.el\", with the current file as a default. Usually this +will be lisp sources, but anything with such hyperlinks can be +checked, including the Emacs .c sources (or the etc/DOC file of +all builtins). + +Because info node hyperlinks are found by a simple regexp search +in the files, the Lisp code checked doesn't have to be loaded, +and links can be in the file commentary or elsewhere too. Even +.elc files can usually be checked successfully if you don't have +the sources handy." - ;; Don't bother to check whether the info file exists as opposed to just - ;; a missing node. If you have the lisp then you should have the - ;; documentation, so missing node name will be the usual fault. - ;; - (info-xref-output "\nChecking custom-links references ...\n") - (let ((good 0) - (bad 0)) - (mapatoms - (lambda (symbol) - (dolist (link (get symbol 'custom-links)) - (when (memq (car link) '(custom-manual info-link)) - ;; skip :tag part of (custom-manual :tag "Foo" "(foo)Node") - (if (eq :tag (cadr link)) - (setq link (cddr link))) - (if (info-xref-goto-node-p (cadr link)) - (setq good (1+ good)) - (setq bad (1+ bad)) - ;; symbol-file gives nil for preloaded variables, would need - ;; to copy what describe-variable does to show the right place - (info-xref-output "Symbol `%s' (in %s): cannot goto node: %s\n" - symbol (symbol-file symbol) (cadr link))))))) - (info-xref-output "%d good, %d bad\n" good bad)))) + (interactive + (let* ((default (and buffer-file-name + (file-relative-name buffer-file-name))) + (prompt (if default + (format "Filename with wildcards (%s): " + default) + "Filename with wildcards: ")) + (pattern (read-file-name prompt nil default)) + (filename-list (file-expand-wildcards pattern + t))) ;; absolute filenames + (eval-and-compile + (require 'cl)) ;; for `remove-if' + (setq filename-list (remove-if 'info-xref-lock-file-p filename-list)) + (unless filename-list + (error "No files: %S" pattern)) + (list filename-list))) + + (eval-and-compile + (require 'help-mode)) ;; for `help-xref-info-regexp' + + (info-xref-with-output + (dolist (info-xref-filename filename-list) + (setq info-xref-xfile-alist nil) ;; "not found"s once per file + + (info-xref-with-file info-xref-filename + (goto-char (point-min)) + (while (re-search-forward help-xref-info-regexp nil t) + (let ((node (match-string 2))) + (save-excursion + (goto-char (match-beginning 2)) ;; start of node as error position + + ;; skip nodes with "%" as probably `format' strings such as in + ;; info-look.el + (unless (string-match "%" node) + + ;; "(emacs)" is the default manual for docstring hyperlinks, + ;; per `help-make-xrefs' + (unless (string-match "\\`(" node) + (setq node (concat "(emacs)" node))) + + (info-xref-check-node node))))))))) + (provide 'info-xref)