1 ;;;; (texinfo reflection) -- documenting Scheme as stexinfo
3 ;;;; Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
4 ;;;; Copyright (C) 2003,2004,2009 Andy Wingo <wingo at pobox dot com>
6 ;;;; This library is free software; you can redistribute it and/or
7 ;;;; modify it under the terms of the GNU Lesser General Public
8 ;;;; License as published by the Free Software Foundation; either
9 ;;;; version 3 of the License, or (at your option) any later version.
11 ;;;; This library is distributed in the hope that it will be useful,
12 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 ;;;; Lesser General Public License for more details.
16 ;;;; You should have received a copy of the GNU Lesser General Public
17 ;;;; License along with this library; if not, write to the Free Software
18 ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 ;;Routines to generare @code{stexi} documentation for objects and
26 ;;Note that in this context, an @dfn{object} is just a value associated
27 ;;with a location. It has nothing to do with GOOPS.
31 (define-module (texinfo reflection)
32 #:use-module ((srfi srfi-1) #:select (append-map))
33 #:use-module (oop goops)
34 #:use-module (texinfo)
35 #:use-module (texinfo plain-text)
36 #:use-module (srfi srfi-13)
37 #:use-module (ice-9 session)
38 #:use-module (ice-9 documentation)
39 #:use-module (ice-9 optargs)
40 #:use-module ((sxml transform) #:select (pre-post-order))
41 #:export (module-stexi-documentation
42 script-stexi-documentation
43 object-stexi-documentation
44 package-stexi-standard-copying
45 package-stexi-standard-titlepage
46 package-stexi-generic-menu
47 package-stexi-standard-menu
48 package-stexi-extended-menu
49 package-stexi-standard-prologue
50 package-stexi-documentation
51 package-stexi-documentation-for-include))
53 ;; List for sorting the definitions in a module
55 '(deftp defcv defivar deftypeivar defop deftypeop defmethod
56 deftypemethod defopt defvr defvar deftypevr deftypevar deffn
57 deftypefn defmac defspec defun deftypefun))
59 (define (sort-defs ordering a b)
61 ;; a and b are lists of the form ((anchor ...) (def* ...)...)
64 (cadr (assq 'name (cdadr (def x)))))
66 (list-index defs (car (def x))))
68 (or (list-index ordering (string->symbol (name x)))
69 ;; if the def is not in the list, a big number
71 (define (compare-in-order proc eq? < . args)
72 (if (not (eq? (proc a) (proc b)))
75 (apply compare-in-order args))))
76 (compare-in-order order = <
78 name string=? string<=?))
80 (define (list*-join l infix restfix)
81 (let lp ((in l) (out '()))
82 (cond ((null? in) (reverse! out))
83 ((symbol? in) (reverse! (cons* in restfix out)))
84 (else (lp (cdr in) (if (null? out)
86 (cons* (car in) infix out)))))))
88 (define (process-args args)
89 (map (lambda (x) (if (string? x) x (object->string x)))
90 (list*-join (or args '())
93 (define (get-proc-args proc)
95 ((procedure-arguments proc)
97 (let ((required-args (assq-ref args 'required))
98 (optional-args (assq-ref args 'optional))
99 (keyword-args (assq-ref args 'keyword))
100 (rest-arg (assq-ref args 'rest)))
103 ;; start with the required args...
104 (map symbol->string required-args)
106 ;; add any optional args if needed...
109 (format #f "[~a = ~s]" (car a) (cadr a))
110 (format #f "[~a]" a)))
113 ;; now the keyword args..
116 (format #f "[~a]" (car a))
117 (format #f "[#:~a]" a)))
120 ;; now the rest arg...
122 (list "." (symbol->string rest-arg))
125 (define (macro-arguments name type transformer)
129 (let ((patterns (procedure-property transformer 'patterns)))
136 (or (procedure-property transformer 'defmacro-args)
139 ;; a procedural (syntax-case) macro. how to document these?
142 (define (macro-additional-stexi name type transformer)
145 (let ((patterns (procedure-property transformer 'patterns)))
148 `(defspecx (% (name ,name)
149 (arguments ,@(process-args x)))))
155 (define many-space? (make-regexp "[[:space:]][[:space:]][[:space:]]"))
156 (define initial-space? (make-regexp "^[[:space:]]"))
157 (define (string->stexi str)
158 (or (and (or (not str) (string-null? str))
160 (and (or (string-index str #\@)
161 (and (not (regexp-exec many-space? str))
162 (not (regexp-exec initial-space? str))))
164 (texi-fragment->stexi str)))
165 `(*fragment* (verbatim ,str))))
167 (define method-formals
168 (and (defined? 'method-formals) method-formals))
170 (define (method-stexi-arguments method)
173 (let lp ((formals (method-formals method))
174 (specializers (method-specializers method))
176 (define (arg-texinfo formal specializer)
177 `(" (" (var ,(symbol->string formal)) " "
178 (code ,(symbol->string (class-name specializer))) ")"))
180 ((null? formals) (reverse out))
182 (lp (cdr formals) (cdr specializers)
183 (append (reverse (arg-texinfo (car formals) (car specializers)))
186 (append (reverse out) (arg-texinfo formals specializers)
188 ((method-source method)
189 (let lp ((bindings (cadr (method-source method))) (out '()))
190 (define (arg-texinfo arg)
191 `(" (" (var ,(symbol->string (car arg))) " "
192 (code ,(symbol->string (cadr arg))) ")"))
196 ((not (pair? (car bindings)))
197 (append (reverse out) (arg-texinfo bindings) (list "...")))
200 (append (reverse (arg-texinfo (car bindings))) out))))))
201 (else (warn method) '())))
203 (define* (object-stexi-documentation object #:optional (name "[unknown]")
206 (set! name (symbol->string name)))
207 (let ((stexi ((lambda (x)
208 (cond ((string? x) (string->stexi x))
209 ((and (pair? x) (eq? (car x) '*fragment*)) x)
210 (force `(*fragment*))
212 (object-documentation
213 (if (is-a? object <method>)
214 (method-procedure object)
216 (define (make-def type args)
217 `(,type (% ,@args) ,@(cdr stexi)))
220 ;; stexi is now a list, headed by *fragment*.
221 ((and (pair? (cdr stexi)) (pair? (cadr stexi))
222 (memq (caadr stexi) defs))
223 ;; it's already a deffoo.
225 ((is-a? object <class>)
226 (make-def 'deftp `((name ,name)
227 (category "Class"))))
228 ((is-a? object <macro>)
229 (let* ((proc (macro-transformer object))
230 (type (and proc (procedure-property proc 'macro-type))))
231 `(defspec (% (name ,name)
232 (arguments ,@(macro-arguments name type proc)))
233 ,@(macro-additional-stexi name type proc)
236 ((is-a? object <procedure>)
237 (make-def 'defun `((name ,name)
238 (arguments ,@(get-proc-args object)))))
239 ((is-a? object <method>)
240 (make-def 'deffn `((category "Method")
242 (arguments ,@(method-stexi-arguments object)))))
243 ((is-a? object <generic>)
245 ,(make-def 'deffn `((name ,name)
246 (category "Generic")))
249 (object-stexi-documentation method name #:force force))
250 (generic-function-methods object))))
252 (make-def 'defvar `((name ,name)))))))
254 (define (module-name->node-name sym-name)
255 (string-join (map symbol->string sym-name) " "))
257 ;; this copied from (ice-9 session); need to find a better way
258 (define (module-filename name)
259 (let* ((name (map symbol->string name))
260 (reverse-name (reverse name))
261 (leaf (car reverse-name))
262 (dir-hint-module-name (reverse (cdr reverse-name)))
263 (dir-hint (apply string-append
265 (string-append elt "/"))
266 dir-hint-module-name))))
267 (%search-load-path (in-vicinity dir-hint leaf))))
269 (define (read-module name)
270 (let ((filename (module-filename name)))
272 (let ((port (open-input-file filename)))
273 (let lp ((out '()) (form (read port)))
274 (if (eof-object? form)
276 (lp (cons form out) (read port)))))
279 (define (module-export-list sym-name)
280 (define (module-form-export-list form)
282 (eq? (car form) 'define-module)
283 (equal? (cadr form) sym-name)
284 (and=> (memq #:export (cddr form)) cadr)))
285 (let lp ((forms (read-module sym-name)))
286 (cond ((null? forms) '())
287 ((module-form-export-list (car forms)) => identity)
288 (else (lp (cdr forms))))))
290 (define* (module-stexi-documentation sym-name
291 #:optional %docs-resolver
294 (lambda (name def) def))))
295 "Return documentation for the module named @var{sym-name}. The
296 documentation will be formatted as @code{stexi}
297 (@pxref{texinfo,texinfo})."
299 (issue-deprecation-warning
300 "module-stexi-documentation: use #:docs-resolver instead of a positional argument."))
301 (let* ((commentary (and=> (module-commentary sym-name)
302 (lambda (x) (string-trim-both x #\newline))))
303 (stexi (string->stexi commentary))
304 (node-name (module-name->node-name sym-name))
305 (name-str (with-output-to-string
306 (lambda () (display sym-name))))
307 (module (resolve-interface sym-name))
308 (export-list (module-export-list sym-name)))
309 (define (anchor-name sym)
310 (string-append node-name " " (symbol->string sym)))
315 `((anchor (% (name ,(anchor-name sym))))
317 (if (eq? (car x) '*fragment*)
320 (if (variable-bound? var)
323 (object-stexi-documentation (variable-ref var) sym
326 (warn "variable unbound!" sym)
327 `(defvar (% (name ,(symbol->string sym)))
330 (lambda (a b) (sort-defs export-list a b))))
332 `(texinfo (% (title ,name-str))
333 (node (% (name ,node-name)))
337 ,@(apply append! (make-defs)))))
339 (define (script-stexi-documentation scriptpath)
340 "Return documentation for given script. The documentation will be
341 taken from the script's commentary, and will be returned in the
342 @code{stexi} format (@pxref{texinfo,texinfo})."
343 (let ((commentary (file-commentary scriptpath)))
344 `(texinfo (% (title ,(basename scriptpath)))
345 (node (% (name ,(basename scriptpath))))
349 (string-trim-both commentary #\newline)))
353 ((defined? 'add-value-help-handler!)
354 (add-value-help-handler!
357 (object-stexi-documentation value name #:force #t))))
358 (add-name-help-handler!
361 (and-map symbol? name)
362 (stexi->plain-text (module-stexi-documentation name)))))))
364 ;; we could be dealing with an old (ice-9 session); fondle it to get
366 (define module-commentary (@@ (ice-9 session) module-commentary))
368 (define (package-stexi-standard-copying name version updated years
369 copyright-holder permissions)
370 "Create a standard texinfo @code{copying} section.
372 @var{years} is a list of years (as integers) in which the modules
373 being documented were released. All other arguments are strings."
375 (para "This manual is for " ,name
376 " (version " ,version ", updated " ,updated ")")
377 (para "Copyright " ,(string-join (map number->string years) ",")
378 " " ,copyright-holder)
380 (para ,permissions))))
382 (define (package-stexi-standard-titlepage name version updated authors)
383 "Create a standard GNU title page.
385 @var{authors} is a list of @code{(@var{name} . @var{email})}
386 pairs. All other arguments are strings.
388 Here is an example of the usage of this procedure:
391 (package-stexi-standard-titlepage
394 \"26 September 2006\"
395 '((\"Alyssa P Hacker\" . \"alyssa@@example.com\"))
397 \"Free Software Foundation, Inc.\"
398 \"Standard GPL permissions blurb goes here\")
401 `(;(setchapternewpage (% (all "odd"))) makes manuals too long
404 (subtitle "version " ,version ", updated " ,updated)
405 ,@(map (lambda (pair)
407 " (" (email ,(cdr pair)) ")"))
410 (vskip (% (all "0pt plus 1filll")))
413 (define (package-stexi-generic-menu name entries)
414 "Create a menu from a generic alist of entries, the car of which
415 should be the node name, and the cdr the description. As an exception,
416 an entry of @code{#f} will produce a separator."
417 (define (make-entry node description)
419 ,(make-string (max (- 21 (string-length node)) 2) #\space)
422 (node (% (name "Top")))
423 (top (% (title ,name)))
431 (make-entry (car entry) (cdr entry))
438 (define (package-stexi-standard-menu name modules module-descriptions
440 "Create a standard top node and menu, suitable for processing
442 (package-stexi-generic-menu
444 (let ((module-entries (map cons
445 (map module-name->node-name modules)
446 module-descriptions))
447 (separate-sections (lambda (x) (if (null? x) x (cons #f x)))))
449 ,@(separate-sections extra-entries)))))
451 (define (package-stexi-extended-menu name module-pairs script-pairs
453 "Create an \"extended\" menu, like the standard menu but with a
454 section for scripts."
455 (package-stexi-generic-menu
457 (let ((module-entries (map cons
458 (map module-name->node-name
459 (map car module-pairs))
460 (map cdr module-pairs)))
461 (script-entries (map cons
462 (map basename (map car script-pairs))
463 (map cdr script-pairs)))
464 (separate-sections (lambda (x) (if (null? x) x (cons #f x)))))
466 ,@(separate-sections script-entries)
467 ,@(separate-sections extra-entries)))))
469 (define (package-stexi-standard-prologue name filename category
470 description copying titlepage
472 "Create a standard prologue, suitable for later serialization
473 to texinfo and .info creation with makeinfo.
475 Returns a list of stexinfo forms suitable for passing to
476 @code{package-stexi-documentation} as the prologue. @xref{texinfo
477 reflection package-stexi-documentation}, @ref{texinfo reflection
478 package-stexi-standard-titlepage,package-stexi-standard-titlepage},
479 @ref{texinfo reflection
480 package-stexi-standard-copying,package-stexi-standard-copying},
481 and @ref{texinfo reflection
482 package-stexi-standard-menu,package-stexi-standard-menu}."
484 (dircategory (% (category ,category)))
486 "* " ,name ": (" ,filename "). " ,description ".")
490 (define (stexi->chapter stexi)
493 `((texinfo . ,(lambda (tag attrs node . body)
495 (chapter ,@(assq-ref (cdr attrs) 'title))
497 (*text* . ,(lambda (tag text) text))
498 (*default* . ,(lambda args args)))))
500 (define* (package-stexi-documentation modules name filename
503 (module-stexi-documentation-args
506 "Create stexi documentation for a @dfn{package}, where a
507 package is a set of modules that is released together.
509 @var{modules} is expected to be a list of module names, where a
510 module name is a list of symbols. The stexi that is returned will
511 be titled @var{name} and a texinfo filename of @var{filename}.
513 @var{prologue} and @var{epilogue} are lists of stexi forms that
514 will be spliced into the output document before and after the
515 generated modules documentation, respectively.
516 @xref{texinfo reflection package-stexi-standard-prologue}, to
517 create a conventional GNU texinfo prologue.
519 @var{module-stexi-documentation-args} is an optional argument that, if
520 given, will be added to the argument list when
521 @code{module-texi-documentation} is called. For example, it might be
522 useful to define a @code{#:docs-resolver} argument."
523 (define (verify-modules-list l)
526 (or (null? (cdr l)) (all pred (cdr l)))))
528 (all (lambda (x) (all symbol? x)) modules)))
529 (if (not (verify-modules-list modules))
530 (error "expected modules to be a list of a list of symbols"
535 (filename ,filename))
537 ,@(append-map (lambda (mod)
539 (apply module-stexi-documentation
540 mod module-stexi-documentation-args)))
542 ,@(append-map (lambda (script)
544 (script-stexi-documentation script)))
548 (define* (package-stexi-documentation-for-include modules module-descriptions
550 (module-stexi-documentation-args '()))
551 "Create stexi documentation for a @dfn{package}, where a
552 package is a set of modules that is released together.
554 @var{modules} is expected to be a list of module names, where a
555 module name is a list of symbols. Returns an stexinfo fragment.
557 Unlike @code{package-stexi-documentation}, this function simply produces
558 a menu and the module documentations instead of producing a full texinfo
559 document. This can be useful if you write part of your manual by hand,
560 and just use @code{@@include} to pull in the automatically generated
563 @var{module-stexi-documentation-args} is an optional argument that, if
564 given, will be added to the argument list when
565 @code{module-texi-documentation} is called. For example, it might be
566 useful to define a @code{#:docs-resolver} argument."
567 (define (make-entry node description)
569 ,(make-string (max (- 21 (string-length node)) 2) #\space)
573 ,@(append-map (lambda (modname desc)
574 (make-entry (module-name->node-name modname)
577 module-descriptions))
578 ,@(append-map (lambda (modname)
580 (apply module-stexi-documentation
582 module-stexi-documentation-args)))
585 ;;; arch-tag: bbe2bc03-e16d-4a9e-87b9-55225dc9836c