123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- ;;;; (texinfo reflection) -- documenting Scheme as stexinfo
- ;;;;
- ;;;; Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
- ;;;; Copyright (C) 2003,2004,2009 Andy Wingo <wingo at pobox dot com>
- ;;;;
- ;;;; This library is free software; you can redistribute it and/or
- ;;;; modify it under the terms of the GNU Lesser General Public
- ;;;; License as published by the Free Software Foundation; either
- ;;;; version 3 of the License, or (at your option) any later version.
- ;;;;
- ;;;; This library is distributed in the hope that it will be useful,
- ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ;;;; Lesser General Public License for more details.
- ;;;;
- ;;;; You should have received a copy of the GNU Lesser General Public
- ;;;; License along with this library; if not, write to the Free Software
- ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- ;;;;
- ;;; Commentary:
- ;;
- ;;Routines to generare @code{stexi} documentation for objects and
- ;;modules.
- ;;
- ;;Note that in this context, an @dfn{object} is just a value associated
- ;;with a location. It has nothing to do with GOOPS.
- ;;
- ;;; Code:
- (define-module (texinfo reflection)
- #:use-module ((srfi srfi-1) #:select (append-map))
- #:use-module (oop goops)
- #:use-module (texinfo)
- #:use-module (texinfo plain-text)
- #:use-module (srfi srfi-13)
- #:use-module (ice-9 session)
- #:use-module (ice-9 documentation)
- #:use-module (ice-9 optargs)
- #:use-module ((sxml transform) #:select (pre-post-order))
- #:export (module-stexi-documentation
- script-stexi-documentation
- object-stexi-documentation
- package-stexi-standard-copying
- package-stexi-standard-titlepage
- package-stexi-generic-menu
- package-stexi-standard-menu
- package-stexi-extended-menu
- package-stexi-standard-prologue
- package-stexi-documentation
- package-stexi-documentation-for-include))
- ;; List for sorting the definitions in a module
- (define defs
- '(deftp defcv defivar deftypeivar defop deftypeop defmethod
- deftypemethod defopt defvr defvar deftypevr deftypevar deffn
- deftypefn defmac defspec defun deftypefun))
- (define (sort-defs ordering a b)
- (define (def x)
- ;; a and b are lists of the form ((anchor ...) (def* ...)...)
- (cadr x))
- (define (name x)
- (cadr (assq 'name (cdadr (def x)))))
- (define (priority x)
- (list-index defs (car (def x))))
- (define (order x)
- (or (list-index ordering (string->symbol (name x)))
- ;; if the def is not in the list, a big number
- 1234567890))
- (define (compare-in-order proc eq? < . args)
- (if (not (eq? (proc a) (proc b)))
- (< (proc a) (proc b))
- (or (null? args)
- (apply compare-in-order args))))
- (compare-in-order order = <
- priority = <
- name string=? string<=?))
- (define (list*-join l infix restfix)
- (let lp ((in l) (out '()))
- (cond ((null? in) (reverse! out))
- ((symbol? in) (reverse! (cons* in restfix out)))
- (else (lp (cdr in) (if (null? out)
- (list (car in))
- (cons* (car in) infix out)))))))
- (define (process-args args)
- (map (lambda (x) (if (string? x) x (object->string x)))
- (list*-join (or args '())
- " " " . ")))
- (define (get-proc-args proc)
- (cond
- ((procedure-arguments proc)
- => (lambda (args)
- (let ((required-args (assq-ref args 'required))
- (optional-args (assq-ref args 'optional))
- (keyword-args (assq-ref args 'keyword))
- (rest-arg (assq-ref args 'rest)))
- (process-args
- (append
- ;; start with the required args...
- (map symbol->string required-args)
- ;; add any optional args if needed...
- (map (lambda (a)
- (if (list? a)
- (format #f "[~a = ~s]" (car a) (cadr a))
- (format #f "[~a]" a)))
- optional-args)
-
- ;; now the keyword args..
- (map (lambda (a)
- (if (pair? a)
- (format #f "[~a]" (car a))
- (format #f "[#:~a]" a)))
- keyword-args)
-
- ;; now the rest arg...
- (if rest-arg
- (list "." (symbol->string rest-arg))
- '()))))))))
- (define (macro-arguments name type transformer)
- (process-args
- (case type
- ((syntax-rules)
- (let ((patterns (procedure-property transformer 'patterns)))
- (if (pair? patterns)
- (car patterns)
- '())))
- ((identifier-syntax)
- '())
- ((defmacro)
- (or (procedure-property transformer 'defmacro-args)
- '()))
- (else
- ;; a procedural (syntax-case) macro. how to document these?
- '()))))
- (define (macro-additional-stexi name type transformer)
- (case type
- ((syntax-rules)
- (let ((patterns (procedure-property transformer 'patterns)))
- (if (pair? patterns)
- (map (lambda (x)
- `(defspecx (% (name ,name)
- (arguments ,@(process-args x)))))
- (cdr patterns))
- '())))
- (else
- '())))
- (define many-space? (make-regexp "[[:space:]][[:space:]][[:space:]]"))
- (define initial-space? (make-regexp "^[[:space:]]"))
- (define (string->stexi str)
- (or (and (or (not str) (string-null? str))
- '(*fragment*))
- (and (or (string-index str #\@)
- (and (not (regexp-exec many-space? str))
- (not (regexp-exec initial-space? str))))
- (false-if-exception
- (texi-fragment->stexi str)))
- `(*fragment* (verbatim ,str))))
- (define method-formals
- (and (defined? 'method-formals) method-formals))
- (define (method-stexi-arguments method)
- (cond
- (method-formals
- (let lp ((formals (method-formals method))
- (specializers (method-specializers method))
- (out '()))
- (define (arg-texinfo formal specializer)
- `(" (" (var ,(symbol->string formal)) " "
- (code ,(symbol->string (class-name specializer))) ")"))
- (cond
- ((null? formals) (reverse out))
- ((pair? formals)
- (lp (cdr formals) (cdr specializers)
- (append (reverse (arg-texinfo (car formals) (car specializers)))
- out)))
- (else
- (append (reverse out) (arg-texinfo formals specializers)
- (list "..."))))))
- ((method-source method)
- (let lp ((bindings (cadr (method-source method))) (out '()))
- (define (arg-texinfo arg)
- `(" (" (var ,(symbol->string (car arg))) " "
- (code ,(symbol->string (cadr arg))) ")"))
- (cond
- ((null? bindings)
- (reverse out))
- ((not (pair? (car bindings)))
- (append (reverse out) (arg-texinfo bindings) (list "...")))
- (else
- (lp (cdr bindings)
- (append (reverse (arg-texinfo (car bindings))) out))))))
- (else (warn method) '())))
- (define* (object-stexi-documentation object #:optional (name "[unknown]")
- #:key (force #f))
- (if (symbol? name)
- (set! name (symbol->string name)))
- (let ((stexi ((lambda (x)
- (cond ((string? x) (string->stexi x))
- ((and (pair? x) (eq? (car x) '*fragment*)) x)
- (force `(*fragment*))
- (else #f)))
- (object-documentation
- (if (is-a? object <method>)
- (method-procedure object)
- object)))))
- (define (make-def type args)
- `(,type (% ,@args) ,@(cdr stexi)))
- (cond
- ((not stexi) #f)
- ;; stexi is now a list, headed by *fragment*.
- ((and (pair? (cdr stexi)) (pair? (cadr stexi))
- (memq (caadr stexi) defs))
- ;; it's already a deffoo.
- stexi)
- ((is-a? object <class>)
- (make-def 'deftp `((name ,name)
- (category "Class"))))
- ((is-a? object <macro>)
- (let* ((proc (macro-transformer object))
- (type (and proc (procedure-property proc 'macro-type))))
- `(defspec (% (name ,name)
- (arguments ,@(macro-arguments name type proc)))
- ,@(macro-additional-stexi name type proc)
- ,@(cdr stexi))))
-
- ((is-a? object <procedure>)
- (make-def 'defun `((name ,name)
- (arguments ,@(get-proc-args object)))))
- ((is-a? object <method>)
- (make-def 'deffn `((category "Method")
- (name ,name)
- (arguments ,@(method-stexi-arguments object)))))
- ((is-a? object <generic>)
- `(*fragment*
- ,(make-def 'deffn `((name ,name)
- (category "Generic")))
- ,@(map
- (lambda (method)
- (object-stexi-documentation method name #:force force))
- (generic-function-methods object))))
- (else
- (make-def 'defvar `((name ,name)))))))
- (define (module-name->node-name sym-name)
- (string-join (map symbol->string sym-name) " "))
- ;; this copied from (ice-9 session); need to find a better way
- (define (module-filename name)
- (let* ((name (map symbol->string name))
- (reverse-name (reverse name))
- (leaf (car reverse-name))
- (dir-hint-module-name (reverse (cdr reverse-name)))
- (dir-hint (apply string-append
- (map (lambda (elt)
- (string-append elt "/"))
- dir-hint-module-name))))
- (%search-load-path (in-vicinity dir-hint leaf))))
- (define (read-module name)
- (let ((filename (module-filename name)))
- (if filename
- (let ((port (open-input-file filename)))
- (let lp ((out '()) (form (read port)))
- (if (eof-object? form)
- (reverse out)
- (lp (cons form out) (read port)))))
- '())))
- (define (module-export-list sym-name)
- (define (module-form-export-list form)
- (and (pair? form)
- (eq? (car form) 'define-module)
- (equal? (cadr form) sym-name)
- (and=> (memq #:export (cddr form)) cadr)))
- (let lp ((forms (read-module sym-name)))
- (cond ((null? forms) '())
- ((module-form-export-list (car forms)) => identity)
- (else (lp (cdr forms))))))
- (define* (module-stexi-documentation sym-name
- #:key (docs-resolver
- (lambda (name def) def)))
- "Return documentation for the module named @var{sym-name}. The
- documentation will be formatted as @code{stexi}
- (@pxref{texinfo,texinfo})."
- (let* ((commentary (and=> (module-commentary sym-name)
- (lambda (x) (string-trim-both x #\newline))))
- (stexi (string->stexi commentary))
- (node-name (module-name->node-name sym-name))
- (name-str (with-output-to-string
- (lambda () (display sym-name))))
- (module (resolve-interface sym-name))
- (export-list (module-export-list sym-name)))
- (define (anchor-name sym)
- (string-append node-name " " (symbol->string sym)))
- (define (make-defs)
- (sort!
- (module-map
- (lambda (sym var)
- `((anchor (% (name ,(anchor-name sym))))
- ,@((lambda (x)
- (if (eq? (car x) '*fragment*)
- (cdr x)
- (list x)))
- (if (variable-bound? var)
- (docs-resolver
- sym
- (object-stexi-documentation (variable-ref var) sym
- #:force #t))
- (begin
- (warn "variable unbound!" sym)
- `(defvar (% (name ,(symbol->string sym)))
- "[unbound!]"))))))
- module)
- (lambda (a b) (sort-defs export-list a b))))
- `(texinfo (% (title ,name-str))
- (node (% (name ,node-name)))
- (section "Overview")
- ,@(cdr stexi)
- (section "Usage")
- ,@(apply append! (make-defs)))))
- (define (script-stexi-documentation scriptpath)
- "Return documentation for given script. The documentation will be
- taken from the script's commentary, and will be returned in the
- @code{stexi} format (@pxref{texinfo,texinfo})."
- (let ((commentary (file-commentary scriptpath)))
- `(texinfo (% (title ,(basename scriptpath)))
- (node (% (name ,(basename scriptpath))))
- ,@(if commentary
- (cdr
- (string->stexi
- (string-trim-both commentary #\newline)))
- '()))))
- (cond
- ((defined? 'add-value-help-handler!)
- (add-value-help-handler!
- (lambda (name value)
- (stexi->plain-text
- (object-stexi-documentation value name #:force #t))))
- (add-name-help-handler!
- (lambda (name)
- (and (list? name)
- (and-map symbol? name)
- (stexi->plain-text (module-stexi-documentation name)))))))
- ;; we could be dealing with an old (ice-9 session); fondle it to get
- ;; module-commentary
- (define module-commentary (@@ (ice-9 session) module-commentary))
- (define (package-stexi-standard-copying name version updated years
- copyright-holder permissions)
- "Create a standard texinfo @code{copying} section.
- @var{years} is a list of years (as integers) in which the modules
- being documented were released. All other arguments are strings."
- `(copying
- (para "This manual is for " ,name
- " (version " ,version ", updated " ,updated ")")
- (para "Copyright " ,(string-join (map number->string years) ",")
- " " ,copyright-holder)
- (quotation
- (para ,permissions))))
- (define (package-stexi-standard-titlepage name version updated authors)
- "Create a standard GNU title page.
- @var{authors} is a list of @code{(@var{name} . @var{email})}
- pairs. All other arguments are strings.
- Here is an example of the usage of this procedure:
- @smallexample
- (package-stexi-standard-titlepage
- \"Foolib\"
- \"3.2\"
- \"26 September 2006\"
- '((\"Alyssa P Hacker\" . \"alyssa@@example.com\"))
- '(2004 2005 2006)
- \"Free Software Foundation, Inc.\"
- \"Standard GPL permissions blurb goes here\")
- @end smallexample
- "
- `(;(setchapternewpage (% (all "odd"))) makes manuals too long
- (titlepage
- (title ,name)
- (subtitle "version " ,version ", updated " ,updated)
- ,@(map (lambda (pair)
- `(author ,(car pair)
- " (" (email ,(cdr pair)) ")"))
- authors)
- (page)
- (vskip (% (all "0pt plus 1filll")))
- (insertcopying))))
- (define (package-stexi-generic-menu name entries)
- "Create a menu from a generic alist of entries, the car of which
- should be the node name, and the cdr the description. As an exception,
- an entry of @code{#f} will produce a separator."
- (define (make-entry node description)
- `("* " ,node "::"
- ,(make-string (max (- 21 (string-length node)) 2) #\space)
- ,@description "\n"))
- `((ifnottex
- (node (% (name "Top")))
- (top (% (title ,name)))
- (insertcopying)
- (menu
- ,@(apply
- append
- (map
- (lambda (entry)
- (if entry
- (make-entry (car entry) (cdr entry))
- '("\n")))
- entries))))
- (iftex
- (shortcontents))))
- (define (package-stexi-standard-menu name modules module-descriptions
- extra-entries)
- "Create a standard top node and menu, suitable for processing
- by makeinfo."
- (package-stexi-generic-menu
- name
- (let ((module-entries (map cons
- (map module-name->node-name modules)
- module-descriptions))
- (separate-sections (lambda (x) (if (null? x) x (cons #f x)))))
- `(,@module-entries
- ,@(separate-sections extra-entries)))))
- (define (package-stexi-extended-menu name module-pairs script-pairs
- extra-entries)
- "Create an \"extended\" menu, like the standard menu but with a
- section for scripts."
- (package-stexi-generic-menu
- name
- (let ((module-entries (map cons
- (map module-name->node-name
- (map car module-pairs))
- (map cdr module-pairs)))
- (script-entries (map cons
- (map basename (map car script-pairs))
- (map cdr script-pairs)))
- (separate-sections (lambda (x) (if (null? x) x (cons #f x)))))
- `(,@module-entries
- ,@(separate-sections script-entries)
- ,@(separate-sections extra-entries)))))
- (define (package-stexi-standard-prologue name filename category
- description copying titlepage
- menu)
- "Create a standard prologue, suitable for later serialization
- to texinfo and .info creation with makeinfo.
- Returns a list of stexinfo forms suitable for passing to
- @code{package-stexi-documentation} as the prologue. @xref{texinfo
- reflection package-stexi-documentation}, @ref{texinfo reflection
- package-stexi-standard-titlepage,package-stexi-standard-titlepage},
- @ref{texinfo reflection
- package-stexi-standard-copying,package-stexi-standard-copying},
- and @ref{texinfo reflection
- package-stexi-standard-menu,package-stexi-standard-menu}."
- `(,copying
- (dircategory (% (category ,category)))
- (direntry
- "* " ,name ": (" ,filename "). " ,description ".")
- ,@titlepage
- ,@menu))
- (define (stexi->chapter stexi)
- (pre-post-order
- stexi
- `((texinfo . ,(lambda (tag attrs node . body)
- `(,node
- (chapter ,@(assq-ref (cdr attrs) 'title))
- ,@body)))
- (*text* . ,(lambda (tag text) text))
- (*default* . ,(lambda args args)))))
- (define* (package-stexi-documentation modules name filename
- prologue epilogue
- #:key
- (module-stexi-documentation-args
- '())
- (scripts '()))
- "Create stexi documentation for a @dfn{package}, where a
- package is a set of modules that is released together.
- @var{modules} is expected to be a list of module names, where a
- module name is a list of symbols. The stexi that is returned will
- be titled @var{name} and a texinfo filename of @var{filename}.
- @var{prologue} and @var{epilogue} are lists of stexi forms that
- will be spliced into the output document before and after the
- generated modules documentation, respectively.
- @xref{texinfo reflection package-stexi-standard-prologue}, to
- create a conventional GNU texinfo prologue.
- @var{module-stexi-documentation-args} is an optional argument that, if
- given, will be added to the argument list when
- @code{module-texi-documentation} is called. For example, it might be
- useful to define a @code{#:docs-resolver} argument."
- (define (verify-modules-list l)
- (define (all pred l)
- (and (pred (car l))
- (or (null? (cdr l)) (all pred (cdr l)))))
- (false-if-exception
- (all (lambda (x) (all symbol? x)) modules)))
- (if (not (verify-modules-list modules))
- (error "expected modules to be a list of a list of symbols"
- modules))
- `(texinfo
- (% (title ,name)
- (filename ,filename))
- ,@prologue
- ,@(append-map (lambda (mod)
- (stexi->chapter
- (apply module-stexi-documentation
- mod module-stexi-documentation-args)))
- modules)
- ,@(append-map (lambda (script)
- (stexi->chapter
- (script-stexi-documentation script)))
- scripts)
- ,@epilogue))
- (define* (package-stexi-documentation-for-include modules module-descriptions
- #:key
- (module-stexi-documentation-args '()))
- "Create stexi documentation for a @dfn{package}, where a
- package is a set of modules that is released together.
- @var{modules} is expected to be a list of module names, where a
- module name is a list of symbols. Returns an stexinfo fragment.
- Unlike @code{package-stexi-documentation}, this function simply produces
- a menu and the module documentations instead of producing a full texinfo
- document. This can be useful if you write part of your manual by hand,
- and just use @code{@@include} to pull in the automatically generated
- parts.
- @var{module-stexi-documentation-args} is an optional argument that, if
- given, will be added to the argument list when
- @code{module-texi-documentation} is called. For example, it might be
- useful to define a @code{#:docs-resolver} argument."
- (define (make-entry node description)
- `("* " ,node "::"
- ,(make-string (max (- 21 (string-length node)) 2) #\space)
- ,@description "\n"))
- `(*fragment*
- (menu
- ,@(append-map (lambda (modname desc)
- (make-entry (module-name->node-name modname)
- desc))
- modules
- module-descriptions))
- ,@(append-map (lambda (modname)
- (stexi->chapter
- (apply module-stexi-documentation
- modname
- module-stexi-documentation-args)))
- modules)))
- ;;; arch-tag: bbe2bc03-e16d-4a9e-87b9-55225dc9836c
|