1 ;;; docstring.el --- utilities for Guile docstring maintenance
3 ;;; Copyright (C) 2001 Neil Jerram
5 ;;; This file is not part of GNU Emacs, but the same permissions apply.
7 ;;; GNU Emacs is free software; you can redistribute it and/or modify
8 ;;; it under the terms of the GNU General Public License as published by
9 ;;; the Free Software Foundation; either version 2, or (at your option)
10 ;;; any later version.
12 ;;; GNU Emacs is distributed in the hope that it will be useful,
13 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;;; GNU General Public License for more details.
17 ;;; You should have received a copy of the GNU General Public License
18 ;;; along with GNU Emacs; see the file COPYING. If not, write to the
19 ;;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 ;;; Boston, MA 02111-1307, USA.
24 ;; The basic premise of these utilities is that - at least in the
25 ;; short term - we can get a lot of reference manual mileage by
26 ;; co-opting the docstrings that are snarfed automatically from
27 ;; Guile's C and Scheme source code. But this leads to problems of
28 ;; synchronization... How do you track when a docstring has been
29 ;; updated in the source and so needs updating in the reference
30 ;; manual. What if a procedure is removed from the Guile source? And
31 ;; so on. To complicate matters, the exact snarfed docstring text
32 ;; will probably need to be modified so that it fits into the flow of
33 ;; the manual section in which it appears. Can we design solutions to
34 ;; synchronization problems that continue to work even when the manual
35 ;; text has been enhanced in this way?
37 ;; This file implements an approach to this problem that I have found
38 ;; useful. It involves keeping track of three copies of each
41 ;; "MANUAL" = the docstring as it appears in the reference manual.
43 ;; "SNARFED" = the docstring as snarfed from the current C or Scheme
46 ;; "TRACKING" = the docstring as it appears in a tracking file whose
47 ;; purpose is to record the most recent snarfed docstrings
48 ;; that are known to be in sync with the reference manual.
50 ;; The approaches are as follows.
52 ;; 1. Comparison of MANUAL-DOC, SOURCE-DOC and TRACK-DOC, to produce a
53 ;; summary output buffer in which keystrokes are defined to bring up
54 ;; detailed comparisons.
56 ;; 2. Comparison of MANUAL-DOC, SOURCE-DOC and TRACK-DOC using Ediff.
60 (defvar docstring-manual-directory
(expand-file-name "~/Guile/cvs/guile-core/doc")
61 "*The directory containing the Texinfo source for the Guile reference manual.")
63 (defvar docstring-tracking-root
(expand-file-name "~/Guile/cvs/guile-core/doc/maint")
64 "*Root directory for docstring tracking files. The tracking file
65 for module (a b c) is expected to be in the file
66 <docstring-tracking-root>/a/b/c.texi.")
68 (defvar docstring-snarfed-roots
(list (expand-file-name "~/Guile/cvs/guile-core/libguile")
69 (expand-file-name "~/Guile/cvs/guile-core/ice-9")
70 (expand-file-name "~/Guile/cvs/guile-core/oop"))
71 "*List of possible root directories for snarfed docstring files.
72 For each entry in this list, the snarfed docstring file for module (a
73 b c) is looked for in the file <entry>/a/b/c.texi.")
75 (defvar docstring-manual-files
'("appendices.texi"
88 "scheme-evaluation.texi"
96 "scheme-procedures.texi"
98 "scheme-scheduling.texi"
99 "scheme-translation.texi"
100 "scheme-utility.texi"
106 "List of Texinfo source files that comprise the Guile reference manual.")
108 (defvar docstring-new-docstrings-file
"new-docstrings.texi"
109 "The name of a file in the Guile reference manual source directory
110 to which new docstrings should be added.")
112 ;; Apply FN in turn to each element in the list CANDIDATES until the
113 ;; first application that returns non-nil.
114 (defun or-map (fn candidates args
)
117 (setq result
(apply fn
(car candidates
) args
))
119 (setq result
(cons (car candidates
) result
)
121 (setq candidates
(cdr candidates
))))
124 ;; Return t if the current buffer position is in the scope of the
125 ;; specified MODULE, as determined by "@c module ..." comments in the
126 ;; buffer. DEFAULT-OK specifies the return value in the case that
127 ;; there are no preceding module comments at all.
128 (defun docstring-in-module (module default-ok
)
130 (if (re-search-backward "^@c module " nil t
)
132 (search-forward "@c module ")
133 (equal module
(read (current-buffer))))
136 ;; Find a docstring in the specified FILE-NAME for the item in module
137 ;; MODULE and with description DESCRIPTION. MODULE should be a list
138 ;; of symbols, Guile-style, for example: '(ice-9 session).
139 ;; DESCRIPTION should be the string that is expected after the @deffn,
140 ;; for example "primitive acons" or "syntax let*".
141 (defun find-docstring (file-name module description
)
142 (and (file-exists-p file-name
)
143 (let ((buf (find-file-noselect file-name
))
144 (deffn-regexp (concat "^@deffnx? "
145 (regexp-quote description
)
151 (goto-char (point-min))
152 (while (and (not found
)
153 (re-search-forward deffn-regexp nil t
))
155 (goto-char (match-beginning 0))
157 (if (docstring-in-module module t
)
161 (list (current-buffer)
163 (re-search-backward "^@deffn ")
167 (re-search-forward "^@end deffn")
172 ;; Find the reference manual version of the specified docstring.
173 ;; MODULE and DESCRIPTION specify the docstring as per
174 ;; `find-docstring'. The set of files that `find-manual-docstring'
175 ;; searches is determined by the value of the `docstring-manual-files'
177 (defun find-manual-docstring (module description
)
179 (or-map 'find-docstring
180 (mapcar (function (lambda (file-name)
181 (concat docstring-manual-directory
184 (cons docstring-new-docstrings-file
185 docstring-manual-files
))
188 (matched-file-name (and (cdr result
)
189 (file-name-nondirectory (car result
)))))
190 (if matched-file-name
191 (setq docstring-manual-files
192 (cons matched-file-name
193 (delete matched-file-name docstring-manual-files
))))
196 ;; Convert MODULE to a directory subpath.
197 (defun module-to-path (module)
198 (mapconcat (function (lambda (component)
199 (symbol-name component
)))
203 ;; Find the current snarfed version of the specified docstring.
204 ;; MODULE and DESCRIPTION specify the docstring as per
205 ;; `find-docstring'. The file that `find-snarfed-docstring' looks in
206 ;; is automatically generated from MODULE.
207 (defun find-snarfed-docstring (module description
)
208 (let ((modpath (module-to-path module
)))
209 (cdr (or-map (function (lambda (root)
210 (find-docstring (concat root
216 docstring-snarfed-roots
219 ;; Find the tracking version of the specified docstring. MODULE and
220 ;; DESCRIPTION specify the docstring as per `find-docstring'. The
221 ;; file that `find-tracking-docstring' looks in is automatically
222 ;; generated from MODULE.
223 (defun find-tracking-docstring (module description
)
224 (find-docstring (concat docstring-tracking-root
226 (module-to-path module
)
231 ;; Extract an alist of modules and descriptions from the current
233 (defun make-module-description-list ()
237 (goto-char (point-min))
238 (while (re-search-forward "^\\(@c module \\|@deffnx? \\({[^}]+}\\|[^ ]+\\) \\([^ \n]+\\)\\)"
241 (let ((matched (buffer-substring (match-beginning 1)
243 (if (string-equal matched
"@c module ")
244 (setq module
(read (current-buffer)))
246 (concat (buffer-substring (match-beginning 2)
249 (buffer-substring (match-beginning 3)
251 (message "Found docstring: %S: %s" module matched
)
252 (let ((descriptions (assoc module alist
)))
254 (cons (cons module
(cons matched
(cdr-safe descriptions
)))
256 (delete descriptions alist
)
260 ;; Return the docstring from the specified LOCATION. LOCATION is a
261 ;; list of three elements: buffer, start position and end position.
262 (defun location-to-docstring (location)
265 (set-buffer (car location
))
266 (buffer-substring (cadr location
) (caddr location
)))))
268 ;; Perform a comparison of the specified docstring. MODULE and
269 ;; DESCRIPTION are as per usual.
270 (defun docstring-compare (module description
)
271 (let* ((manual-location (find-manual-docstring module description
))
272 (snarf-location (find-snarfed-docstring module description
))
273 (track-location (find-tracking-docstring module description
))
275 (manual-docstring (location-to-docstring manual-location
))
276 (snarf-docstring (location-to-docstring snarf-location
))
277 (track-docstring (location-to-docstring track-location
))
282 ;; Decide what to do.
283 (cond ((null snarf-location
)
285 issue
(if manual-location
289 ((null manual-location
)
290 (setq action
'add-to-manual issue nil
))
292 ((null track-location
)
294 issue
(if (string-equal manual-docstring snarf-docstring
)
298 ((string-equal track-docstring snarf-docstring
)
299 (setq action nil issue nil
))
301 ((string-equal track-docstring manual-docstring
)
302 (setq action
'auto-update-manual issue nil
))
305 (setq action nil issue
'update-needed
)))
307 ;; Return a pair indicating any automatic action that can be
308 ;; taken, and any issue for resolution.
309 (cons action issue
)))
311 ;; Add the specified docstring to the manual.
312 (defun docstring-add-to-manual (module description
)
313 (let ((buf (find-file-noselect (concat docstring-manual-directory
315 docstring-new-docstrings-file
))))
318 (goto-char (point-max))
319 (or (docstring-in-module module nil
)
320 (insert "\n@c module " (prin1-to-string module
) "\n"))
321 (insert "\n" (location-to-docstring (find-snarfed-docstring module
324 ;; Auto-update the specified docstring in the manual.
325 (defun docstring-auto-update-manual (module description
)
326 (let ((manual-location (find-manual-docstring module description
))
327 (track-location (find-tracking-docstring module description
)))
329 (set-buffer (car manual-location
))
330 (goto-char (cadr manual-location
))
331 (delete-region (cadr manual-location
) (caddr manual-location
))
332 (insert (location-to-docstring (find-snarfed-docstring module
335 ;; Process an alist of modules and descriptions, and produce a summary
336 ;; buffer describing actions taken and issues to be resolved.
337 (defun docstring-process-alist (alist)
338 (let (check-needed-list
340 consider-removal-list
342 auto-updated-manual-list
)
345 (function (lambda (module-list)
346 (let ((module (car module-list
)))
347 (message "Module: %S" module
)
349 (function (lambda (description)
350 (message "Comparing docstring: %S: %s" module description
)
351 (let* ((ai (docstring-compare module description
))
355 (cond ((eq action
'add-to-manual
)
356 (docstring-add-to-manual module description
)
357 (setq added-to-manual-list
358 (cons (cons module description
)
359 added-to-manual-list
)))
361 ((eq action
'auto-update-manual
)
362 (docstring-auto-update-manual module description
)
363 (setq auto-updated-manual-list
364 (cons (cons module description
)
365 auto-updated-manual-list
))))
367 (cond ((eq issue
'check-needed
)
368 (setq check-needed-list
369 (cons (cons module description
)
372 ((eq issue
'update-needed
)
373 (setq update-needed-list
374 (cons (cons module description
)
375 update-needed-list
)))
377 ((eq issue
'consider-removal
)
378 (setq consider-removal-list
379 (cons (cons module description
)
380 consider-removal-list
)))))))
381 (cdr module-list
)))))
384 ;; Prepare a buffer describing the results.
385 (set-buffer (get-buffer-create "*Docstring Results*"))
389 The following items have been automatically added to the manual in
390 file `" docstring-manual-directory
"/" docstring-new-docstrings-file
"'.\n\n")
391 (if added-to-manual-list
392 (mapcar (function (lambda (moddesc)
393 (insert (prin1-to-string (car moddesc
))
397 added-to-manual-list
)
401 The following items have been automatically updated in the manual.\n\n")
402 (if auto-updated-manual-list
403 (mapcar (function (lambda (moddesc)
404 (insert (prin1-to-string (car moddesc
))
408 auto-updated-manual-list
)
412 The following items are already documented in the manual but are not
413 mentioned in the reference copy of the snarfed docstrings file.
414 You should check that the manual documentation matches the docstring
415 in the current snarfed docstrings file.\n\n")
416 (if check-needed-list
417 (mapcar (function (lambda (moddesc)
418 (insert (prin1-to-string (car moddesc
))
426 The following items have manual documentation that is different from
427 the docstring in the reference copy of the snarfed docstrings file,
428 and the snarfed docstring has changed. You need to update the manual
429 documentation by hand with reference to the snarfed docstring changes.\n\n")
430 (if update-needed-list
431 (mapcar (function (lambda (moddesc)
432 (insert (prin1-to-string (car moddesc
))
440 The following items are documented in the manual but are no longer
441 present in the snarfed docstrings file. You should consider whether
442 the existing manual documentation is still pertinent. If it is, its
443 docstring module comment may need updating, to connect it with a
444 new snarfed docstring file.\n\n")
445 (if consider-removal-list
446 (mapcar (function (lambda (moddesc)
447 (insert (prin1-to-string (car moddesc
))
451 consider-removal-list
)
455 (goto-char (point-min))
457 ;; Popup the issues buffer.
458 (let ((pop-up-frames t
))
459 (set-window-point (display-buffer (current-buffer))
462 (defun docstring-process-current-buffer ()
464 (docstring-process-alist (make-module-description-list)))
466 (defun docstring-process-current-region (beg end
)
468 (narrow-to-region beg end
)
471 (docstring-process-alist (make-module-description-list)))
474 (defun docstring-process-module (module)
475 (interactive "xModule: ")
476 (let ((modpath (module-to-path module
))
478 (mapcar (function (lambda (root)
479 (let ((fn (concat root
483 (if (file-exists-p fn
)
486 (message "Getting docstring list from %s" fn
)
489 (make-module-description-list))))))))
490 docstring-snarfed-roots
)
491 (docstring-process-alist mdlist
)))
493 (defun docstring-ediff-this-line ()
499 (setq module
(read (current-buffer)))
501 (setq description
(buffer-substring (point)
506 (message "Ediff docstring: %S: %s" module description
)
508 (let ((track-location (or (find-tracking-docstring module description
)
509 (docstring-temp-location "No docstring in tracking file")))
510 (snarf-location (or (find-snarfed-docstring module description
)
511 (docstring-temp-location "No docstring in snarfed file")))
512 (manual-location (or (find-manual-docstring module description
)
513 (docstring-temp-location "No docstring in manual"))))
515 (setq docstring-ediff-buffers
516 (list (car track-location
)
518 (car manual-location
)))
520 (docstring-narrow-to-location track-location
)
521 (docstring-narrow-to-location snarf-location
)
522 (docstring-narrow-to-location manual-location
)
524 (add-hook 'ediff-quit-hook
'docstring-widen-ediff-buffers
)
526 (ediff-buffers3 (nth 0 docstring-ediff-buffers
)
527 (nth 1 docstring-ediff-buffers
)
528 (nth 2 docstring-ediff-buffers
)))))
530 (defun docstring-narrow-to-location (location)
532 (set-buffer (car location
))
533 (narrow-to-region (cadr location
) (caddr location
))))
535 (defun docstring-temp-location (str)
536 (let ((buf (generate-new-buffer "*Docstring Temp*")))
541 (list buf
(point-min) (point-max)))))
545 (defvar docstring-ediff-buffers
'())
547 (defun docstring-widen-ediff-buffers ()
548 (remove-hook 'ediff-quit-hook
'docstring-widen-ediff-buffers
)
550 (mapcar (function (lambda (buffer)
553 docstring-ediff-buffers
)))
558 ;(find-docstring "/home/neil/Guile/cvs/guile-core/doc/maint/guile.texi" nil "primitive sloppy-assq")
559 ;(find-manual-docstring '(guile) "primitive sloppy-assq")
560 ;(find-tracking-docstring '(guile) "primitive sloppy-assq")
561 ;(find-snarfed-docstring '(guile) "primitive sloppy-assq")