1;; guile-gnome
2;; Copyright (C) 2007, 2011 Free Software Foundation
3
4;; This program is free software; you can redistribute it and/or
5;; modify it under the terms of the GNU General Public License as
6;; published by the Free Software Foundation; either version 2 of
7;; the License, or (at your option) any later version.
8;;
9;; This program is distributed in the hope that it will be useful,
10;; but WITHOUT ANY WARRANTY; without even the implied warranty of
11;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12;; GNU General Public License for more details.
13;;
14;; You should have received a copy of the GNU General Public License
15;; along with this program; if not, contact:
16;;
17;; Free Software Foundation           Voice:  +1-617-542-5942
18;; 59 Temple Place - Suite 330        Fax:    +1-617-542-2652
19;; Boston, MA  02111-1307,  USA       gnu@gnu.org
20
21;;; Commentary:
22;;
23;; This module exports two high-level procedures to transform the
24;; Docbook files generated by GTK-Doc into texinfo.
25;;
26;; @uref{http://www.gtk.org/gtk-doc/,GTK-Doc} is commonly used to
27;; document GObject-based libraries, such as those that Guile-GNOME
28;; wraps. In a typical build setup, GTK-Doc generates a reference manual
29;; with one XML file per section. The routines in this module attempt to
30;; recreate those sections, but in Texinfo instead of Docbook, and which
31;; document the Scheme modules instead of the upstream C libraries.
32;;
33;; The tricky part of translating GTK-Doc's documentation is not the
34;; vocabulary (Docbook), but that it documents C functions which have
35;; different calling conventions than Scheme. For example, a C function
36;; might take four @code{double*} arguments, but in Scheme the function
37;; would return four rational values. Given only the C prototype, the
38;; code in this module will make an attempt to determine what the Scheme
39;; function's arguments will be based on some heuristics.
40;;
41;; In most cases, however, we can do better than heuristics, because we
42;; have the G-Wrap information that describes the relationship between
43;; the C function and the Scheme wrapper. In that way we can know
44;; exactly what the input and output arguments are for a particular
45;; function.
46;;
47;; The @code{gtk-doc->texi-stubs} function is straightforward. It
48;; extracts the "header" in a set of GTK-Doc files, translates them into
49;; texinfo, writing them out one by one to files named
50;; @samp{section-@var{foo}.texi}, where @var{foo} is the name of the XML
51;; file. It is unclear whether it is best to continously generate these
52;; sections when updating the manuals, or whether this "stub" generation
53;; should be run only once when the documentation is initially
54;; generated, and thereafter maintained by hand. Your call!
55;;
56;; @code{gtk-doc->texi-defuns} is slightly more complicated, because you
57;; have the choice as to whether to use heuristics or the g-wrap method
58;; for determining the arguments. See its documentation for more
59;; information.
60;;
61;; Both of these functions are designed to be directly callable from the
62;; shell. Here is a makefile snippet suitable for using the heuristics
63;; method for defuns generation:
64;;
65;; @example
66;; GTK_DOC_TO_TEXI_STUBS = \
67;;   '((@@ (gnome gw support gtk-doc) gtk-doc->texi-stubs) \
68;;   (cdr (program-arguments)))'
69;; GTK_DOC_DEFUN_METHOD = heuristics
70;; GTK_DOC_DEFUN_ARGS = (your-module-here)
71;; GTK_DOC_TO_TEXI_DEFUNS = "(apply (@@ (gnome gw support gtk-doc) \
72;;    gtk-doc->texi-defuns) (cadr (program-arguments)) \
73;;    '$(GTK_DOC_DEFUN_METHOD) '($(GTK_DOC_DEFUN_ARGS)) \
74;;    (cddr (program-arguments)))"
75;; GUILE = $(top_builddir)/dev-environ guile
76;;
77;; generate-stubs:
78;;      $(GUILE) $(GUILE_FLAGS) -c $(GTK_DOC_TO_TEXI_STUBS) \
79;;         $(docbook_xml_files)
80;;
81;; generate-defuns:
82;; 	$(GUILE) $(GUILE_FLAGS) -c $(GTK_DOC_TO_TEXI_DEFUNS) \
83;;         ./overrides.texi $(docbook_xml_files)
84;; @end example
85;;
86;; To make the above snippet work, you will have to define
87;; @code{$(docbook_xml_files)} as the set of docbook XML files to
88;; transform. To use the G-Wrap method, try the following:
89;;
90;; @example
91;; wrapset_module = (gnome gw $(wrapset_stem)-spec)
92;; wrapset_name = gnome-$(wrapset_stem)
93;; GTK_DOC_DEFUN_METHOD = g-wrap
94;; GTK_DOC_DEFUN_ARGS = $(wrapset_module) $(wrapset_name)
95;; @end example
96;;
97;; Set @code{$(wrapset_stem)} to the stem of the wrapset name, e.g.
98;; @code{pango}, and there you are.
99;;
100;;; Code:
101
102(define-module (gnome gw support gtk-doc)
103  #:use-module (sxml ssax)
104  #:use-module ((sxml xpath) #:select (sxpath))
105  #:use-module (sxml transform)
106  #:use-module (ice-9 regex)
107  #:use-module ((srfi srfi-1)
108                #:select (append-map (fold . srfi-1:fold) lset-difference))
109  #:use-module (srfi srfi-13)
110
111  #:use-module (texinfo)
112  #:use-module (texinfo docbook)
113  #:use-module (texinfo reflection)
114  #:use-module (texinfo serialize)
115  #:use-module (match-bind)
116
117  #:use-module (g-wrap)
118  #:use-module (g-wrap guile) ;; for the `module' generic
119  #:use-module (gnome gobject)
120  #:use-module (gnome gobject utils)
121  #:use-module (oop goops)
122
123  #:use-module (gnome gobject utils)
124
125  #:export (gtk-doc->texi-stubs
126            gtk-doc->texi-defuns
127            check-documentation-coverage
128            generate-undocumented-texi))
129
130(define (attr-ref attrs name . default)
131  (or (and=> (assq name (cdr attrs)) cadr)
132      (if (pair? default)
133          (car default)
134          (error "missing attribute" name))))
135
136(eval-when (expand load eval)
137  ;; Make SSAX understand   and % -- nasty, but that's how it
138  ;; is
139  (for-each
140      (lambda (pair)
141	  (define-parsed-entity! (car pair) (cdr pair)))
142      '((nbsp . " ")
143	(percnt . "%")
144	(oacute . "ó")
145	(sol . "/")
146	(mdash . "—")
147	(ast . "*")
148	(num . "#")
149	(times . "✕")
150	(ldquo . "“")
151	(rdquo . "”")
152	(hash . "#"))))
153
154(define (zap-whitespace sxml)
155  (define (not-whitespace x)
156    (or (not (string? x))
157        (not (string-every char-whitespace? x))))
158  (pre-post-order sxml
159                  `((*default* . ,(lambda (tag . body)
160                                    (cons tag
161                                          (filter not-whitespace body))))
162                    (*text* . ,(lambda (tag text) text)))))
163
164(define (docbook->sdocbook docbook-fragment)
165  "Parse a docbook file @var{docbook-fragment} into SXML. Simply calls
166SSAX's @code{xml->sxml}, but having made sure that @samp{ }
167elements are interpreted correctly. Does not deal with XInclude."
168  (zap-whitespace
169   (call-with-input-file docbook-fragment
170     (lambda (port) (ssax:xml->sxml port '())))))
171
172(define (sdocbook-fold-defuns proc seed sdocbook-fragment)
173  "Fold over the defuns in the gtk-doc-generated docbook fragment
174@var{sdocbook-fragment}. Very dependent on the form of docbook that
175gtk-doc emits."
176  (let lp ((in ((sxpath '(refentry
177                          refsect1
178                          (refsect2 (@
179                                     role
180                                     (equal? "function")))))
181                sdocbook-fragment))
182           (seed seed))
183    (if (null? in)
184        seed
185        (lp (cdr in) (proc (car in) seed)))))
186
187(define (sdocbook-fold-structs proc seed sdocbook-fragment)
188  "Fold over the struct definitions in the gtk-doc-generated docbook
189fragment @var{sdocbook-fragment}. Very dependent on the form of docbook
190that gtk-doc emits. Normally this corresponds to the set of classes
191exported by the module, although it can contain other things."
192  (let lp ((in (map cddr ((sxpath '(refentry refsect1 refsect2
193                                    (title (anchor @ role
194                                            (equal? "struct")))))
195                          sdocbook-fragment)))
196           (seed seed))
197    (cond
198     ((null? in) seed)
199     ((and (pair? (car in)) (string? (caar in)))
200      (lp (cdr in) (proc (caar in) seed)))
201     (else
202      (lp (cdr in) seed)))))
203
204(define (identity . args) args)
205
206(define strip-final-parens (s/// " *\\(\\)$" ""))
207
208(define *gtk-doc-sdocbook->stexi-rules*
209  `((variablelist
210     ((varlistentry
211       . ,(lambda (tag term . body)
212            `(entry (% (heading ,@(cdr term))) ,@body)))
213      (listitem
214       . ,(lambda (tag . rest)
215            (cond ((null? rest)
216                   (warn "null listitem")
217                   '(*fragment*))
218                  ((pair? (car rest))
219                   (if (not (null? (cdr rest)))
220                       (warn "ignoring listitem extra contents:" (cddr rest)))
221                   (car rest))
222                  (else
223                   (list 'para rest))))))
224     . ,(lambda (tag attrs . body)
225          `(table (% (formatter (var))) ,@body)))
226    (term
227     . ,(lambda (tag param . rest)
228          (if (pair? param)
229              param
230              (list 'var param))))
231    (parameter
232     . ,(lambda (tag body)
233          `(var ,(gtype-name->scheme-name body))))
234    (type
235     . ,(lambda (tag body)
236          `(code ,(if (string? body)
237                      (symbol->string (gtype-name->class-name body))
238                      body))))
239    (function
240     . ,(lambda (tag body . ignored)
241          (or (null? ignored) (warn "ignored function tail" ignored))
242          `(code ,(if (pair? body) body
243                      (gtype-name->scheme-name (strip-final-parens body))))))
244    (xref . ,(lambda (tag attrs)
245               `(emph "(the missing figure, " ,(cadr (assq 'linkend (cdr attrs))))))
246    (figure
247     *preorder*
248     . ,(lambda (tag attrs . body)
249          `(para "(The missing figure, "  ,(cadr (assq 'id (cdr attrs))))))
250    (indexterm
251     *preorder*
252     . ,(lambda (tag . body)
253          (let ((entry (string-join
254                        (apply append (map cdr body)) ", ")))
255            (if (string-null? entry)
256                #f
257                `(cindex (% (entry ,entry)))))))
258    (emphasis
259     *preorder*
260     . ,(lambda (tag . body)
261          (if (and (pair? body)
262                   (pair? (car body))
263                   (eq? (caar body) '@))
264              (if (assq 'role (cdar body))
265                  ;; Ignore role = annotation.
266                  ""
267                  (begin
268                    (warn "Ignoring emphasis attributes" (car body))
269                    (cons 'emph
270                          (map (lambda (x)
271                                 (pre-post-order x *gtk-doc-sdocbook->stexi-rules*))
272                               (cdr body)))))
273              (cons 'emph
274                    (map (lambda (x)
275                           (pre-post-order x *gtk-doc-sdocbook->stexi-rules*))
276                         body)))))
277    (*text*
278     . ,(lambda (tag text)
279          (or (assoc-ref '(("NULL" . (code "#f"))
280                           ("FALSE" . (code "#f"))
281                           ("TRUE" . (code "#t"))
282                           ("Returns" . "ret")) text)
283              text)))
284    ,@*sdocbook->stexi-rules*))
285
286(define *gtk-doc-sdocbook->stexi-desc-rules*
287  `((link
288     . ,(lambda (tag args body)
289          body))
290    ,@*gtk-doc-sdocbook->stexi-rules*))
291
292(define (gtk-doc-sdocbook-title sdocbook)
293  "Extract the title from a fragment of docbook, as produced by gtk-doc.
294May return @code{#f} if the title is not found."
295  (let ((l ((sxpath '(refentry refnamediv refname)) sdocbook)))
296    (if (null? l)
297        #f
298        (cdar l))))
299
300(define (gtk-doc-sdocbook-subtitle sdocbook)
301  "Extract the subtitle from a fragment of docbook, as produced by gtk-doc.
302May return @code{#f} if the subtitle is not found."
303  (let ((l ((sxpath '(refentry refnamediv refpurpose)) sdocbook)))
304    (if (null? l)
305        #f
306        (cdar l))))
307
308(define (sdocbook-description sdocbook)
309  (filter-empty-elements
310   (replace-titles
311    (sdocbook-flatten
312     (cons '*fragment*
313           (let ((fragments ((sxpath '(refentry
314                                       (refsect1 (@ role (equal? "desc")))
315                                       *))
316                             sdocbook)))
317             (if (and (pair? fragments) (pair? (car fragments))
318                      (eq? (caar fragments) 'title))
319                 ;; cdr past title... ugh.
320                 (cdr fragments)
321                 fragments)))))))
322
323(define (gtk-doc-sdocbook->description-fragment sdocbook)
324  "Extract the \"description\" of a module from a fragment of docbook,
325as produced by gtk-doc, translated into texinfo."
326  (cons '*fragment*
327        (map
328         (lambda (x)
329           (pre-post-order x *gtk-doc-sdocbook->stexi-desc-rules*))
330         (sdocbook-description sdocbook))))
331
332(define (gtk-doc->texi-stubs files)
333  "Generate a section overview texinfo file for each docbook XML file in
334@var{files}.
335
336The files will be created in the current directory, as described in the
337documentation for @code{(gnome gw support gtk-doc)}. They will include a
338file named @code{defuns-@var{file}.texi}, which should probably be
339created using @code{gtk-doc->texi-defuns}."
340  (for-each
341   (lambda (file)
342     (let* ((sdocbook (docbook->sdocbook file))
343            (basename (basename file))
344            (title (gtk-doc-sdocbook-title sdocbook))
345            (subtitle (gtk-doc-sdocbook-subtitle sdocbook))
346            (desc (gtk-doc-sdocbook->description-fragment sdocbook)))
347       (if title
348           (with-output-to-file (string-append "section-" basename ".texi")
349             (lambda ()
350               (display
351                (stexi->texi
352                 `(*fragment*
353                   (node (% (name ,@title)))
354                   (chapter ,@title)
355                   ,@(if subtitle `((para ,@subtitle)) '())
356                   (section "Overview")
357                   ,@(cdr desc)
358                   (section "Usage")
359                   (include ,(string-append "defuns-" basename ".texi"))))))))))
360   files))
361
362;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
363;; The following functions try to parse out the arguments from the
364;; preformatted function definition produced by gtk-doc. It uses
365;; heuristics, necessarily. The culmination of this effort is
366;; make-deffn-args, called in *gtk-doc-sdocbook->stexi-def-rules*.
367;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
368
369(define (string-value xml)
370  ;; inefficient, but hey!
371  (cond ((symbol? xml) "")
372        ((string? xml) xml)
373        ((pair? xml)
374         (if (eq? (car xml) '@)
375             ""
376             (apply string-append (map string-value xml))))))
377
378(define (parse-deffn-args elt)
379  (define (trim-const type)
380    (match-bind "^(const )?(.*)$" type (_ const rest)
381                rest))
382  (define (parse-arg arg args)
383    ;; ignores const.
384    (match-bind "^(.*) (\\**)([^ ]+)$" arg (_ type stars name)
385                (acons (string-append (trim-const
386                                       (string-trim-both type))
387                                      stars)
388                       name
389                       args)
390                (cond ((string=? "void" arg) args)
391                      ((string=? "void" arg) args)
392                      (else (error "could not parse" arg)))))
393  (let ((flat (string-value elt)))
394    (match-bind "^(.*?) ([^ ]+) +\\((.*)\\);" flat (_ ret-type name args)
395                `((data-type
396                   . ,(trim-const (string-trim-both ret-type)))
397                  (name
398                   . ,name)
399                  (arguments
400                   . ,(reverse
401                       (srfi-1:fold
402                        parse-arg
403                        '()
404                        (map string-trim-both
405                             (string-split args #\,)))))))))
406
407(define *immediate-types*
408  '("double"))
409
410(define (output-type? type)
411  (let* ((base (substring type 0 (or (string-index type #\*)
412                                    (string-length type))))
413         (nstars (string-length (substring type (string-length base)))))
414    (> nstars
415       (if (member base *immediate-types*) 0 1))))
416
417(define (strip-star type)
418  (let ((len (string-length type)))
419    (if (eqv? (string-ref type (1- len)) #\*)
420        (substring type 0 (1- len))
421        type)))
422
423(define (c-arguments->scheme-arguments args return-type)
424  (define (arg-texinfo arg)
425    `(" (" (var ,(gtype-name->scheme-name (cdr arg))) (tie)
426      (code ,(symbol->string
427              (gtype-name->class-name (strip-star (car arg))))) ")"))
428  (define (finish input-args output-args)
429    (let ((inputs (append-map arg-texinfo input-args))
430          (outputs (append-map arg-texinfo
431                               (if (string=? return-type "void")
432                                   output-args
433                                   (acons return-type "ret" output-args)))))
434      (if (null? outputs)
435          inputs
436          (append inputs '(" " (result) (tie)) outputs))))
437  (let lp ((args args) (out '()))
438    (cond
439     ((null? args)
440      (finish (reverse out) '()))
441     ((output-type? (caar args))
442      (finish (reverse out) args))
443     (else
444      (lp (cdr args) (cons (car args) out))))))
445
446(define (make-deffn-args elt)
447  (let ((args (parse-deffn-args elt)))
448    `(% (category "Function")
449        (name ,(gtype-name->scheme-name (assq-ref args 'name)))
450        (arguments ,@(c-arguments->scheme-arguments
451                      (assq-ref args 'arguments)
452                      (assq-ref args 'data-type))))))
453
454(define *gtk-doc-sdocbook->stexi-def-rules*
455  `((refsect2
456     *macro*
457     . ,(lambda (tag . body)
458          (let lp ((body body))
459            (let ((elt (car body)))
460              (if (and (pair? elt) (eq? (car elt) 'programlisting))
461                  `(deffn ,(make-deffn-args elt)
462                     ,@(filter-empty-elements
463                        (replace-titles
464                         (sdocbook-flatten
465                          (cons '*fragment* (cdr body))))))
466                  (lp (cdr body)))))))
467    (deffn
468      . ,identity)
469    (link
470     . ,(lambda (tag args body)
471          body))
472    ,@*gtk-doc-sdocbook->stexi-rules*))
473
474(define (gtk-doc-sdocbook->def-list/heuristics sdocbook process-def)
475  (reverse
476   (sdocbook-fold-defuns
477    (lambda (fragment seed)
478      (let* ((parsed (pre-post-order
479                      fragment *gtk-doc-sdocbook->stexi-def-rules*))
480             (name (string->symbol (attr-ref (cadr parsed) 'name)))
481             (def (process-def name parsed)))
482        (if def
483            (cons def seed)
484            seed)))
485    '()
486    sdocbook)))
487
488(define (input-arg-names func)
489  (map name (input-arguments func)))
490(define (input-arg-type-names func)
491  (map name (map type (map typespec (input-arguments func)))))
492(define (output-arg-names func)
493  (map name (output-arguments func)))
494(define (output-arg-type-names func)
495  (map name (map type (map typespec (output-arguments func)))))
496(define (return-type-name func)
497  (name (return-type func)))
498
499(define (make-function-hash wrapset)
500  (let ((ret (make-hash-table)))
501    (fold-functions
502     (lambda (f nil)
503       (let ((in (input-arguments f))
504             (out (output-arguments f)))
505         (hashq-set! ret (name f) f)))
506     #f
507     wrapset)
508    ret))
509
510(define (parse-func-name elt)
511  (string->symbol
512   (gtype-name->scheme-name
513    (let ((flat (string-value elt)))
514      (match-bind "^(.*?) ([^ ]+) +\\((.*)\\);"
515                  flat (_ ret-type name args)
516                  name
517                  (error "could not parse" flat))))))
518
519(define (function-stexi-arguments f)
520  (define (arg-texinfo name type)
521    `(" (" ,(symbol->string name) (tie)
522      (code ,(symbol->string type)) ")"))
523  (let ((inputs (append-map arg-texinfo (input-arg-names f)
524                            (input-arg-type-names f)))
525        (outputs (apply append-map arg-texinfo
526                        (if (eq? (return-type-name f) 'void)
527                            (list (output-arg-names f)
528                                  (output-arg-type-names f))
529                            (list (cons 'ret
530                                        (output-arg-names f))
531                                  (cons (return-type-name f)
532                                        (output-arg-type-names f)))))))
533    (if (null? outputs)
534        inputs
535        (append inputs '(" " (result) (tie)) outputs))))
536
537(define (make-defs/g-wrap elt funcs body process-def)
538  (or
539   (and=>
540    (hashq-ref funcs (parse-func-name elt))
541    (lambda (f)
542      (let ((deffn (process-def
543                    (name f)
544                    `(deffn (% (name ,(symbol->string (name f)))
545                               (category "Function")
546                               (arguments
547                                ,@(function-stexi-arguments f)))
548                       ,@(map
549                          (lambda (fragment)
550                            (pre-post-order
551                             fragment
552                             *gtk-doc-sdocbook->stexi-def-rules*))
553                          (filter-empty-elements
554                           (replace-titles
555                            (sdocbook-flatten
556                             (cons '*fragment* body))))))))
557            (generic (generic-name f)))
558        (if generic
559            `((,(car deffn)
560               ,(cadr deffn)
561               (deffnx (% (name ,(symbol->string generic))
562                          (category "Method")))
563               ,@(cddr deffn)))
564            (list deffn)))))
565   '()))
566
567(define (gtk-doc-sdocbook->def-list/g-wrap sdocbook process-def wrapset)
568  (let ((funcs (make-function-hash wrapset)))
569    (reverse
570     (sdocbook-fold-defuns
571      (lambda (fragment seed)
572        (append
573         (let lp ((body fragment))
574           (let ((elt (car body)))
575             (if (and (pair? elt) (eq? (car elt) 'programlisting))
576                 (reverse
577                  (make-defs/g-wrap elt funcs (cdr body) process-def))
578                 (lp (cdr body)))))
579         seed))
580      '()
581      sdocbook))))
582
583(define (signal-doc-name rs2)
584  (let ((full-name (cadar ((sxpath '(indexterm primary)) rs2))))
585    (substring full-name (1+ (string-index-right full-name #\:)))))
586
587(define (signal-doc-docs rs2)
588  (map
589   (lambda (fragment)
590     (pre-post-order
591      fragment
592      *gtk-doc-sdocbook->stexi-def-rules*))
593   (filter-empty-elements
594    (replace-titles
595     (append-map
596      sdocbook-flatten
597      ((sxpath '(para)) rs2))))))
598
599(define (signal-refsect2-list sdocbook)
600  ((sxpath '(refentry (refsect1 (@ role (equal? "signals"))) refsect2))
601   sdocbook))
602
603(define (signal-docs-alist sdocbook)
604  (map (lambda (rs2)
605         (cons (signal-doc-name rs2)
606               (signal-doc-docs rs2)))
607       (signal-refsect2-list sdocbook)))
608
609(define (signal-stexi-args s)
610  (define (class->texi class)
611    `(code ,(symbol->string (class-name class))))
612  (define (arg-texinfo name class)
613    `(" (" ,name (tie) ,(class->texi class) ")"))
614  (with-accessors (param-types return-type)
615    (let ((inputs (append-map arg-texinfo
616                              (map (lambda (i)
617                                     (string-append "arg" (number->string i)))
618                                   (iota (length (param-types s))))
619                              (param-types s)))
620          (outputs (let ((type (return-type s)))
621                     (if (not type)
622                         '()
623                         (list (class->texi type))))))
624      (if (null? outputs)
625          inputs
626          (append inputs '(" " (result) (tie)) outputs)))))
627
628(define (class-signal-stexi-docs class sdocbook)
629  (let ((alist (signal-docs-alist sdocbook)))
630    (map
631     (lambda (s)
632       (with-accessors (name)
633         `(defop (% (category "Signal")
634                    (name ,(name s))
635                    (class ,(symbol->string (class-name class)))
636                    (arguments ,@(signal-stexi-args s)))
637            ,@(or (assoc-ref alist (name s)) '("undocumented")))))
638     (if (is-a? class <gtype-class>)
639         (with-accessors (interface-type)
640           (filter (lambda (signal) (eq? (interface-type signal) class))
641                   (gtype-class-get-signals class)))
642         '()))))
643
644(define (superclasses class)
645  (define (list-join l infix)
646    "Infixes @var{infix} into list @var{l}."
647    (if (null? l) l
648        (let lp ((in (cdr l)) (out (list (car l))))
649          (cond ((null? in) (reverse out))
650                (else (lp (cdr in) (cons* (car in) infix out)))))))
651  (cond
652   ((is-a? class <class>)
653    `(para
654      "Derives from "
655      ,@(list-join
656         (map (lambda (namesym) `(code ,(symbol->string namesym)))
657              (map class-name (class-direct-supers class)))
658         ", ")
659      "."))
660   (else
661    '(para "Opaque pointer."))))
662
663(define (gobject-class-stexi-docs module-name class-name sdocbook)
664  (define (doc-slots class)
665    (define (doc-slot slot)
666      (let ((name (slot-definition-name slot)))
667        `(entry
668          (% (heading ,(symbol->string name)))
669          (para
670           ,(case (slot-definition-allocation slot)
671              ((#:gproperty #:gparam)
672               (with-accessors (blurb)
673                 (blurb (gobject-class-find-property class name))))
674              (else
675               "Scheme slot."))))))
676    (let ((slots (if (is-a? class <class>)
677                     (class-direct-slots class)
678                     '()))) ;; silliness regardings wcts...
679      (cond
680       ((null? slots)
681        '((para "This class defines no direct slots.")))
682       (else
683        `((para "This class defines the following slots:")
684          (table (% (formatter (code)))
685                 ,@(map doc-slot slots)))))))
686  (let ((v (module-variable (resolve-interface module-name) class-name)))
687    (cond
688     (v
689      `((deftp (% (name ,(symbol->string class-name))
690                  (category "Class"))
691          ,(superclasses (variable-ref v))
692          ,@(doc-slots (variable-ref v)))
693        ,@(class-signal-stexi-docs (variable-ref v) sdocbook)))
694     (else
695      '()))))
696
697(define (make-type-docs? class-name wrapset)
698  ;; does the type (1) exist and (2) define an export?
699  (fold-types (lambda (x y)
700                (or y (and (eq? (name x) class-name)
701                           (or (not (slot-exists? x 'define-class?))
702                               (slot-ref x 'define-class?)))))
703              #f wrapset))
704
705(define (gtk-doc-sdocbook->class-list/g-wrap sdocbook process-def wrapset)
706  (reverse
707   (sdocbook-fold-structs
708    (lambda (cname seed)
709      (let ((class-name (gtype-name->class-name cname)))
710        (cond
711         ((make-type-docs? class-name wrapset)
712          (append (reverse
713                   (gobject-class-stexi-docs (module wrapset) class-name
714                                             sdocbook))
715                  seed))
716         (else
717          seed))))
718    '()
719    sdocbook)))
720
721(define (gtk-doc->texi-defuns/g-wrap sdocbook defs-alist module-name
722                                     wrapset-name)
723  ;; load up the wrapset definition from the module
724  (resolve-interface module-name)
725  (let ((wrapset (get-wrapset 'guile wrapset-name)))
726    (define (munge-def name def)
727      (or (and=> (assq name defs-alist) cdr) def))
728    (append
729     (gtk-doc-sdocbook->class-list/g-wrap sdocbook munge-def wrapset)
730     (gtk-doc-sdocbook->def-list/g-wrap sdocbook munge-def wrapset))))
731
732(define (gtk-doc->texi-defuns/heuristics sdocbook defs-alist module-name)
733  (let ((interface (resolve-interface module-name)))
734    (define (munge-def name def)
735      (and (module-variable interface name)
736           (or (and=> (assq name defs-alist) cdr) def)))
737    (gtk-doc-sdocbook->def-list/heuristics sdocbook munge-def)))
738
739(define *gtk-doc->texi-defuns-methods*
740  `((g-wrap . ,gtk-doc->texi-defuns/g-wrap)
741    (heuristics . ,gtk-doc->texi-defuns/heuristics)))
742
743(define (def-name def)
744  (string->symbol (cadr (assq 'name (cdadr def)))))
745
746(define (gtk-doc->texi-defuns overrides method args . files)
747  "Generate documentation for the types and functions defined in a set
748of docbook files genearted by GTK-Doc.
749
750@var{overrides} should be a path to a texinfo file from which
751@code{@@deffn} overrides will be taken. @var{method} should be either
752@code{g-wrap} or @code{heuristics}, as discussed in the @code{(gnome gw
753support gtk-doc)} documentation. @var{files} is the list of docbook XML
754files from which to pull function documentation.
755
756@var{args} should be a list, whose form depends on the @var{method}. For
757@code{g-wrap}, it should be two elements, the first the name of a module
758that, when loaded, will load the necessary wrapset into the g-wrap
759runtime. For example, @code{(gnome gw glib-spec)}. The second argument
760should be the name of the wrapset, e.g. @code{gnome-glib}.
761
762If @var{method} is @code{heuristics}, @var{args} should have only one
763element, the name of the module to load to check the existence of
764procedures, e.g. @code{(cairo)}."
765  (let* ((defs ((sxpath '(deffn))
766                (call-with-input-file overrides texi-fragment->stexi)))
767         (defs-alist (map cons (map def-name defs) defs))
768         (make-defuns (or (assq-ref *gtk-doc->texi-defuns-methods* method)
769                          (error "unknown method" method))))
770    (for-each
771     (lambda (file)
772       (let* ((sdocbook (docbook->sdocbook file))
773              (basename (basename file))
774              (docs (stexi->texi
775                     `(*fragment*
776                       ,@(apply make-defuns sdocbook defs-alist args)))))
777         (with-output-to-file (string-append "defuns-" basename ".texi")
778           (lambda ()
779             (display docs)))))
780     files)))
781
782(define (symbolcomp pred)
783  (lambda (a b)
784    (pred (symbol->string a) (symbol->string b))))
785
786(define symbol<?
787  (symbolcomp string<?))
788
789(define (extract-defs stexi)
790  (let ((commands (srfi-1:fold
791                   (lambda (def rest)
792                     (if (string-prefix? "def" (symbol->string (car def)))
793                         (cons (car def) rest)
794                         rest))
795                        '() texi-command-specs)))
796    (srfi-1:fold
797     (lambda (x rest)
798       (if (and (pair? x) (memq (car x) commands))
799           (cons x rest)
800           rest))
801     '() stexi)))
802
803(define (check-documentation-coverage modules texi)
804  "Check the coverage of generated documentation.
805
806@var{modules} is a list of module names, and @var{texi} is a path to
807a texinfo file. The set of exports of @var{modules} is checked against
808the set of procedures defined in @var{texi}, resulting in a calculation
809of documentation coverage, and the output of any missing documentation
810to the current output port."
811  (let* ((defs (extract-defs
812                (call-with-input-file texi texi->stexi)))
813         (def-names (map def-name defs))
814         (exports (append-map
815                   (lambda (mod)
816                     (or
817                      (false-if-exception
818                       (module-map (lambda (k v) k)
819                                   (resolve-interface mod)))
820                      (begin (warn "Module does not exist:" mod) '())))
821                   modules))
822         (undocumented (lset-difference eq? exports def-names))
823         (spurious (lset-difference eq? def-names exports)))
824    (format #t "~A symbols exported\n" (length exports))
825    (format #t "~A symbols documented\n" (length def-names))
826    (format #t "~A symbols undocumented\n" (length undocumented))
827    (for-each
828     (lambda (sym)
829       (format #t "  ~A\n" sym))
830     (sort undocumented symbol<?))
831    (format #t "~A symbols spuriously documented\n" (length spurious))
832    (for-each
833     (lambda (sym)
834       (format #t "  ~A\n" sym))
835     (sort spurious symbol<?))))
836
837(define (generate-undocumented-texi modules texi)
838  "Verify the bindings exported by @var{modules} against the
839documentation in @var{texi}, writing documentation for any undocumented
840symbol to @code{undocumented.texi}.
841
842@var{modules} is a list of module names, and @var{texi} is a path to a
843texinfo file."
844  (let* ((defs (extract-defs
845                (call-with-input-file texi texi->stexi)))
846         (def-names (map def-name defs)))
847    (define (module-undocumented mod)
848      (sort!
849       (lset-difference eq?
850                        (module-map (lambda (k v) k)
851                                    (resolve-interface mod))
852                        def-names)
853       symbol<?))
854    (display
855     (stexi->texi
856      `(*fragment*
857        (node (% (name "Undocumented")))
858        (chapter "Undocumented")
859        (para "The following symbols, if any, have not been properly "
860              "documented.")
861        ,@(append-map
862           (lambda (mod)
863             (let ((undocumented (module-undocumented mod)))
864               (cond
865                ((null? undocumented) '())
866                (else
867                 `((section ,(with-output-to-string
868                               (lambda () (write mod))))
869                   ,@(append-map
870                      (lambda (sym)
871                        (let ((odoc (object-stexi-documentation
872                                     (module-ref (resolve-interface mod) sym)
873                                     sym
874                                     #:force #t)))
875                          (if (eq? (car odoc) '*fragment*)
876                              (cdr odoc)
877                              (list odoc))))
878                      undocumented))))))
879           modules)))
880     (open-output-file "undocumented.texi"))))
881