Mercurial > emacs
diff lisp/eieio/eieio-doc.el @ 104401:2efe3dc24373
Add files for the EIEIO library.
author | Chong Yidong <cyd@stupidchicken.com> |
---|---|
date | Sat, 22 Aug 2009 04:12:52 +0000 |
parents | |
children | 7602fd69cd93 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/eieio/eieio-doc.el Sat Aug 22 04:12:52 2009 +0000 @@ -0,0 +1,365 @@ +;;; eieio-doc.el --- create texinfo documentation for an eieio class + +;;; Copyright (C) 1996, 1998, 1999, 2000, 2001, 2004, 2005 +;;; Free Software Foundation, Inc. + +;; Author: Eric M. Ludlam <zappo@gnu.org> +;; Version: 0.2 +;; Keywords: OO, lisp, docs + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs 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 General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; Outputs into the current buffer documentation in texinfo format + +(require 'eieio-opt) + +;; for a class, all it's children, and all it's slots. + +;;; Code: +(defvar eieiodoc-currently-in-node nil + "String representing the node we go BACK to.") + +(defvar eieiodoc-current-section-level nil + "String represending what type of section header to use.") + +(defvar eieiodoc-prev-class nil + "Non-nil when while `eieiodoc-recurse' is running. +Can be referenced from the recursed function.") + +(defvar eieiodoc-next-class nil + "Non-nil when `eieiodoc-recurse' is running. +Can be referenced from the recursed function.") + +(defun eieiodoc-class-nuke (root-class indexstring &optional skiplist) + "Call `eieiodoc-class' after nuking everything from POINT on. +ROOT-CLASS, INDEXSTRING, and SKIPLIST are the same as `eieiodoc-class'." + (delete-region (point) (point-max)) + (sit-for 0) + (eieiodoc-class root-class indexstring skiplist)) + +(defun eieiodoc-class (root-class indexstring &optional skiplist) + "Create documentation starting with ROOT-CLASS. +The first job is to create an indented menu of all the classes +starting with `root-class' and including all it's children. Once this +is done, @nodes are created for all the subclasses. Each node is then +documented with a description of the class, a brief inheritance tree +\(with xrefs) and a list of all slots in a big table. Where each slot +is inherited from is also documented. In addition, each class is +documented in the index referenced by INDEXSTRING, a two letter code +described in the texinfo manual. + +The optional third argument SKIPLIST is a list of object not to put +into any menus, nodes or lists." + (interactive + (list (intern-soft + (completing-read "Class: " (eieio-build-class-alist) nil t)) + (read-string "Index name (2 chars): "))) + (if (looking-at "[ \t\n]+@end ignore") + (goto-char (match-end 0))) + (save-excursion + (setq eieiodoc-currently-in-node + (if (re-search-backward "@node \\([^,]+\\)" nil t) + (buffer-substring (match-beginning 1) (match-end 1)) + "Top") + eieiodoc-current-section-level + (if (re-search-forward "@\\(chapter\\|\\(sub\\)*section\\)" + (+ (point) 500) t) + (progn + (goto-char (match-beginning 0)) + (cond ((looking-at "@chapter") "section") + ((looking-at "@section") "subsection") + ((looking-at "@\\(sub\\)+section") "subsubsection") + (t "subsubsection"))) + "subsubsection"))) + (save-excursion + (eieiodoc-main-menu root-class skiplist) + (insert "\n") + (eieiodoc-recurse root-class 'eieiodoc-one-node nil skiplist))) + +(defun eieiodoc-main-menu (class skiplist) + "Create a menu of all classes under CLASS indented the correct amount. +SKIPLIST is a list of objects to skip" + (end-of-line) + (insert "\n@menu\n") + (eieiodoc-recurse class (lambda (class level) + (insert "* " (make-string level ? ) + (symbol-name class) " ::\n")) + nil skiplist) + (insert "@end menu\n")) + +(defun eieiodoc-one-node (class level) + "Create a node for CLASS, and for all subclasses of CLASS in order. +This function should only be called by `eieiodoc-class' +Argument LEVEL is the current level of recursion we have hit." + (message "Building node for %s" class) + (insert "\n@node " (symbol-name class) ", " + (if eieiodoc-next-class (symbol-name eieiodoc-next-class) " ") ", " + (if eieiodoc-prev-class (symbol-name eieiodoc-prev-class) " ") ", " + eieiodoc-currently-in-node "\n" + "@comment node-name, next, previous, up\n" + "@" eieiodoc-current-section-level " " (symbol-name class) "\n" + ;; indexstring is grabbed from parent calling function + "@" indexstring "index " (symbol-name class) "\n\n") + ;; Now lets create a nifty little inheritance tree + (let ((cl class) + (revlist nil) + (depth 0)) + (while cl + (setq revlist (cons cl revlist) + cl (class-parent cl))) + (insert "@table @asis\n@item Inheritance Tree:\n") + (while revlist + ;; root-class is dragged in from the top-level function + (insert "@table @code\n@item " + (if (and (child-of-class-p (car revlist) root-class) + (not (eq class (car revlist)))) + (concat "@w{@xref{" (symbol-name (car revlist)) "}.}") + (symbol-name (car revlist))) + "\n") + (setq revlist (cdr revlist) + depth (1+ depth))) + ;; the value of rclass is brought in from caller + (let ((clist (reverse (aref (class-v rclass) class-children)))) + (if (not clist) + (insert "No children") + (insert "@table @asis\n@item Children:\n") + (while clist + (insert "@w{@xref{" (symbol-name (car clist)) "}") + (if (cdr clist) (insert ",") (insert ".")) + (insert "} ") + (setq clist (cdr clist))) + (insert "\n@end table\n") + )) + (while (> depth 0) + (insert "\n@end table\n") + (setq depth (1- depth))) + (insert "@end table\n\n ")) + ;; Now lets build some documentation by extracting information from + ;; the class description vector + (let* ((cv (class-v class)) + (docs (aref cv class-public-doc)) + (names (aref cv class-public-a)) + (deflt (aref cv class-public-d)) + (prot (aref cv class-protection)) + (typev (aref cv class-public-type)) + (i 0) + (set-one nil) + (anchor nil) + ) + ;; doc of the class itself + (insert (eieiodoc-texify-docstring (documentation class) class) + "\n\n@table @asis\n") + (if names + (progn + (setq anchor (point)) + (insert "@item Slots:\n\n@table @code\n") + (while names + (if (eieiodoc-one-attribute class (car names) (car docs) + (car prot) (car deflt) (aref typev i)) + (setq set-one t)) + (setq names (cdr names) + docs (cdr docs) + prot (cdr prot) + deflt (cdr deflt) + i (1+ i))) + (insert "@end table\n\n") + (if (not set-one) (delete-region (point) anchor)) + )) + (insert "@end table\n") + ;; Finally, document all the methods associated with this class. + (let ((methods (eieio-all-generic-functions class)) + (doc nil)) + (if (not methods) nil + (if (string= eieiodoc-current-section-level "subsubsection") + (insert "@" eieiodoc-current-section-level) + (insert "@sub" eieiodoc-current-section-level)) + (insert " Specialized Methods\n\n") + (while methods + (setq doc (eieio-method-documentation (car methods) class)) + (insert "@deffn Method " (symbol-name (car methods))) + (if (not doc) + (insert "\n Undocumented") + (if (car doc) + (progn + (insert " :BEFORE ") + (eieiodoc-output-deffn-args (car (car doc))) + (insert "\n") + (eieiodoc-insert-and-massage-docstring-with-args + (cdr (car doc)) (car (car doc)) class))) + (setq doc (cdr doc)) + (if (car doc) + (progn + (insert " :PRIMARY ") + (eieiodoc-output-deffn-args (car (car doc))) + (insert "\n") + (eieiodoc-insert-and-massage-docstring-with-args + (cdr (car doc)) (car (car doc)) class))) + (setq doc (cdr doc)) + (if (car doc) + (progn + (insert " :AFTER ") + (eieiodoc-output-deffn-args (car (car doc))) + (insert "\n") + (eieiodoc-insert-and-massage-docstring-with-args + (cdr (car doc)) (car (car doc)) class))) + (insert "\n@end deffn\n\n")) + (setq methods (cdr methods))))) + )) + +(defun eieiodoc-insert-and-massage-docstring-with-args (doc arglst class) + "Update DOC with texinfo strings using ARGLST with @var. +Argument CLASS is the class passed to `eieiodoc-texify-docstring'." + (let ((start (point)) + (end nil) + (case-fold-search nil)) + ;; Insert the text + (insert (eieiodoc-texify-docstring doc class)) + (setq end (point)) + (save-restriction + (narrow-to-region start end) + (save-excursion + ;; Now find arguments + (while arglst + (goto-char (point-min)) + (while (re-search-forward (upcase (symbol-name (car arglst))) nil t) + (replace-match "@var{\\&}" t)) + (setq arglst (cdr arglst))))))) + +(defun eieiodoc-output-deffn-args (arglst) + "Output ARGLST for a deffn." + (while arglst + (insert (symbol-name (car arglst)) " ") + (setq arglst (cdr arglst)))) + +(defun eieiodoc-one-attribute (class attribute doc priv deflt type) + "Create documentation of CLASS for a single ATTRIBUTE. +Assume this attribute is inside a table, so it is initiated with the +@item indicator. If this attribute is not inserted (because it is +contained in the parent) then return nil, else return t. +DOC is the documentation to use, PRIV is non-nil if it is a private slot, +and DEFLT is the default value. TYPE is the symbol describing what type +validation is done on that slot." + (let ((pv (eieiodoc-parent-diff class attribute)) + (ia (eieio-attribute-to-initarg class attribute)) + (set-me nil)) + (if (or (eq pv t) (not ia)) + nil ;; same in parent or no init arg + (setq set-me t) + (insert "@item " (if priv "Private: " "") + (symbol-name ia)) + (if (and type (not (eq type t))) + (insert "\nType: @code{" (format "%S" type) "}")) + (if (not (eq deflt eieio-unbound)) + (insert " @*\nDefault Value: @code{"(format "%S" deflt) "}")) + (insert "\n\n") + (if (eq pv 'default) + ;; default differs only, xref the parent + ;; This should be upgraded to actually search for the last + ;; differing default (or the original.) + (insert "@xref{" (symbol-name (class-parent class)) "}.\n") + (insert (if doc (eieiodoc-texify-docstring doc class) "Not Documented") + "\n@refill\n\n"))) + set-me)) +;;; +;; Utilities +;; +(defun eieiodoc-recurse (rclass func &optional level skiplist) + "Recurse down all children of RCLASS, calling FUNC on each one. +LEVEL indicates the current depth below the first call we are. The +function FUNC will be called with RCLASS and LEVEL. This will then +recursivly call itself once for each child class of RCLASS. The +optional fourth argument SKIPLIST is a list of objects to ignore while +recursing." + + (if (not level) (setq level 0)) + + ;; we reverse the children so they appear in the same order as it + ;; does in the code that creates them. + (let* ((children (reverse (aref (class-v rclass) class-children))) + (ocnc eieiodoc-next-class) + (eieiodoc-next-class (or (car children) ocnc)) + (eieiodoc-prev-class eieiodoc-prev-class)) + + (if (not (member rclass skiplist)) + (progn + (apply func (list rclass level)) + + (setq eieiodoc-prev-class rclass))) + + (while children + (setq eieiodoc-next-class (or (car (cdr children)) ocnc)) + (setq eieiodoc-prev-class (eieiodoc-recurse (car children) func (1+ level))) + (setq children (cdr children))) + ;; return the previous class so that the prev/next node gets it right + eieiodoc-prev-class)) + +(defun eieiodoc-parent-diff (class slot) + "Return nil if the parent of CLASS does not have slot SLOT. +Return t if it does, and return 'default if the default has changed." + (let ((df nil) (err t) + (scoped-class (class-parent class)) + (eieio-skip-typecheck)) + (condition-case nil + (setq df (eieio-oref-default (class-parent class) slot) + err nil) + (invalid-slot-name (setq df nil)) + (error (setq df nil))) + (if err + nil + (if (equal df (eieio-oref-default class slot)) + t + 'default)))) + +(defun eieiodoc-texify-docstring (string class) + "Take STRING, (a normal doc string), and convert it into a texinfo string. +For instances where CLASS is the class being referenced, do not Xref +that class. + + `function' => @dfn{function} + `variable' => @code{variable} + `class' => @code{class} @xref{class} + `unknown' => @code{unknonwn} + 'quoteme => @code{quoteme} + non-nil => non-@code{nil} + t => @code{t} + :tag => @code{:tag} + [ stuff ] => @code{[ stuff ]} + Key => @kbd{Key}" + (while (string-match "`\\([-a-zA-Z0-9]+\\)'" string) + (let* ((vs (substring string (match-beginning 1) (match-end 1))) + (v (intern-soft vs))) + (setq string + (concat + (replace-match (concat + (if (and (not (class-p v))(fboundp v)) + "@dfn{" "@code{") + vs "}" + (if (and (class-p v) (not (eq v class))) + (concat " @xref{" vs "}."))) + nil t string))))) + (while (string-match "\\( \\|^\\|-\\)\\(nil\\|t\\|'[-a-zA-Z0-9]+\\|:[-a-zA-Z0-9]+\\)\\([ ,]\\|$\\)" string) + (setq string (replace-match "@code{\\2}" t nil string 2))) + (while (string-match "\\( \\|^\\)\\(\\[[^]]+\\]\\)\\( \\|$\\)" string) + (setq string (replace-match "@code{\\2}" t nil string 2))) + (while (string-match "\\( \\|^\\)\\(\\(\\(C-\\|M-\\|S-\\)+\\([^ \t\n]\\|RET\\|SPC\\|TAB\\)\\)\\|\\(RET\\|SPC\\|TAB\\)\\)\\( \\|$\\)" string) + (setq string (replace-match "@kbd{\\2}" t nil string 2))) + string) + +(provide 'eieio-doc) + +;;; eieio-doc.el ends here