-;;;; -*- lisp -*-
+;;; -*- lisp -*-
+
+;;;; A docstring extractor for the sbcl manual. Creates
+;;;; @include-ready documentation from the docstrings of exported
+;;;; symbols of specified packages.
+
+;;;; This software is part of the SBCL software system. SBCL is in the
+;;;; public domain and is provided with absolutely no warranty. See
+;;;; the COPYING file for more information.
+
+;;;; Written by Rudi Schlatte <rudi@constantly.at>
-;;;; (c) 2004 Rudi Schlatte <rudi@constantly.at>
-;;;; Use it as you wish, send changes back to me if you like.
-#+sbcl
(eval-when (:compile-toplevel :load-toplevel :execute)
(require 'sb-introspect))
(when docs (setf result (nconc docs result)))))
(when package-doc
(setf result (nconc (list (list (intern (package-name package) :keyword)
- 'package package-doc)) result)))))
+ 'package package-doc)) result)))
+ result))
;;; Helpers for texinfo output
(defvar *texinfo-escaped-chars* "@{}"
"Characters that must be escaped with #\@ for Texinfo.")
-(defun texinfoify (string-designator)
+(defun texinfoify (string-designator &optional (downcase-p t))
"Return 'string-designator' with characters in
- *texinfo-escaped-chars* escaped with #\@"
- (let ((name (string string-designator)))
- (nstring-downcase
- (with-output-to-string (s)
- (loop for char across name
+ *texinfo-escaped-chars* escaped with #\@. Optionally downcase
+ the result."
+ (let ((result (with-output-to-string (s)
+ (loop for char across (string string-designator)
when (find char *texinfo-escaped-chars*)
do (write-char #\@ s)
- do (write-char char s))))))
+ do (write-char char s)))))
+ (if downcase-p (nstring-downcase result) result)))
+
+(defvar *symbol-characters* "ABCDEFGHIJKLMNOPQRSTUVWXYZ*:-+"
+ "List of characters that make up symbols in a docstring.")
+
+(defvar *symbol-delimiters* " ,.!?")
+
+(defun locate-symbols (line)
+ "Return a list of index pairs of symbol-like parts of LINE."
+ (do ((result nil)
+ (begin nil)
+ (maybe-begin t)
+ (i 0 (1+ i)))
+ ((= i (length line))
+ (when begin (push (list begin i) result))
+ (nreverse result))
+ (cond
+ ((and begin (find (char line i) *symbol-delimiters*))
+ ;; symbol end; remember it if it's not "A" or "I"
+ (when (or (> i (1+ begin)) (not (member (char line begin) '(#\A #\I))))
+ (push (list begin i) result))
+ (setf begin nil
+ maybe-begin t))
+ ((and begin (not (find (char line i) *symbol-characters*)))
+ ;; Not a symbol: abort
+ (setf begin nil))
+ ((and maybe-begin (not begin) (find (char line i) *symbol-characters*))
+ ;; potential symbol begin at this position
+ (setf begin i
+ maybe-begin nil))
+ ((find (char line i) *symbol-delimiters*)
+ ;; potential symbol begin after this position
+ (setf maybe-begin t)))))
+
+(defun all-symbols (list)
+ (cond ((or (null list) (numberp list)) nil)
+ ((atom list) (list list))
+ (t (append (all-symbols (car list)) (all-symbols (cdr list))))))
+
+(defun frob-docstring (docstring symbol-arglist)
+ "Try to guess as much formatting for a raw docstring as possible."
+ ;; Per-line processing is not necessary now, but it will be when we
+ ;; attempt itemize / table auto-detection in docstrings
+ (with-output-to-string (result)
+ (let ((arglist-symbols (all-symbols symbol-arglist)))
+ (with-input-from-string (s (texinfoify docstring nil))
+ (loop for line = (read-line s nil nil)
+ while line
+ do (let ((last 0))
+ (dolist (symbol-index (locate-symbols line))
+ (write-string (subseq line last (first symbol-index)) result)
+ (let ((symbol-name (apply #'subseq line symbol-index)))
+ (format result (if (member symbol-name arglist-symbols
+ :test #'string=)
+ "@var{~A}"
+ "@code{~A}")
+ (string-downcase symbol-name)))
+ (setf last (second symbol-index)))
+ (write-line (subseq line last) result)))))))
;;; Begin, rest and end of definition.
(subseq name (1+ index))))))
name))
-(defun unique-name (symbol kind)
+(defun unique-name (symbol package kind)
(nstring-downcase
(format nil "~A-~A-~A"
(ecase kind
(compiler-macro "compiler-macro")
- (function (if (macro-function symbol)
- "macro"
- "fun"))
+ (function (cond
+ ((macro-function symbol) "macro")
+ ((special-operator-p symbol) "special-operator")
+ (t "fun")))
(method-combination "method-combination")
(package "package")
(setf "setf-expander")
(structure "struct")
- (type (let ((class (find-class symbol)))
+ (type (let ((class (find-class symbol nil)))
(etypecase class
(structure-class "struct")
(standard-class "class")
(sb-pcl::condition-class "condition")
- (null "type"))))
+ ((or built-in-class null) "type"))))
(variable (if (constantp symbol)
"constant"
"var")))
- (package-name (symbol-package symbol))
- (alphanumize symbol)
- )))
+ (package-name package)
+ (alphanumize symbol))))
(defun def-begin (symbol kind)
(ecase kind
(compiler-macro "@deffn {Compiler Macro}")
- (function (if (macro-function symbol)
- "@defmac"
- "@defun"))
+ (function (cond
+ ((macro-function symbol) "@deffn Macro")
+ ((special-operator-p symbol) "@deffn {Special Operator}")
+ (t "@deffn Function")))
(method-combination "@deffn {Method Combination}")
- (package "@deffn Package")
+ (package "@defvr Package")
(setf "@deffn {Setf Expander}")
(structure "@deftp Structure")
- (type (let ((class (find-class symbol)))
- (etypecase class
- (structure-class "@deftp Structure")
- (standard-class "@deftp Class")
- (sb-pcl::condition-class "@deftp Condition")
- (null "@deftp Type"))))
+ (type (let ((class (find-class symbol nil)))
+ (etypecase class
+ (structure-class "@deftp Structure")
+ (standard-class "@deftp Class")
+ (sb-pcl::condition-class "@deftp Condition")
+ ((or built-in-class null) "@deftp Type"))))
(variable (if (constantp symbol)
"@defvr Constant"
- "@defvar"))))
+ "@defvr Variable"))))
+
+(defun def-index (symbol kind)
+ (case kind
+ ((compiler-macro function method-combination)
+ (format nil "@findex ~A" (texinfoify symbol)))
+ ((structure type)
+ (format nil "@tindex ~A" (texinfoify symbol)))
+ (variable
+ (format nil "@vindex ~A" (texinfoify symbol)))))
(defparameter *arglist-keywords*
'(&allow-other-keys &aux &body &environment &key &optional &rest &whole))
(defun texinfoify-arglist-part (part)
(with-output-to-string (s)
(etypecase part
- (string (prin1 (texinfoify part) s))
+ (string (prin1 (texinfoify part nil) s))
(number (prin1 part s))
(symbol
(if (member part *arglist-keywords*)
(list
(format s "(~{~A~^ ~})" (mapcar #'texinfoify-arglist-part part))))))
-(defun def-rest (symbol kind)
+(defun def-arglist (symbol kind)
(case kind
(function
- (format nil "~{~A~^ ~}" (mapcar #'texinfoify-arglist-part
- (argument-list symbol))))))
+ (format nil "~{~A~^ ~}"
+ (mapcar #'texinfoify-arglist-part (argument-list symbol))))))
(defun def-end (symbol kind)
+ (declare (ignore symbol))
(ecase kind
- (compiler-macro "@end deffn")
- (function (if (macro-function symbol)
- "@end defmac"
- "@end defun"))
- (method-combination "@end deffn")
- (package "@end deffn")
- (setf "@end deffn")
- (type "@end deftp")
- (variable (if (constantp symbol)
- "@end defvr"
- "@defvar"))))
+ ((compiler-macro function method-combination setf) "@end deffn")
+ ((package variable) "@end defvr")
+ ((structure type) "@end deftp")))
(defun make-info-file (package &optional filename)
"Create a file containing all available documentation for the
(with-open-file (out filename :direction :output
:if-does-not-exist :create :if-exists :supersede)
(loop for (symbol kind docstring) in docs
- do (format out "~&@anchor{~A}~%~A ~A~@[ ~A~]~%~A~%~A~%~%"
- (unique-name symbol kind)
+ do (format out "~&@anchor{~A}~%~A ~A:~A~@[ ~A~]~%~A~&~A~%~A~%~%"
+ (unique-name symbol package kind)
(def-begin symbol kind)
+ (texinfoify (package-name package))
(texinfoify symbol)
- (def-rest symbol kind)
- docstring
+ (def-arglist symbol kind)
+ (def-index symbol kind)
+ (frob-docstring docstring (argument-list symbol))
(def-end symbol kind))))
filename))
(loop
with docs = (collect-documentation (find-package package))
for (symbol kind docstring) in docs
- for doc-identifier = (unique-name symbol kind)
+ for doc-identifier = (unique-name symbol package kind)
do (with-open-file (out
(merge-pathnames
(make-pathname :name doc-identifier :type "texinfo")
directory)
:direction :output
:if-does-not-exist :create :if-exists :supersede)
- (format out "~&@anchor{~A}~%~A ~A~@[ ~A~]~%~A~%~A~%~%"
- (unique-name symbol kind)
+ (format out "~&@anchor{~A}~%~A ~A:~A~@[ ~A~]~%~A~&~A~%~A~%~%"
+ (unique-name symbol package kind)
(def-begin symbol kind)
+ (texinfoify (package-name package))
(texinfoify symbol)
- (def-rest symbol kind)
- docstring
+ (def-arglist symbol kind)
+ (def-index symbol kind)
+ (frob-docstring docstring (ignore-errors (argument-list symbol)))
(def-end symbol kind)))))
directory))