Merge from emacs-24; up to 2012-12-24T15:56:17Z!eliz@gnu.org
[bpt/emacs.git] / lisp / info-xref.el
CommitLineData
d9026f5c
JL
1;;; info-xref.el --- check external references in an Info document
2
ab422c4d 3;; Copyright (C) 2003-2013 Free Software Foundation, Inc.
dac15a1e 4
dac15a1e
JB
5;; Author: Kevin Ryde <user42@zip.com.au>
6;; Keywords: docs
444ee8dd 7;; Version: 3
dac15a1e 8
d9026f5c
JL
9;; This file is part of GNU Emacs.
10
eb3fa2cf 11;; GNU Emacs is free software: you can redistribute it and/or modify
d9026f5c 12;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
d9026f5c
JL
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
eb3fa2cf 22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
dac15a1e
JB
23
24;;; Commentary:
25
444ee8dd
KR
26;; This is some simple checking of external cross references in info files,
27;; docstrings and custom-links by attempting to visit the nodes specified.
dac15a1e 28;;
444ee8dd
KR
29;; `M-x info-xref-check' checks a single info file. See the docstring for
30;; details.
dac15a1e 31;;
444ee8dd
KR
32;; `M-x info-xref-check-all' checks all info files in Info-directory-list.
33;; This is a good way to check the consistency of the whole system.
dac15a1e 34;;
444ee8dd
KR
35;; `M-x info-xref-check-all-custom' loads up all defcustom variables and
36;; checks any info references in them.
dac15a1e 37;;
444ee8dd
KR
38;; `M-x info-xref-docstrings' checks docstring "Info node ..." hyperlinks in
39;; source files (and other files).
40
41;;; History:
42
43;; Version 3 - new M-x info-xref-docstrings, use compilation-mode
dac15a1e 44
dac15a1e
JB
45;;; Code:
46
47(require 'info)
f58e0fd5 48(eval-when-compile (require 'cl-lib)) ;; for `incf'
444ee8dd
KR
49
50;;-----------------------------------------------------------------------------
51;; vaguely generic
52
53(defun info-xref-lock-file-p (filename)
54 "Return non-nil if FILENAME is an Emacs lock file.
55A lock file is \".#foo.txt\" etc per `lock-buffer'."
56 (string-match "\\(\\`\\|\\/\\)\\.#" filename))
57
58(defun info-xref-subfile-p (filename)
59 "Return t if FILENAME is an info subfile.
60If removing the last \"-<NUM>\" from the filename gives a file
61which exists, then consider FILENAME a subfile. This is an
62imperfect test, probably ought to open up the purported top file
63and see what subfiles it says."
64 (and (string-match "\\`\\(\\([^-]*-\\)*[^-]*\\)-[0-9]+\\(.*\\)\\'" filename)
65 (file-exists-p (concat (match-string 1 filename)
66 (match-string 3 filename)))))
67
68(defmacro info-xref-with-file (filename &rest body)
69 ;; checkdoc-params: (filename body)
70 "Evaluate BODY in a buffer containing the contents of FILENAME.
71If FILENAME is already in a buffer then that's used, otherwise a
72temporary buffer.
73
74The current implementation uses `insert-file-contents' rather
75than `find-file-noselect' so as not to be held up by queries
76about local variables or possible weirdness in a major mode.
77`lm-with-file' does a similar thing, but it sets
78`emacs-lisp-mode' which is not wanted here."
79
80 (declare (debug t) (indent 1))
81 `(let* ((info-xref-with-file--filename ,filename)
82 (info-xref-with-file--body (lambda () ,@body))
83 (info-xref-with-file--existing
84 (find-buffer-visiting info-xref-with-file--filename)))
85 (if info-xref-with-file--existing
86 (with-current-buffer info-xref-with-file--existing
87 (save-excursion
88 (funcall info-xref-with-file--body)))
89 (with-temp-buffer
90 (insert-file-contents ,filename)
91 (funcall info-xref-with-file--body)))))
92
93
94;;-----------------------------------------------------------------------------
95;; output buffer
dac15a1e 96
444ee8dd 97(defconst info-xref-output-buffer "*info-xref results*"
dac15a1e
JB
98 "Name of the buffer for info-xref results.")
99
444ee8dd
KR
100(defvar info-xref-good 0
101 "Count of good cross references, during info-xref processing.")
102(defvar info-xref-bad 0
103 "Count of bad cross references, during info-xref processing.")
104(defvar info-xref-unavail 0
105 "Count of unavailable cross references, during info-xref processing.")
106
107(defvar info-xref-output-heading ""
108 "A heading string, during info-xref processing.
109This is shown if there's an error, but not if successful.")
110
111(defvar info-xref-filename nil
112 "The current buffer's filename, during info-xref processing.
113When looking at file contents in a temp buffer there's no
114`buffer-file-name', hence this variable.")
115
116(defvar info-xref-xfile-alist nil
117 "Info files found or not found, during info-xref processing.
118Key is \"(foo)\" etc and value nil or t according to whether info
119manual \"(foo)\" exists or not. This is used to suppress
120duplicate messages about foo not being available. (Duplicates
121within one top-level file that is.)")
122
123(defvar info-xref-in-progress nil)
124(defmacro info-xref-with-output (&rest body)
125 "Run BODY with an info-xref output buffer.
126This is meant to nest, so you can wrap it around a set of
127different info-xref checks and have them write to the one output
128buffer created by the outermost `info-xref-with-output', with an
129overall good/bad count summary inserted at the very end."
130
131 (declare (debug t))
132 `(save-excursion
133 (unless info-xref-in-progress
134 (display-buffer (get-buffer-create info-xref-output-buffer))
135 (set-buffer info-xref-output-buffer)
136 (setq buffer-read-only nil)
137 (fundamental-mode)
138 (erase-buffer)
139 (insert ";; info-xref output -*- mode: compilation -*-\n\n")
140 (compilation-mode)
141 (setq info-xref-good 0
142 info-xref-bad 0
143 info-xref-unavail 0
144 info-xref-xfile-alist nil))
145
146 (let ((info-xref-in-progress t)
147 (info-xref-output-heading ""))
148 ,@body)
149
150 (unless info-xref-in-progress
151 (info-xref-output "done, %d good, %d bad, %d unavailable"
152 info-xref-good info-xref-bad info-xref-unavail))))
06b60517 153
444ee8dd
KR
154(defun info-xref-output (fmt &rest args)
155 "Emit a `format'-ed message FMT+ARGS to the `info-xref-output-buffer'."
156 (with-current-buffer info-xref-output-buffer
157 (save-excursion
158 (goto-char (point-max))
159 (let ((inhibit-read-only t))
160 (insert info-xref-output-heading
161 (apply 'format fmt args)
162 "\n")))
163 (setq info-xref-output-heading "")
164 ;; all this info-xref can be pretty slow, display now so the user sees
165 ;; some progress
166 (sit-for 0)))
167(put 'info-xref-output 'byte-compile-format-like t)
168
169(defun info-xref-output-error (fmt &rest args)
170 "Emit a `format'-ed error FMT+ARGS to the `info-xref-output-buffer'.
171The error is attributed to `info-xref-filename' and the current
172buffer's line and column of point."
173 (apply 'info-xref-output
174 (concat "%s:%s:%s: " fmt)
175 info-xref-filename
176 (1+ (count-lines (point-min) (line-beginning-position)))
177 (1+ (current-column))
178 args))
179(put 'info-xref-output-error 'byte-compile-format-like t)
180
181
182;;-----------------------------------------------------------------------------
183;; node checking
184
185;; When asking Info-goto-node to fork, *info* needs to be the current
186;; buffer, otherwise it seems to clone the current buffer but then do the
187;; goto-node in plain *info*.
188;;
189;; We only fork if *info* already exists, if it doesn't then can create and
190;; destroy just that instead of a new name.
191;;
192;; If Info-goto-node can't find the file, then no new buffer is created. If
193;; it finds the file but not the node, then a buffer is created. Handle
194;; this difference by checking before killing.
195;;
196(defun info-xref-goto-node-p (node)
197 "Return t if it's possible to go to the given NODE."
198 (let ((oldbuf (current-buffer)))
199 (save-excursion
200 (save-window-excursion
201 (prog1
06b60517 202 (condition-case nil
444ee8dd
KR
203 (progn
204 (Info-goto-node node
205 (when (get-buffer "*info*")
206 (set-buffer "*info*")
207 "xref - temporary"))
208 t)
209 (error nil))
210 (unless (equal (current-buffer) oldbuf)
211 (kill-buffer)))))))
212
213(defun info-xref-check-node (node)
214
215 ;; Collapse spaces as per info.el and `help-make-xrefs'.
216 ;; Note defcustom :info-link nodes don't get this whitespace collapsing,
217 ;; they should be the exact node name ready to visit.
218 ;; `info-xref-check-all-custom' uses `info-xref-goto-node-p' and so
219 ;; doesn't come through here.
220 ;;
221 ;; Could use "[\t\n ]+" but try to avoid uselessly replacing " " with " ".
222 (setq node (replace-regexp-in-string "[\t\n][\t\n ]*\\| [\t\n ]+" " "
223 node t t))
224
225 (if (not (string-match "\\`([^)]*)" node))
226 (info-xref-output-error "no `(file)' part at start of node: %s\n" node)
227 (let ((file (match-string 0 node)))
228
229 (if (string-equal "()" file)
230 (info-xref-output-error "empty filename part: %s" node)
231
232 ;; see if the file exists, if haven't looked before
233 (unless (assoc file info-xref-xfile-alist)
234 (let ((found (info-xref-goto-node-p file)))
235 (push (cons file found) info-xref-xfile-alist)
236 (unless found
237 (info-xref-output-error "not available to check: %s\n (this reported once per file)" file))))
238
239 ;; if the file exists, try the node
240 (cond ((not (cdr (assoc file info-xref-xfile-alist)))
f58e0fd5 241 (cl-incf info-xref-unavail))
444ee8dd 242 ((info-xref-goto-node-p node)
f58e0fd5 243 (cl-incf info-xref-good))
444ee8dd 244 (t
f58e0fd5 245 (cl-incf info-xref-bad)
444ee8dd
KR
246 (info-xref-output-error "no such node: %s" node)))))))
247
248
249;;-----------------------------------------------------------------------------
250
dac15a1e
JB
251;;;###autoload
252(defun info-xref-check (filename)
444ee8dd
KR
253 "Check external references in FILENAME, an info document.
254Interactively from an `Info-mode' or `texinfo-mode' buffer the
255current info file is the default.
256
257Results are shown in a `compilation-mode' buffer. The format is
258a bit rough, but there shouldn't be many problems normally. The
259file:line:column: is the info document, but of course normally
260any correction should be made in the original .texi file.
261Finding the right place in the .texi is a manual process.
262
263When a target info file doesn't exist there's obviously no way to
264validate node references within it. A message is given for
265missing target files once per source document. It could be
266simply that you don't have the target installed, or it could be a
267mistake in the reference.
268
269Indirect info files are understood, just pass the top-level
270foo.info to `info-xref-check' and it traverses all sub-files.
271Compressed info files are accepted too as usual for `Info-mode'.
272
273\"makeinfo\" checks references internal to an info document, but
274not external references, which makes it rather easy for mistakes
275to creep in or node name changes to go unnoticed.
276`Info-validate' doesn't check external references either."
277
dac15a1e
JB
278 (interactive
279 (list
280 (let* ((default-filename
281 (cond ((eq major-mode 'Info-mode)
282 Info-current-file)
283 ((eq major-mode 'texinfo-mode)
284 ;; look for @setfilename like makeinfo.el does
285 (save-excursion
286 (goto-char (point-min))
287 (if (re-search-forward
288 "^@setfilename[ \t]+\\([^ \t\n]+\\)[ \t]*"
da05debc 289 (line-beginning-position 100) t)
dac15a1e
JB
290 (expand-file-name (match-string 1)))))))
291 (prompt (if default-filename
292 (format "Info file (%s): " default-filename)
293 "Info file: ")))
da05debc 294 (read-file-name prompt nil default-filename t))))
444ee8dd 295
dac15a1e
JB
296 (info-xref-check-list (list filename)))
297
298;;;###autoload
299(defun info-xref-check-all ()
444ee8dd
KR
300 "Check external references in all info documents in the info path.
301`Info-directory-list' and `Info-additional-directory-list' are
302the info paths. See `info-xref-check' for how each file is
303checked.
304
305The search for \"all\" info files is rather permissive, since
306info files don't necessarily have a \".info\" extension and in
307particular the Emacs manuals normally don't. If you have a
308source code directory in `Info-directory-list' then a lot of
309extraneous files might be read. This will be time consuming but
310should be harmless."
311
dac15a1e
JB
312 (interactive)
313 (info-xref-check-list (info-xref-all-info-files)))
314
3ed8598c 315;; An alternative for getting only top-level files here would be to simply
444ee8dd
KR
316;; return all files and have info-xref-check-list not follow "Indirect:".
317;; The current way seems better because it (potentially) gets the proper
318;; top-level filename into the error messages, and suppresses duplicate "not
319;; available" messages for all subfiles of a single document.
dac15a1e
JB
320
321(defun info-xref-all-info-files ()
322 "Return a list of all available info files.
444ee8dd 323Only top level files are returned, subfiles are excluded.
dac15a1e 324
444ee8dd
KR
325Since info files don't have to have a .info suffix, all files in
326the relevant directories are considered, which might mean a lot
327of extraneous things if for instance a source code directory is
328in the path."
dac15a1e
JB
329
330 (info-initialize) ;; establish Info-directory-list
da05debc 331 (apply 'nconc
dac15a1e
JB
332 (mapcar
333 (lambda (dir)
334 (let ((result nil))
444ee8dd
KR
335 (dolist (name (directory-files
336 dir
337 t ;; absolute filenames
338 "\\`[^.]")) ;; not dotfiles, nor .# lockfiles
339 (when (and (file-exists-p name) ;; ignore broken symlinks
340 (not (string-match "\\.te?xi\\'" name)) ;; not .texi
341 (not (backup-file-name-p name))
342 (not (file-directory-p name))
343 (not (info-xref-subfile-p name)))
da05debc
SM
344 (push name result)))
345 (nreverse result)))
dac15a1e
JB
346 (append Info-directory-list Info-additional-directory-list))))
347
dac15a1e
JB
348(defun info-xref-check-list (filename-list)
349 "Check external references in info documents in FILENAME-LIST."
444ee8dd 350 (info-xref-with-output
dac15a1e 351 (dolist (info-xref-filename filename-list)
444ee8dd
KR
352 (setq info-xref-xfile-alist nil)
353 (let ((info-xref-output-heading
354 (format "Info file %s\n" info-xref-filename)))
dac15a1e
JB
355 (with-temp-message (format "Looking at %s" info-xref-filename)
356 (with-temp-buffer
357 (info-insert-file-contents info-xref-filename)
358 (goto-char (point-min))
444ee8dd 359 (if (search-forward "\^_\nIndirect:\n" nil t)
dac15a1e
JB
360 (let ((dir (file-name-directory info-xref-filename)))
361 (while (looking-at "\\(.*\\): [0-9]+\n")
444ee8dd
KR
362 (let ((info-xref-filename
363 (expand-file-name (match-string 1) dir)))
dac15a1e 364 (with-temp-buffer
444ee8dd 365 (info-insert-file-contents info-xref-filename)
dac15a1e
JB
366 (info-xref-check-buffer)))
367 (forward-line)))
444ee8dd 368 (info-xref-check-buffer))))))))
dac15a1e
JB
369
370(defun info-xref-check-buffer ()
371 "Check external references in the info file in the current buffer.
399a26ce 372This should be the raw file contents, not `Info-mode'."
dac15a1e
JB
373 (goto-char (point-min))
374 (while (re-search-forward
0332a905 375 "\\*[Nn]ote[ \n\t]+[^:]*:[ \n\t]+\\(\\(([^)]*)\\)[^.,]+\\)[.,]"
dac15a1e 376 nil t)
dac15a1e 377 (save-excursion
444ee8dd
KR
378 (goto-char (match-beginning 1)) ;; start of nodename as error position
379 (info-xref-check-node (match-string 1)))))
380
381(defvar viper-mode) ;; quieten the byte compiler
382(defvar gnus-registry-install)
dac15a1e 383
0332a905
JL
384;;;###autoload
385(defun info-xref-check-all-custom ()
386 "Check info references in all customize groups and variables.
444ee8dd
KR
387Info references can be in `custom-manual' or `info-link' entries
388of the `custom-links' for a variable.
0332a905 389
444ee8dd
KR
390Any `custom-load' autoloads in variables are loaded in order to
391get full link information. This will be a lot of Lisp packages
392and can take a long time."
0332a905
JL
393
394 (interactive)
444ee8dd
KR
395 (info-xref-with-output
396
397 ;; `custom-load-symbol' is not used, since it quietly ignores errors, but
398 ;; we want to show them since they mean incomplete checking.
399 ;;
400 ;; Just one pass through mapatoms is made. There shouldn't be any new
401 ;; custom-loads setup by packages loaded.
402 ;;
403 (info-xref-output "Loading custom-load autoloads ...")
404 (require 'cus-start)
405 (require 'cus-load)
406
407 ;; These are `setq' rather than `let' since a let would unbind the
408 ;; variables after viper.el/gnus-registry.el have loaded, defeating the
409 ;; defvars in those files. Of course it'd be better if those files
410 ;; didn't make interactive queries on loading at all, to allow for
411 ;; programmatic loading like here.
412 (unless (boundp 'viper-mode)
413 (setq viper-mode nil)) ;; avoid viper.el ask about viperizing
414 (unless (boundp 'gnus-registry-install)
40ba43b4 415 (setq gnus-registry-install nil)) ;; avoid gnus-registry.el querying
444ee8dd
KR
416
417 (mapatoms
418 (lambda (symbol)
419 (dolist (load (get symbol 'custom-loads))
420 (cond ((symbolp load)
421 (condition-case cause (require load)
422 (error
423 (info-xref-output "Symbol `%s': cannot require '%s: %s"
424 symbol load cause))))
425 ;; skip if previously loaded
426 ((assoc load load-history))
427 ((assoc (locate-library load) load-history))
428 (t
429 (condition-case err
430 (load load)
431 (error
432 (info-xref-output "Symbol `%s': cannot load \"%s\": %s"
433 symbol load
434 (error-message-string err)))))))))
435
436 ;; Don't bother to check whether the info file exists as opposed to just
437 ;; a missing node. If you have the code then you should have the
438 ;; documentation, so a wrong node name will be the usual fault.
439 ;;
440 (info-xref-output "\nChecking custom-links references ...")
441 (mapatoms
442 (lambda (symbol)
443 (dolist (link (get symbol 'custom-links))
444 (when (memq (car link) '(custom-manual info-link))
445 ;; skip :tag part of (custom-manual :tag "Foo" "(foo)Node")
446 (if (eq :tag (cadr link))
447 (setq link (cddr link)))
448 (if (info-xref-goto-node-p (cadr link))
f58e0fd5
SM
449 (cl-incf info-xref-good)
450 (cl-incf info-xref-bad)
444ee8dd
KR
451 ;; symbol-file gives nil for preloaded variables, would need
452 ;; to copy what describe-variable does to show the right place
453 (info-xref-output "Symbol `%s' (file %s): cannot goto node: %s"
454 symbol
455 (symbol-file symbol 'defvar)
456 (cadr link)))))))))
457
458;;;###autoload
459(defun info-xref-docstrings (filename-list)
460 ;; checkdoc-params: (filename-list)
461 "Check docstring info node references in source files.
462The given files are searched for docstring hyperlinks like
463
464 Info node `(elisp)Documentation Tips'
465
466and those links checked by attempting to visit the target nodes
467as per `info-xref-check' does.
468
469Interactively filenames are read as a wildcard pattern like
470\"foo*.el\", with the current file as a default. Usually this
471will be lisp sources, but anything with such hyperlinks can be
472checked, including the Emacs .c sources (or the etc/DOC file of
473all builtins).
474
475Because info node hyperlinks are found by a simple regexp search
476in the files, the Lisp code checked doesn't have to be loaded,
477and links can be in the file commentary or elsewhere too. Even
478.elc files can usually be checked successfully if you don't have
479the sources handy."
444ee8dd 480 (interactive
a767645f 481 (let* ((default (and buffer-file-name
444ee8dd 482 (file-relative-name buffer-file-name)))
a767645f
GM
483 (prompt (if default
484 (format "Filename with wildcards (%s): "
485 default)
486 "Filename with wildcards: "))
487 (pattern (read-file-name prompt nil default))
488 ;; absolute filenames
489 (filename-list (file-expand-wildcards pattern t))
490 newlist)
491 (setq filename-list
492 (dolist (file filename-list (nreverse newlist))
493 (or (info-xref-lock-file-p file)
e5c7913c 494 (file-directory-p file)
a767645f 495 (push file newlist))))
444ee8dd
KR
496 (unless filename-list
497 (error "No files: %S" pattern))
498 (list filename-list)))
499
500 (eval-and-compile
501 (require 'help-mode)) ;; for `help-xref-info-regexp'
502
503 (info-xref-with-output
504 (dolist (info-xref-filename filename-list)
505 (setq info-xref-xfile-alist nil) ;; "not found"s once per file
506
507 (info-xref-with-file info-xref-filename
508 (goto-char (point-min))
509 (while (re-search-forward help-xref-info-regexp nil t)
510 (let ((node (match-string 2)))
511 (save-excursion
512 (goto-char (match-beginning 2)) ;; start of node as error position
513
514 ;; skip nodes with "%" as probably `format' strings such as in
515 ;; info-look.el
516 (unless (string-match "%" node)
517
518 ;; "(emacs)" is the default manual for docstring hyperlinks,
519 ;; per `help-make-xrefs'
520 (unless (string-match "\\`(" node)
521 (setq node (concat "(emacs)" node)))
522
523 (info-xref-check-node node)))))))))
524
0332a905 525
dac15a1e
JB
526(provide 'info-xref)
527
528;;; info-xref.el ends here