1 ;;; docstring.el --- utilities for Guile docstring maintenance
3 ;;; Copyright (C) 2001, 2004 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., 51 Franklin Street, Fifth Floor,
20 ;;; Boston, MA 02110-1301, 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.
58 ;; Here is a brief list of commands available (via "M-x COMMAND"):
60 ;; docstring-process-current-buffer
61 ;; docstring-process-current-region BEG END
62 ;; docstring-process-module MODULE
63 ;; docstring-ediff-this-line
64 ;; docstring-show-source
67 (defvar guile-core-dir (or (getenv "GUILE_MAINTAINER_GUILE_CORE_DIR")
68 (error "GUILE_MAINTAINER_GUILE_CORE_DIR not set"))
69 "*Full path of guile-core source directory.")
71 (defvar guile-build-dir (or (getenv "GUILE_MAINTAINER_BUILD_CORE_DIR")
73 "*Full path of guile-core build directory. Defaults to guile-core-dir.")
75 (defvar docstring-manual-directory (expand-file-name "doc/ref" guile-core-dir)
76 "*The directory containing the Texinfo source for the Guile reference manual.")
78 (defvar docstring-tracking-root (expand-file-name "doc/maint" guile-core-dir)
79 "*Root directory for docstring tracking files. The tracking file
80 for module (a b c) is expected to be in the file
81 <docstring-tracking-root>/a/b/c.texi.")
83 (defvar docstring-snarfed-roots (mapcar
85 (expand-file-name frag guile-build-dir))
86 '("libguile" "ice-9" "oop"))
87 "*List of possible root directories for snarfed docstring files.
88 For each entry in this list, the snarfed docstring file for module (a
89 b c) is looked for in the file <entry>/a/b/c.texi.")
91 (defvar docstring-manual-files
92 (directory-files docstring-manual-directory nil "\\.texi$" t)
93 "List of Texinfo source files that comprise the Guile reference manual.")
95 (defvar docstring-new-docstrings-file "new-docstrings.texi"
96 "The name of a file in the Guile reference manual source directory
97 to which new docstrings should be added.")
99 ;; Apply FN in turn to each element in the list CANDIDATES until the
100 ;; first application that returns non-nil.
101 (defun or-map (fn candidates args)
104 (setq result (apply fn (car candidates) args))
106 (setq result (cons (car candidates) result)
108 (setq candidates (cdr candidates))))
111 ;; Return t if the current buffer position is in the scope of the
112 ;; specified MODULE, as determined by "@c module-for-docstring ..." comments in the
113 ;; buffer. DEFAULT-OK specifies the return value in the case that
114 ;; there are no preceding module comments at all.
115 (defun docstring-in-module (module default-ok)
117 (if (re-search-backward "^@c module-for-docstring " nil t)
119 (search-forward "@c module-for-docstring ")
120 (equal module (read (current-buffer))))
123 ;; Find a docstring in the specified FILE-NAME for the item in module
124 ;; MODULE and with description DESCRIPTION. MODULE should be a list
125 ;; of symbols, Guile-style, for example: '(ice-9 session).
126 ;; DESCRIPTION should be the string that is expected after the @deffn,
127 ;; for example "primitive acons" or "syntax let*".
128 (defun find-docstring (file-name module description)
129 (and (file-exists-p file-name)
130 (let ((buf (find-file-noselect file-name))
131 (deffn-regexp (concat "^@deffnx? "
132 (regexp-quote description)
138 (goto-char (point-min))
139 (while (and (not found)
140 (re-search-forward deffn-regexp nil t))
142 (goto-char (match-beginning 0))
144 (if (docstring-in-module module t)
148 (list (current-buffer)
150 (re-search-backward "^@deffn ")
154 (re-search-forward "^@end deffn")
159 ;; Find the reference manual version of the specified docstring.
160 ;; MODULE and DESCRIPTION specify the docstring as per
161 ;; `find-docstring'. The set of files that `find-manual-docstring'
162 ;; searches is determined by the value of the `docstring-manual-files'
164 (defun find-manual-docstring (module description)
166 (or-map 'find-docstring
167 (mapcar (function (lambda (file-name)
168 (concat docstring-manual-directory
171 (cons docstring-new-docstrings-file
172 docstring-manual-files))
175 (matched-file-name (and (cdr result)
176 (file-name-nondirectory (car result)))))
177 (if matched-file-name
178 (setq docstring-manual-files
179 (cons matched-file-name
180 (delete matched-file-name docstring-manual-files))))
183 ;; Convert MODULE to a directory subpath.
184 (defun module-to-path (module)
185 (mapconcat (function (lambda (component)
186 (symbol-name component)))
190 ;; Find the current snarfed version of the specified docstring.
191 ;; MODULE and DESCRIPTION specify the docstring as per
192 ;; `find-docstring'. The file that `find-snarfed-docstring' looks in
193 ;; is automatically generated from MODULE.
194 (defun find-snarfed-docstring (module description)
195 (let ((modpath (module-to-path module)))
196 (cdr (or-map (function (lambda (root)
197 (find-docstring (concat root
203 docstring-snarfed-roots
206 ;; Find the tracking version of the specified docstring. MODULE and
207 ;; DESCRIPTION specify the docstring as per `find-docstring'. The
208 ;; file that `find-tracking-docstring' looks in is automatically
209 ;; generated from MODULE.
210 (defun find-tracking-docstring (module description)
211 (find-docstring (concat docstring-tracking-root
213 (module-to-path module)
218 ;; Extract an alist of modules and descriptions from the current
220 (defun make-module-description-list ()
224 (goto-char (point-min))
225 (while (re-search-forward "^\\(@c module-for-docstring \\|@deffnx? \\({[^}]+}\\|[^ ]+\\) \\([^ \n]+\\)\\)"
228 (let ((matched (buffer-substring (match-beginning 1)
230 (if (string-equal matched "@c module-for-docstring ")
231 (setq module (read (current-buffer)))
232 (let ((type (buffer-substring (match-beginning 2)
234 (if (string-equal type "{C Function}")
239 (buffer-substring (match-beginning 3)
241 (message "Found docstring: %S: %s" module matched)
242 (let ((descriptions (assoc module alist)))
244 (cons (cons module (cons matched (cdr-safe descriptions)))
246 (delete descriptions alist)
250 ;; missing in some environments?
254 ;; Return the docstring from the specified LOCATION. LOCATION is a
255 ;; list of three elements: buffer, start position and end position.
256 (defun location-to-docstring (location)
259 (set-buffer (car location))
260 (buffer-substring (cadr location) (caddr location)))))
262 ;; Perform a comparison of the specified docstring. MODULE and
263 ;; DESCRIPTION are as per usual.
264 (defun docstring-compare (module description)
265 (let* ((manual-location (find-manual-docstring module description))
266 (snarf-location (find-snarfed-docstring module description))
267 (track-location (find-tracking-docstring module description))
269 (manual-docstring (location-to-docstring manual-location))
270 (snarf-docstring (location-to-docstring snarf-location))
271 (track-docstring (location-to-docstring track-location))
276 ;; Decide what to do.
277 (cond ((null snarf-location)
279 issue (if manual-location
283 ((null manual-location)
284 (setq action 'add-to-manual issue nil))
286 ((null track-location)
288 issue (if (string-equal manual-docstring snarf-docstring)
292 ((string-equal track-docstring snarf-docstring)
293 (setq action nil issue nil))
295 ((string-equal track-docstring manual-docstring)
296 (setq action 'auto-update-manual issue nil))
299 (setq action nil issue 'update-needed)))
301 ;; Return a pair indicating any automatic action that can be
302 ;; taken, and any issue for resolution.
303 (cons action issue)))
305 ;; Add the specified docstring to the manual.
306 (defun docstring-add-to-manual (module description)
307 (let ((buf (find-file-noselect (concat docstring-manual-directory
309 docstring-new-docstrings-file))))
312 (goto-char (point-max))
313 (or (docstring-in-module module nil)
314 (insert "\n@c module-for-docstring " (prin1-to-string module) "\n"))
315 (insert "\n" (location-to-docstring (find-snarfed-docstring module
318 ;; Auto-update the specified docstring in the manual.
319 (defun docstring-auto-update-manual (module description)
320 (let ((manual-location (find-manual-docstring module description))
321 (track-location (find-tracking-docstring module description)))
323 (set-buffer (car manual-location))
324 (goto-char (cadr manual-location))
325 (delete-region (cadr manual-location) (caddr manual-location))
326 (insert (location-to-docstring (find-snarfed-docstring module
329 ;; Process an alist of modules and descriptions, and produce a summary
330 ;; buffer describing actions taken and issues to be resolved.
331 (defun docstring-process-alist (alist)
332 (let (check-needed-list
334 consider-removal-list
336 auto-updated-manual-list)
339 (function (lambda (module-list)
340 (let ((module (car module-list)))
341 (message "Module: %S" module)
343 (function (lambda (description)
344 (message "Comparing docstring: %S: %s" module description)
345 (let* ((ai (docstring-compare module description))
349 (cond ((eq action 'add-to-manual)
350 (docstring-add-to-manual module description)
351 (setq added-to-manual-list
352 (cons (cons module description)
353 added-to-manual-list)))
355 ((eq action 'auto-update-manual)
356 (docstring-auto-update-manual module description)
357 (setq auto-updated-manual-list
358 (cons (cons module description)
359 auto-updated-manual-list))))
361 (cond ((eq issue 'check-needed)
362 (setq check-needed-list
363 (cons (cons module description)
366 ((eq issue 'update-needed)
367 (setq update-needed-list
368 (cons (cons module description)
369 update-needed-list)))
371 ((eq issue 'consider-removal)
372 (setq consider-removal-list
373 (cons (cons module description)
374 consider-removal-list)))))))
375 (reverse (cdr module-list))))))
378 ;; Prepare a buffer describing the results.
379 (set-buffer (get-buffer-create "*Docstring Results*"))
383 The following items have been automatically added to the manual in
384 file `" docstring-manual-directory "/" docstring-new-docstrings-file "'.\n\n")
385 (if added-to-manual-list
386 (mapcar (function (lambda (moddesc)
387 (insert (prin1-to-string (car moddesc))
391 added-to-manual-list)
395 The following items have been automatically updated in the manual.\n\n")
396 (if auto-updated-manual-list
397 (mapcar (function (lambda (moddesc)
398 (insert (prin1-to-string (car moddesc))
402 auto-updated-manual-list)
406 The following items are already documented in the manual but are not
407 mentioned in the reference copy of the snarfed docstrings file.
408 You should check that the manual documentation matches the docstring
409 in the current snarfed docstrings file.\n\n")
410 (if check-needed-list
411 (mapcar (function (lambda (moddesc)
412 (insert (prin1-to-string (car moddesc))
420 The following items have manual documentation that is different from
421 the docstring in the reference copy of the snarfed docstrings file,
422 and the snarfed docstring has changed. You need to update the manual
423 documentation by hand with reference to the snarfed docstring changes.\n\n")
424 (if update-needed-list
425 (mapcar (function (lambda (moddesc)
426 (insert (prin1-to-string (car moddesc))
434 The following items are documented in the manual but are no longer
435 present in the snarfed docstrings file. You should consider whether
436 the existing manual documentation is still pertinent. If it is, its
437 docstring module comment may need updating, to connect it with a
438 new snarfed docstring file.\n\n")
439 (if consider-removal-list
440 (mapcar (function (lambda (moddesc)
441 (insert (prin1-to-string (car moddesc))
445 consider-removal-list)
449 (goto-char (point-min))
450 (local-set-key "d" 'docstring-ediff-this-line)
452 ;; Popup the issues buffer.
453 (let ((pop-up-frames t))
454 (set-window-point (display-buffer (current-buffer))
457 (defun docstring-process-current-buffer ()
459 (docstring-process-alist (make-module-description-list)))
461 (defun docstring-process-current-region (beg end)
463 (narrow-to-region beg end)
466 (docstring-process-alist (make-module-description-list)))
469 (defun docstring-process-module (module)
470 (interactive "xModule: ")
471 (let ((modpath (module-to-path module))
473 (mapcar (function (lambda (root)
474 (let ((fn (concat root
478 (if (file-exists-p fn)
481 (message "Getting docstring list from %s" fn)
484 (make-module-description-list))))))))
485 docstring-snarfed-roots)
486 (docstring-process-alist mdlist)))
488 (defun docstring-ediff-this-line ()
494 (setq module (read (current-buffer)))
496 (setq description (buffer-substring (point)
501 (message "Ediff docstring: %S: %s" module description)
503 (let ((track-location (or (find-tracking-docstring module description)
504 (docstring-temp-location "No docstring in tracking file")))
505 (snarf-location (or (find-snarfed-docstring module description)
506 (docstring-temp-location "No docstring in snarfed file")))
507 (manual-location (or (find-manual-docstring module description)
508 (docstring-temp-location "No docstring in manual"))))
510 (setq docstring-ediff-buffers
511 (list (car track-location)
513 (car manual-location)))
515 (docstring-narrow-to-location track-location)
516 (docstring-narrow-to-location snarf-location)
517 (docstring-narrow-to-location manual-location)
519 (add-hook 'ediff-quit-hook 'docstring-widen-ediff-buffers)
521 (ediff-buffers3 (nth 0 docstring-ediff-buffers)
522 (nth 1 docstring-ediff-buffers)
523 (nth 2 docstring-ediff-buffers)))))
525 (defun docstring-narrow-to-location (location)
527 (set-buffer (car location))
528 (narrow-to-region (cadr location) (caddr location))))
530 (defun docstring-temp-location (str)
531 (let ((buf (generate-new-buffer "*Docstring Temp*")))
536 (list buf (point-min) (point-max)))))
540 (defvar docstring-ediff-buffers '())
542 (defun docstring-widen-ediff-buffers ()
543 (remove-hook 'ediff-quit-hook 'docstring-widen-ediff-buffers)
545 (mapcar (function (lambda (buffer)
548 docstring-ediff-buffers)))
553 ;(find-docstring "/home/neil/Guile/cvs/guile-core/doc/maint/guile.texi" nil "primitive sloppy-assq")
554 ;(find-manual-docstring '(guile) "primitive sloppy-assq")
555 ;(find-tracking-docstring '(guile) "primitive sloppy-assq")
556 ;(find-snarfed-docstring '(guile) "primitive sloppy-assq")
558 (defvar docstring-libguile-directory (expand-file-name "libguile"
560 "*The directory containing the C source for libguile.")
562 (defvar docstring-libguile-build-directory (expand-file-name "libguile"
564 "*The directory containing the libguile build directory.")
566 (defun docstring-display-location (file line)
567 (let ((buffer (find-file-noselect
568 (expand-file-name file docstring-libguile-directory))))
570 (let* ((window (or (get-buffer-window buffer)
571 (display-buffer buffer)))
576 (set-window-point window pos)))))
578 (defun docstring-show-source ()
579 "Given that point is sitting in a docstring in one of the Texinfo
580 source files for the Guile manual, and that that docstring may be
581 snarfed automatically from a libguile C file, determine whether the
582 docstring is from libguile and, if it is, display the relevant C file
583 at the line from which the docstring was snarfed.
585 Why? When updating snarfed docstrings, you should usually edit the C
586 source rather than the Texinfo source, so that your updates benefit
587 Guile's online help as well. This function locates the C source for a
588 docstring so that it is easy for you to do this."
593 (or (re-search-backward "^@deffn " nil t)
594 (error "No docstring here!"))
595 (buffer-substring (point)
600 (expand-file-name "guile.texi" docstring-libguile-build-directory))
603 (set-buffer (find-file-noselect guile-texi-file))
605 (goto-char (point-min))
606 (or (re-search-forward (concat "^"
607 (regexp-quote deffn-line)
610 (error "Docstring not from libguile"))
612 (if (looking-at "^@c snarfed from \\([^:]+\\):\\([0-9]+\\)$")
613 (cons (match-string 1)
614 (string-to-int (match-string 2)))
615 (error "Corrupt docstring entry in guile.texi"))))))
616 (docstring-display-location (car source-location)
617 (cdr source-location))))
622 ;;; docstring.el ends here