]> git.donarmstrong.com Git - lilypond.git/blob - guile18/doc/maint/docstring.el
Import guile-1.8 as multiple upstream tarball component
[lilypond.git] / guile18 / doc / maint / docstring.el
1 ;;; docstring.el --- utilities for Guile docstring maintenance
2 ;;;
3 ;;; Copyright (C) 2001, 2004 Neil Jerram
4 ;;;
5 ;;; This file is not part of GNU Emacs, but the same permissions apply.
6 ;;;
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.
11 ;;;
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.
16 ;;;
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.
21
22 ;;; Commentary:
23
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?
36 ;;
37 ;; This file implements an approach to this problem that I have found
38 ;; useful.  It involves keeping track of three copies of each
39 ;; docstring:
40 ;;
41 ;; "MANUAL"   = the docstring as it appears in the reference manual.
42 ;;
43 ;; "SNARFED"  = the docstring as snarfed from the current C or Scheme
44 ;;              source.
45 ;;
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.
49 ;;
50 ;; The approaches are as follows.
51 ;;
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.
55 ;;
56 ;; 2. Comparison of MANUAL-DOC, SOURCE-DOC and TRACK-DOC using Ediff.
57 ;;
58 ;; Here is a brief list of commands available (via "M-x COMMAND"):
59 ;;
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
65
66
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.")
70
71 (defvar guile-build-dir (or (getenv "GUILE_MAINTAINER_BUILD_CORE_DIR")
72                            guile-core-dir)
73   "*Full path of guile-core build directory.  Defaults to guile-core-dir.")
74
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.")
77
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.")
82
83 (defvar docstring-snarfed-roots (mapcar
84                                  #'(lambda (frag)
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.")
90
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.")
94
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.")
98
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)
102   (let ((result nil))
103     (while candidates
104       (setq result (apply fn (car candidates) args))
105       (if result
106           (setq result (cons (car candidates) result)
107                 candidates nil)
108         (setq candidates (cdr candidates))))
109     result))
110
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)
116   (save-excursion
117     (if (re-search-backward "^@c module-for-docstring " nil t)
118         (progn
119           (search-forward "@c module-for-docstring ")
120           (equal module (read (current-buffer))))
121       default-ok)))
122
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)
133                                    "[ \n\t]"))
134              found
135              result)
136          (save-excursion
137            (set-buffer buf)
138            (goto-char (point-min))
139            (while (and (not found)
140                        (re-search-forward deffn-regexp nil t))
141              (save-excursion
142                (goto-char (match-beginning 0))
143                (beginning-of-line)
144                (if (docstring-in-module module t)
145                    (setq found t))))
146            (if found
147                (setq result
148                      (list (current-buffer)
149                            (progn
150                              (re-search-backward "^@deffn ")
151                              (beginning-of-line)
152                              (point))
153                            (progn
154                              (re-search-forward "^@end deffn")
155                              (forward-line 1)
156                              (point))))))
157          result)))
158
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'
163 ;; variable.
164 (defun find-manual-docstring (module description)
165   (let* ((result
166           (or-map 'find-docstring
167                   (mapcar (function (lambda (file-name)
168                                       (concat docstring-manual-directory
169                                               "/"
170                                               file-name)))
171                           (cons docstring-new-docstrings-file
172                                 docstring-manual-files))
173                   (list module
174                         description)))
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))))
181     (cdr result)))
182
183 ;; Convert MODULE to a directory subpath.
184 (defun module-to-path (module)
185   (mapconcat (function (lambda (component)
186                          (symbol-name component)))
187              module
188              "/"))
189
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
198                                                      "/"
199                                                      modpath
200                                                      ".texi")
201                                              module
202                                              description)))
203                  docstring-snarfed-roots
204                  nil))))
205
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
212                           "/"
213                           (module-to-path module)
214                           ".texi")
215                   module
216                   description))
217
218 ;; Extract an alist of modules and descriptions from the current
219 ;; buffer.
220 (defun make-module-description-list ()
221   (let ((alist nil)
222         (module '(guile)))
223     (save-excursion
224       (goto-char (point-min))
225       (while (re-search-forward "^\\(@c module-for-docstring \\|@deffnx? \\({[^}]+}\\|[^ ]+\\) \\([^ \n]+\\)\\)"
226                                 nil
227                                 t)
228         (let ((matched (buffer-substring (match-beginning 1)
229                                          (match-end 1))))
230           (if (string-equal matched "@c module-for-docstring ")
231               (setq module (read (current-buffer)))
232             (let ((type (buffer-substring (match-beginning 2)
233                                           (match-end 2))))
234               (if (string-equal type "{C Function}")
235                   nil
236                 (setq matched
237                       (concat type
238                               " "
239                               (buffer-substring (match-beginning 3)
240                                                 (match-end 3))))
241                 (message "Found docstring: %S: %s" module matched)
242                 (let ((descriptions (assoc module alist)))
243                   (setq alist
244                         (cons (cons module (cons matched (cdr-safe descriptions)))
245                               (if descriptions
246                                   (delete descriptions alist)
247                                 alist))))))))))
248     alist))
249
250 ;; missing in some environments?
251 (defun caddr (list)
252   (nth 2 list))
253
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)
257   (and location
258        (save-excursion
259          (set-buffer (car location))
260          (buffer-substring (cadr location) (caddr location)))))
261
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))
268
269          (manual-docstring (location-to-docstring manual-location))
270          (snarf-docstring (location-to-docstring snarf-location))
271          (track-docstring (location-to-docstring track-location))
272
273          action
274          issue)
275
276     ;; Decide what to do.
277     (cond ((null snarf-location)
278            (setq action nil
279                  issue (if manual-location
280                            'consider-removal
281                          nil)))
282
283           ((null manual-location)
284            (setq action 'add-to-manual issue nil))
285
286           ((null track-location)
287            (setq action nil
288                  issue (if (string-equal manual-docstring snarf-docstring)
289                            nil
290                          'check-needed)))
291
292           ((string-equal track-docstring snarf-docstring)
293            (setq action nil issue nil))
294
295           ((string-equal track-docstring manual-docstring)
296            (setq action 'auto-update-manual issue nil))
297
298           (t
299            (setq action nil issue 'update-needed)))
300
301     ;; Return a pair indicating any automatic action that can be
302     ;; taken, and any issue for resolution.
303     (cons action issue)))
304
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
308                                          "/"
309                                          docstring-new-docstrings-file))))
310     (save-excursion
311       (set-buffer buf)
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
316                                                                   description))))))
317
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)))
322     (save-excursion
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
327                                                              description))))))
328
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
333         update-needed-list
334         consider-removal-list
335         added-to-manual-list
336         auto-updated-manual-list)
337
338     (mapcar
339      (function (lambda (module-list)
340                  (let ((module (car module-list)))
341                    (message "Module: %S" module)
342                    (mapcar
343                     (function (lambda (description)
344                                 (message "Comparing docstring: %S: %s" module description)
345                                 (let* ((ai (docstring-compare module description))
346                                        (action (car ai))
347                                        (issue (cdr ai)))
348
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)))
354
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))))
360
361                                   (cond ((eq issue 'check-needed)
362                                          (setq check-needed-list
363                                                (cons (cons module description)
364                                                      check-needed-list)))
365
366                                         ((eq issue 'update-needed)
367                                          (setq update-needed-list
368                                                (cons (cons module description)
369                                                      update-needed-list)))
370
371                                         ((eq issue 'consider-removal)
372                                          (setq consider-removal-list
373                                                (cons (cons module description)
374                                                      consider-removal-list)))))))
375                     (reverse (cdr module-list))))))
376      alist)
377
378     ;; Prepare a buffer describing the results.
379     (set-buffer (get-buffer-create "*Docstring Results*"))
380     (erase-buffer)
381
382     (insert "
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))
388                                     ": "
389                                     (cdr moddesc)
390                                     "\n")))
391                 added-to-manual-list)
392       (insert "(none)\n"))
393
394     (insert "
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))
399                                     ": "
400                                     (cdr moddesc)
401                                     "\n")))
402                 auto-updated-manual-list)
403       (insert "(none)\n"))
404
405     (insert "
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))
413                                     ": "
414                                     (cdr moddesc)
415                                     "\n")))
416                 check-needed-list)
417       (insert "(none)\n"))
418
419     (insert "
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))
427                                     ": "
428                                     (cdr moddesc)
429                                     "\n")))
430                 update-needed-list)
431       (insert "(none)\n"))
432
433     (insert "
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))
442                                     ": "
443                                     (cdr moddesc)
444                                     "\n")))
445                 consider-removal-list)
446       (insert "(none)\n"))
447     (insert "\n")
448
449     (goto-char (point-min))
450     (local-set-key "d" 'docstring-ediff-this-line)
451
452     ;; Popup the issues buffer.
453     (let ((pop-up-frames t))
454       (set-window-point (display-buffer (current-buffer))
455                         (point-min)))))
456
457 (defun docstring-process-current-buffer ()
458   (interactive)
459   (docstring-process-alist (make-module-description-list)))
460
461 (defun docstring-process-current-region (beg end)
462   (interactive "r")
463   (narrow-to-region beg end)
464   (unwind-protect
465       (save-excursion
466         (docstring-process-alist (make-module-description-list)))
467     (widen)))
468
469 (defun docstring-process-module (module)
470   (interactive "xModule: ")
471   (let ((modpath (module-to-path module))
472         (mdlist nil))
473     (mapcar (function (lambda (root)
474                         (let ((fn (concat root
475                                           "/"
476                                           modpath
477                                           ".texi")))
478                           (if (file-exists-p fn)
479                               (save-excursion
480                                 (find-file fn)
481                                 (message "Getting docstring list from %s" fn)
482                                 (setq mdlist
483                                       (append mdlist
484                                               (make-module-description-list))))))))
485             docstring-snarfed-roots)
486     (docstring-process-alist mdlist)))
487
488 (defun docstring-ediff-this-line ()
489   (interactive)
490   (let (module
491         description)
492     (save-excursion
493       (beginning-of-line)
494       (setq module (read (current-buffer)))
495       (forward-char 2)
496       (setq description (buffer-substring (point)
497                                           (progn
498                                             (end-of-line)
499                                             (point)))))
500
501     (message "Ediff docstring: %S: %s" module description)
502
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"))))
509
510       (setq docstring-ediff-buffers
511             (list (car track-location)
512                   (car snarf-location)
513                   (car manual-location)))
514
515       (docstring-narrow-to-location track-location)
516       (docstring-narrow-to-location snarf-location)
517       (docstring-narrow-to-location manual-location)
518
519       (add-hook 'ediff-quit-hook 'docstring-widen-ediff-buffers)
520
521     (ediff-buffers3 (nth 0 docstring-ediff-buffers)
522                     (nth 1 docstring-ediff-buffers)
523                     (nth 2 docstring-ediff-buffers)))))
524
525 (defun docstring-narrow-to-location (location)
526   (save-excursion
527     (set-buffer (car location))
528     (narrow-to-region (cadr location) (caddr location))))
529
530 (defun docstring-temp-location (str)
531   (let ((buf (generate-new-buffer "*Docstring Temp*")))
532     (save-excursion
533       (set-buffer buf)
534       (erase-buffer)
535       (insert str "\n")
536       (list buf (point-min) (point-max)))))
537
538 (require 'ediff)
539
540 (defvar docstring-ediff-buffers '())
541
542 (defun docstring-widen-ediff-buffers ()
543   (remove-hook 'ediff-quit-hook 'docstring-widen-ediff-buffers)
544   (save-excursion
545     (mapcar (function (lambda (buffer)
546                         (set-buffer buffer)
547                         (widen)))
548             docstring-ediff-buffers)))
549
550
551 ;;; Tests:
552
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")
557
558 (defvar docstring-libguile-directory (expand-file-name "libguile"
559                                                        guile-core-dir)
560   "*The directory containing the C source for libguile.")
561
562 (defvar docstring-libguile-build-directory (expand-file-name "libguile"
563                                                              guile-build-dir)
564   "*The directory containing the libguile build directory.")
565
566 (defun docstring-display-location (file line)
567   (let ((buffer (find-file-noselect
568                  (expand-file-name file docstring-libguile-directory))))
569     (if buffer
570         (let* ((window (or (get-buffer-window buffer)
571                            (display-buffer buffer)))
572                (pos (save-excursion
573                       (set-buffer buffer)
574                       (goto-line line)
575                       (point))))
576           (set-window-point window pos)))))
577
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.
584
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."
589   (interactive)
590   (let* ((deffn-line
591            (save-excursion
592              (end-of-line)
593              (or (re-search-backward "^@deffn " nil t)
594                  (error "No docstring here!"))
595              (buffer-substring (point)
596                                (progn
597                                  (end-of-line)
598                                  (point)))))
599          (guile-texi-file
600           (expand-file-name "guile.texi" docstring-libguile-build-directory))
601          (source-location
602           (save-excursion
603             (set-buffer (find-file-noselect guile-texi-file))
604             (save-excursion
605               (goto-char (point-min))
606               (or (re-search-forward (concat "^"
607                                              (regexp-quote deffn-line)
608                                              "$")
609                                      nil t)
610                   (error "Docstring not from libguile"))
611               (forward-line -1)
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))))
618
619
620 (provide 'docstring)
621
622 ;;; docstring.el ends here