3 ;;;; (c) 2004 Rudi Schlatte <rudi@constantly.at>
4 ;;;; Use it as you wish, send changes back to me if you like.
7 (eval-when (:compile-toplevel :load-toplevel :execute)
8 (require 'sb-introspect))
10 (defparameter *documentation-types*
15 ;;structure ; also handled by `type'
18 "A list of symbols accepted as second argument of `documentation'")
20 ;;; Collecting info from package
22 (defun documentation-for-symbol (symbol)
23 "Collects all doc for a symbol, returns a list of the
24 form (symbol doc-type docstring). See `*documentation-types*'
25 for the possible values of doc-type."
26 (loop for kind in *documentation-types*
27 for doc = (documentation symbol kind)
29 collect (list symbol kind doc)))
31 (defun collect-documentation (package)
32 "Collects all documentation for all external symbols of the
33 given package, as well as for the package itself."
34 (let* ((package (find-package package))
35 (package-doc (documentation package t))
37 (check-type package package)
38 (do-external-symbols (symbol package)
39 (let ((docs (documentation-for-symbol symbol)))
40 (when docs (setf result (nconc docs result)))))
42 (setf result (nconc (list (list (intern (package-name package) :keyword)
43 'package package-doc)) result)))
46 ;;; Helpers for texinfo output
48 (defvar *texinfo-escaped-chars* "@{}"
49 "Characters that must be escaped with #\@ for Texinfo.")
51 (defun texinfoify (string-designator)
52 "Return 'string-designator' with characters in
53 *texinfo-escaped-chars* escaped with #\@"
54 (let ((name (string string-designator)))
56 (with-output-to-string (s)
57 (loop for char across name
58 when (find char *texinfo-escaped-chars*)
60 do (write-char char s))))))
62 ;;; Begin, rest and end of definition.
64 (defun argument-list (fname)
65 (sb-introspect:function-arglist fname))
67 (defvar *character-replacements*
68 '((#\* . "star") (#\/ . "slash") (#\+ . "plus"))
69 "Characters and their replacement names that `alphanumize'
70 uses. If the replacements contain any of the chars they're
71 supposed to replace, you deserve to lose.")
73 (defvar *characters-to-drop* '(#\\ #\` #\')
74 "Characters that should be removed by `alphanumize'.")
77 (defun alphanumize (symbol)
78 "Construct a string without characters like *`' that will
79 f-star-ck up filename handling. See `*character-replacements*'
80 and `*characters-to-drop*' for customization."
81 (let ((name (remove-if #'(lambda (x) (member x *characters-to-drop*))
83 (chars-to-replace (mapcar #'car *character-replacements*)))
84 (flet ((replacement-delimiter (index)
85 (cond ((or (< index 0) (>= index (length name))) "")
86 ((alphanumericp (char name index)) "-")
88 (loop for index = (position-if #'(lambda (x) (member x chars-to-replace))
91 do (setf name (concatenate 'string (subseq name 0 index)
92 (replacement-delimiter (1- index))
93 (cdr (assoc (aref name index)
94 *character-replacements*))
95 (replacement-delimiter (1+ index))
96 (subseq name (1+ index))))))
99 (defun unique-name (symbol package kind)
101 (format nil "~A-~A-~A"
103 (compiler-macro "compiler-macro")
105 ((macro-function symbol) "macro")
106 ((special-operator-p symbol) "special-operator")
108 (method-combination "method-combination")
110 (setf "setf-expander")
112 (type (let ((class (find-class symbol)))
114 (structure-class "struct")
115 (standard-class "class")
116 (sb-pcl::condition-class "condition")
117 ((or built-in-class null) "type"))))
118 (variable (if (constantp symbol)
121 (package-name package)
122 (alphanumize symbol))))
124 (defun def-begin (symbol kind)
126 (compiler-macro "@deffn {Compiler Macro}")
128 ((macro-function symbol) "@defmac")
129 ((special-operator-p symbol) "@defspec")
131 (method-combination "@deffn {Method Combination}")
132 (package "@deffn Package")
133 (setf "@deffn {Setf Expander}")
134 (structure "@deftp Structure")
135 (type (let ((class (find-class symbol)))
137 (structure-class "@deftp Structure")
138 (standard-class "@deftp Class")
139 (sb-pcl::condition-class "@deftp Condition")
140 ((or built-in-class null) "@deftp Type"))))
141 (variable (if (constantp symbol)
145 (defparameter *arglist-keywords*
146 '(&allow-other-keys &aux &body &environment &key &optional &rest &whole))
148 (defun texinfoify-arglist-part (part)
149 (with-output-to-string (s)
151 (string (prin1 (texinfoify part) s))
152 (number (prin1 part s))
154 (if (member part *arglist-keywords*)
155 (princ (texinfoify part) s)
156 (format s "@var{~A}" (texinfoify part))))
158 (format s "(~{~A~^ ~})" (mapcar #'texinfoify-arglist-part part))))))
160 (defun def-rest (symbol kind)
163 (format nil "~{~A~^ ~}" (mapcar #'texinfoify-arglist-part
164 (argument-list symbol))))))
166 (defun def-end (symbol kind)
168 (compiler-macro "@end deffn")
170 ((macro-function symbol) "@end defmac")
171 ((special-operator-p symbol) "@end defspec")
173 (method-combination "@end deffn")
174 (package "@end deffn")
177 (variable (if (constantp symbol)
181 (defun make-info-file (package &optional filename)
182 "Create a file containing all available documentation for the
183 exported symbols of `package' in Texinfo format. If `filename'
184 is not supplied, a file \"<packagename>.texinfo\" is generated.
186 The definitions can be referenced using Texinfo statements like
187 @ref{<doc-type>_<packagename>_<symbol-name>.texinfo}. Texinfo
188 syntax-significant characters are escaped in symbol names, but
189 if a docstring contains invalid Texinfo markup, you lose."
190 (let* ((package (find-package package))
191 (filename (or filename (make-pathname
192 :name (string-downcase (package-name package))
194 (docs (sort (collect-documentation package) #'string< :key #'first)))
195 (with-open-file (out filename :direction :output
196 :if-does-not-exist :create :if-exists :supersede)
197 (loop for (symbol kind docstring) in docs
198 do (format out "~&@anchor{~A}~%~A ~A~@[ ~A~]~%~A~%~A~%~%"
199 (unique-name symbol package kind)
200 (def-begin symbol kind)
202 (def-rest symbol kind)
204 (def-end symbol kind))))
207 (defun docstrings-to-texinfo (directory &rest packages)
208 "Create files in `directory' containing Texinfo markup of all
209 docstrings of each exported symbol in `packages'. `directory'
210 is created if necessary. If you supply a namestring that
211 doesn't end in a slash, you lose. The generated files are of
212 the form \"<doc-type>_<packagename>_<symbol-name>.texinfo\" and
213 can be included via @include statements. Texinfo
214 syntax-significant characters are escaped in symbol names, but
215 if a docstring contains invalid Texinfo markup, you lose."
216 (let ((directory (merge-pathnames (pathname directory))))
217 (ensure-directories-exist directory)
218 (dolist (package packages)
220 with docs = (collect-documentation (find-package package))
221 for (symbol kind docstring) in docs
222 for doc-identifier = (unique-name symbol package kind)
223 do (with-open-file (out
225 (make-pathname :name doc-identifier :type "texinfo")
228 :if-does-not-exist :create :if-exists :supersede)
229 (format out "~&@anchor{~A}~%~A ~A~@[ ~A~]~%~A~%~A~%~%"
230 (unique-name symbol package kind)
231 (def-begin symbol kind)
233 (def-rest symbol kind)
235 (def-end symbol kind)))))