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