| 1 | ;; texi-docstring-magic.el -- munge internal docstrings into texi | 
|---|
| 2 | ;; | 
|---|
| 3 | ;; Keywords: lisp, docs, tex | 
|---|
| 4 | ;; Author: David Aspinall <da@dcs.ed.ac.uk> | 
|---|
| 5 | ;; Copyright (C) 1998 David Aspinall | 
|---|
| 6 | ;; Maintainer:  David Aspinall <da@dcs.ed.ac.uk> | 
|---|
| 7 | ;; | 
|---|
| 8 | ;; $Id: texi-docstring-magic.el,v 1.2 2004/04/11 17:56:47 karl Exp $ | 
|---|
| 9 | ;; | 
|---|
| 10 | ;; This package is distributed under the terms of the | 
|---|
| 11 | ;; GNU General Public License, Version 2. | 
|---|
| 12 | ;; You should have a copy of the GPL with your version of | 
|---|
| 13 | ;; GNU Emacs or the Texinfo distribution. | 
|---|
| 14 | ;; | 
|---|
| 15 | ;; | 
|---|
| 16 | ;; This package generates Texinfo source fragments from Emacs | 
|---|
| 17 | ;; docstrings.   This avoids documenting functions and variables | 
|---|
| 18 | ;; in more than one place, and automatically adds Texinfo markup | 
|---|
| 19 | ;; to docstrings. | 
|---|
| 20 | ;; | 
|---|
| 21 | ;; It relies heavily on you following the Elisp documentation | 
|---|
| 22 | ;; conventions to produce sensible output, check the Elisp manual | 
|---|
| 23 | ;; for details.  In brief: | 
|---|
| 24 | ;; | 
|---|
| 25 | ;;  * The first line of a docstring should be a complete sentence. | 
|---|
| 26 | ;;  * Arguments to functions should be written in upper case: ARG1..ARGN | 
|---|
| 27 | ;;  * User options (variables users may want to set) should have docstrings | 
|---|
| 28 | ;;    beginning with an asterisk. | 
|---|
| 29 | ;; | 
|---|
| 30 | ;; Usage: | 
|---|
| 31 | ;; | 
|---|
| 32 | ;;  Write comments of the form: | 
|---|
| 33 | ;; | 
|---|
| 34 | ;;    @c TEXI DOCSTRING MAGIC: my-package-function-or-variable-name | 
|---|
| 35 | ;; | 
|---|
| 36 | ;;  In your texi source, mypackage.texi.  From within an Emacs session | 
|---|
| 37 | ;;  where my-package is loaded, visit mypackage.texi and run | 
|---|
| 38 | ;;  M-x texi-docstring-magic to update all of the documentation strings. | 
|---|
| 39 | ;; | 
|---|
| 40 | ;;  This will insert @defopt, @deffn and the like underneath the | 
|---|
| 41 | ;;  magic comment strings. | 
|---|
| 42 | ;; | 
|---|
| 43 | ;;  The default value for user options will be printed. | 
|---|
| 44 | ;; | 
|---|
| 45 | ;;  Symbols are recognized if they are defined for faces, functions, | 
|---|
| 46 | ;;  or variables (in that order). | 
|---|
| 47 | ;; | 
|---|
| 48 | ;; Automatic markup rules: | 
|---|
| 49 | ;; | 
|---|
| 50 | ;; 1. Indented lines are gathered into @lisp environment. | 
|---|
| 51 | ;; 2. Pieces of text `stuff' or surrounded in quotes marked up with @samp. | 
|---|
| 52 | ;; 3. Words *emphasized* are made @strong{emphasized} | 
|---|
| 53 | ;; 4. Words sym-bol which are symbols become @code{sym-bol}. | 
|---|
| 54 | ;; 5. Upper cased words ARG corresponding to arguments become @var{arg}. | 
|---|
| 55 | ;;    In fact, you can any word longer than three letters, so that | 
|---|
| 56 | ;;    metavariables can be used easily. | 
|---|
| 57 | ;;    FIXME: to escape this, use `ARG' | 
|---|
| 58 | ;; 6. Words 'sym which are lisp-quoted are marked with @code{'sym}. | 
|---|
| 59 | ;; | 
|---|
| 60 | ;; ----- | 
|---|
| 61 | ;; | 
|---|
| 62 | ;; Useful key binding when writing Texinfo: | 
|---|
| 63 | ;; | 
|---|
| 64 | ;;  (define-key TeXinfo-mode-map "C-cC-d" 'texi-docstring-magic-insert-magic) | 
|---|
| 65 | ;; | 
|---|
| 66 | ;; ----- | 
|---|
| 67 | ;; | 
|---|
| 68 | ;; Useful enhancements to do: | 
|---|
| 69 | ;; | 
|---|
| 70 | ;;  * Use customize properties (e.g. group, simple types) | 
|---|
| 71 | ;;  * Look for a "texi-docstring" property for symbols | 
|---|
| 72 | ;;    so TeXInfo can be defined directly in case automatic markup | 
|---|
| 73 | ;;    goes badly wrong. | 
|---|
| 74 | ;;  * Add tags to special comments so that user can specify face, | 
|---|
| 75 | ;;    function, or variable binding for a symbol in case more than | 
|---|
| 76 | ;;    one binding exists. | 
|---|
| 77 | ;; | 
|---|
| 78 | ;; ------ | 
|---|
| 79 |  | 
|---|
| 80 | (defun texi-docstring-magic-splice-sep (strings sep) | 
|---|
| 81 | "Return concatenation of STRINGS spliced together with separator SEP." | 
|---|
| 82 | (let (str) | 
|---|
| 83 | (while strings | 
|---|
| 84 | (setq str (concat str (car strings))) | 
|---|
| 85 | (if (cdr strings) | 
|---|
| 86 | (setq str (concat str sep))) | 
|---|
| 87 | (setq strings (cdr strings))) | 
|---|
| 88 | str)) | 
|---|
| 89 |  | 
|---|
| 90 | (defconst texi-docstring-magic-munge-table | 
|---|
| 91 | '(;; 1. Indented lines are gathered into @lisp environment. | 
|---|
| 92 | ("\\(^.*\\S-.*$\\)" | 
|---|
| 93 | t | 
|---|
| 94 | (let | 
|---|
| 95 | ((line (match-string 0 docstring))) | 
|---|
| 96 | (if (eq (char-syntax (string-to-char line)) ?\ ) | 
|---|
| 97 | ;; whitespace | 
|---|
| 98 | (if in-quoted-region | 
|---|
| 99 | line | 
|---|
| 100 | (setq in-quoted-region t) | 
|---|
| 101 | (concat "@lisp\n" line)) | 
|---|
| 102 | ;; non-white space | 
|---|
| 103 | (if in-quoted-region | 
|---|
| 104 | (progn | 
|---|
| 105 | (setq in-quoted-region nil) | 
|---|
| 106 | (concat "@end lisp\n" line)) | 
|---|
| 107 | line)))) | 
|---|
| 108 | ;; 2. Pieces of text `stuff' or surrounded in quotes | 
|---|
| 109 | ;; are marked up with @samp.  NB: Must be backquote | 
|---|
| 110 | ;; followed by forward quote for this to work. | 
|---|
| 111 | ;; Can't use two forward quotes else problems with | 
|---|
| 112 | ;; symbols. | 
|---|
| 113 | ;; Odd hack: because ' is a word constituent in text/texinfo | 
|---|
| 114 | ;; mode, putting this first enables the recognition of args | 
|---|
| 115 | ;; and symbols put inside quotes. | 
|---|
| 116 | ("\\(`\\([^']+\\)'\\)" | 
|---|
| 117 | t | 
|---|
| 118 | (concat "@samp{" (match-string 2 docstring) "}")) | 
|---|
| 119 | ;; 3. Words *emphasized* are made @strong{emphasized} | 
|---|
| 120 | ("\\(\\*\\(\\w+\\)\\*\\)" | 
|---|
| 121 | t | 
|---|
| 122 | (concat "@strong{" (match-string 2 docstring) "}")) | 
|---|
| 123 | ;; 4. Words sym-bol which are symbols become @code{sym-bol}. | 
|---|
| 124 | ;; Must have at least one hyphen to be recognized, | 
|---|
| 125 | ;; terminated in whitespace, end of line, or punctuation. | 
|---|
| 126 | ;; (Only consider symbols made from word constituents | 
|---|
| 127 | ;; and hyphen. | 
|---|
| 128 | ("\\(\\(\\w+\\-\\(\\w\\|\\-\\)+\\)\\)\\(\\s\)\\|\\s-\\|\\s.\\|$\\)" | 
|---|
| 129 | (or (boundp (intern (match-string 2 docstring))) | 
|---|
| 130 | (fboundp (intern (match-string 2 docstring)))) | 
|---|
| 131 | (concat "@code{" (match-string 2 docstring) "}" | 
|---|
| 132 | (match-string 4 docstring))) | 
|---|
| 133 | ;; 5. Upper cased words ARG corresponding to arguments become | 
|---|
| 134 | ;; @var{arg} | 
|---|
| 135 | ;; In fact, include any word so long as it is more than 3 characters | 
|---|
| 136 | ;; long.  (Comes after symbols to avoid recognizing the | 
|---|
| 137 | ;; lowercased form of an argument as a symbol) | 
|---|
| 138 | ;; FIXME: maybe we don't want to downcase stuff already | 
|---|
| 139 | ;; inside @samp | 
|---|
| 140 | ;; FIXME: should - terminate?  should _ be included? | 
|---|
| 141 | ("\\([A-Z0-9\\-]+\\)\\(/\\|\)\\|}\\|\\s-\\|\\s.\\|$\\)" | 
|---|
| 142 | (or (> (length (match-string 1 docstring)) 3) | 
|---|
| 143 | (member (downcase (match-string 1 docstring)) args)) | 
|---|
| 144 | (concat "@var{" (downcase (match-string 1 docstring)) "}" | 
|---|
| 145 | (match-string 2 docstring))) | 
|---|
| 146 |  | 
|---|
| 147 | ;; 6. Words 'sym which are lisp quoted are | 
|---|
| 148 | ;; marked with @code. | 
|---|
| 149 | ("\\(\\(\\s-\\|^\\)'\\(\\(\\w\\|\\-\\)+\\)\\)\\(\\s\)\\|\\s-\\|\\s.\\|$\\)" | 
|---|
| 150 | t | 
|---|
| 151 | (concat (match-string 2 docstring) | 
|---|
| 152 | "@code{'" (match-string 3 docstring) "}" | 
|---|
| 153 | (match-string 5 docstring))) | 
|---|
| 154 | ;; 7,8. Clean up for @lisp environments left with spurious newlines | 
|---|
| 155 | ;; after 1. | 
|---|
| 156 | ("\\(\\(^\\s-*$\\)\n@lisp\\)" t "@lisp") | 
|---|
| 157 | ("\\(\\(^\\s-*$\\)\n@end lisp\\)" t "@end lisp")) | 
|---|
| 158 | "Table of regexp matches and replacements used to markup docstrings. | 
|---|
| 159 | Format of table is a list of elements of the form | 
|---|
| 160 | (regexp predicate replacement-form) | 
|---|
| 161 | If regexp matches and predicate holds, then replacement-form is | 
|---|
| 162 | evaluated to get the replacement for the match. | 
|---|
| 163 | predicate and replacement-form can use variables arg, | 
|---|
| 164 | and forms such as (match-string 1 docstring) | 
|---|
| 165 | Match string 1 is assumed to determine the | 
|---|
| 166 | length of the matched item, hence where parsing restarts from. | 
|---|
| 167 | The replacement must cover the whole match (match string 0), | 
|---|
| 168 | including any whitespace included to delimit matches.") | 
|---|
| 169 |  | 
|---|
| 170 |  | 
|---|
| 171 | (defun texi-docstring-magic-munge-docstring (docstring args) | 
|---|
| 172 | "Markup DOCSTRING for texi according to regexp matches." | 
|---|
| 173 | (let ((case-fold-search nil)) | 
|---|
| 174 | (dolist (test texi-docstring-magic-munge-table docstring) | 
|---|
| 175 | (let ((regexp     (nth 0 test)) | 
|---|
| 176 | (predicate  (nth 1 test)) | 
|---|
| 177 | (replace    (nth 2 test)) | 
|---|
| 178 | (i          0) | 
|---|
| 179 | in-quoted-region) | 
|---|
| 180 |  | 
|---|
| 181 | (while (and | 
|---|
| 182 | (< i (length docstring)) | 
|---|
| 183 | (string-match regexp docstring i)) | 
|---|
| 184 | (setq i (match-end 1)) | 
|---|
| 185 | (if (eval predicate) | 
|---|
| 186 | (let* ((origlength  (- (match-end 0) (match-beginning 0))) | 
|---|
| 187 | (replacement (eval replace)) | 
|---|
| 188 | (newlength   (length replacement))) | 
|---|
| 189 | (setq docstring | 
|---|
| 190 | (replace-match replacement t t docstring)) | 
|---|
| 191 | (setq i (+ i (- newlength origlength)))))) | 
|---|
| 192 | (if in-quoted-region | 
|---|
| 193 | (setq docstring (concat docstring "\n@end lisp")))))) | 
|---|
| 194 | ;; Force a new line after (what should be) the first sentence, | 
|---|
| 195 | ;; if not already a new paragraph. | 
|---|
| 196 | (let* | 
|---|
| 197 | ((pos      (string-match "\n" docstring)) | 
|---|
| 198 | (needscr  (and pos | 
|---|
| 199 | (not (string= "\n" | 
|---|
| 200 | (substring docstring | 
|---|
| 201 | (1+ pos) | 
|---|
| 202 | (+ pos 2))))))) | 
|---|
| 203 | (if (and pos needscr) | 
|---|
| 204 | (concat (substring docstring 0 pos) | 
|---|
| 205 | "@*\n" | 
|---|
| 206 | (substring docstring (1+ pos))) | 
|---|
| 207 | docstring))) | 
|---|
| 208 |  | 
|---|
| 209 | (defun texi-docstring-magic-texi (env grp name docstring args &optional endtext) | 
|---|
| 210 | "Make a texi def environment ENV for entity NAME with DOCSTRING." | 
|---|
| 211 | (concat "@def" env (if grp (concat " " grp) "") " " name | 
|---|
| 212 | " " | 
|---|
| 213 | (texi-docstring-magic-splice-sep args " ") | 
|---|
| 214 | ;; " " | 
|---|
| 215 | ;; (texi-docstring-magic-splice-sep extras " ") | 
|---|
| 216 | "\n" | 
|---|
| 217 | (texi-docstring-magic-munge-docstring docstring args) | 
|---|
| 218 | "\n" | 
|---|
| 219 | (or endtext "") | 
|---|
| 220 | "@end def" env "\n")) | 
|---|
| 221 |  | 
|---|
| 222 | (defun texi-docstring-magic-format-default (default) | 
|---|
| 223 | "Make a default value string for the value DEFAULT. | 
|---|
| 224 | Markup as @code{stuff} or @lisp stuff @end lisp." | 
|---|
| 225 | (let ((text       (format "%S" default))) | 
|---|
| 226 | (concat | 
|---|
| 227 | "\nThe default value is " | 
|---|
| 228 | (if (string-match "\n" text) | 
|---|
| 229 | ;; Carriage return will break @code, use @lisp | 
|---|
| 230 | (if (stringp default) | 
|---|
| 231 | (concat "the string: \n@lisp\n" default "\n@end lisp\n") | 
|---|
| 232 | (concat "the value: \n@lisp\n" text "\n@end lisp\n")) | 
|---|
| 233 | (concat "@code{" text "}.\n"))))) | 
|---|
| 234 |  | 
|---|
| 235 |  | 
|---|
| 236 | (defun texi-docstring-magic-texi-for (symbol) | 
|---|
| 237 | (cond | 
|---|
| 238 | ;; Faces | 
|---|
| 239 | ((find-face symbol) | 
|---|
| 240 | (let* | 
|---|
| 241 | ((face      symbol) | 
|---|
| 242 | (name      (symbol-name face)) | 
|---|
| 243 | (docstring (or (face-doc-string face) | 
|---|
| 244 | "Not documented.")) | 
|---|
| 245 | (useropt   (eq ?* (string-to-char docstring)))) | 
|---|
| 246 | ;; Chop off user option setting | 
|---|
| 247 | (if useropt | 
|---|
| 248 | (setq docstring (substring docstring 1))) | 
|---|
| 249 | (texi-docstring-magic-texi "fn" "Face" name docstring nil))) | 
|---|
| 250 | ((fboundp symbol) | 
|---|
| 251 | ;; Functions. | 
|---|
| 252 | ;; We don't handle macros,  aliases, or compiled fns properly. | 
|---|
| 253 | (let* | 
|---|
| 254 | ((function  symbol) | 
|---|
| 255 | (name      (symbol-name function)) | 
|---|
| 256 | (docstring (or (documentation function) | 
|---|
| 257 | "Not documented.")) | 
|---|
| 258 | (def       (symbol-function function)) | 
|---|
| 259 | (argsyms   (cond ((eq (car-safe def) 'lambda) | 
|---|
| 260 | (nth 1 def)))) | 
|---|
| 261 | (args      (mapcar 'symbol-name argsyms))) | 
|---|
| 262 | (if (commandp function) | 
|---|
| 263 | (texi-docstring-magic-texi "fn" "Command" name docstring args) | 
|---|
| 264 | (texi-docstring-magic-texi "un" nil name docstring args)))) | 
|---|
| 265 | ((boundp symbol) | 
|---|
| 266 | ;; Variables. | 
|---|
| 267 | (let* | 
|---|
| 268 | ((variable  symbol) | 
|---|
| 269 | (name      (symbol-name variable)) | 
|---|
| 270 | (docstring (or (documentation-property variable | 
|---|
| 271 | 'variable-documentation) | 
|---|
| 272 | "Not documented.")) | 
|---|
| 273 | (useropt   (eq ?* (string-to-char docstring))) | 
|---|
| 274 | (default   (if useropt | 
|---|
| 275 | (texi-docstring-magic-format-default | 
|---|
| 276 | (default-value symbol))))) | 
|---|
| 277 | ;; Chop off user option setting | 
|---|
| 278 | (if useropt | 
|---|
| 279 | (setq docstring (substring docstring 1))) | 
|---|
| 280 | (texi-docstring-magic-texi | 
|---|
| 281 | (if useropt "opt" "var") nil name docstring nil default))) | 
|---|
| 282 | (t | 
|---|
| 283 | (error "Don't know anything about symbol %s" (symbol-name symbol))))) | 
|---|
| 284 |  | 
|---|
| 285 | (defconst texi-docstring-magic-comment | 
|---|
| 286 | "@c TEXI DOCSTRING MAGIC:" | 
|---|
| 287 | "Magic string in a texi buffer expanded into @defopt, or @deffn.") | 
|---|
| 288 |  | 
|---|
| 289 | (defun texi-docstring-magic () | 
|---|
| 290 | "Update all texi docstring magic annotations in buffer." | 
|---|
| 291 | (interactive) | 
|---|
| 292 | (save-excursion | 
|---|
| 293 | (goto-char (point-min)) | 
|---|
| 294 | (let ((magic (concat "^" | 
|---|
| 295 | (regexp-quote texi-docstring-magic-comment) | 
|---|
| 296 | "\\s-*\\(\\(\\w\\|\\-\\)+\\)$")) | 
|---|
| 297 | p | 
|---|
| 298 | symbol) | 
|---|
| 299 | (while (re-search-forward magic nil t) | 
|---|
| 300 | (setq symbol (intern (match-string 1))) | 
|---|
| 301 | (forward-line) | 
|---|
| 302 | (setq p (point)) | 
|---|
| 303 | ;; If comment already followed by an environment, delete it. | 
|---|
| 304 | (if (and | 
|---|
| 305 | (looking-at "@def\\(\\w+\\)\\s-") | 
|---|
| 306 | (search-forward (concat "@end def" (match-string 1)) nil t)) | 
|---|
| 307 | (progn | 
|---|
| 308 | (forward-line) | 
|---|
| 309 | (delete-region p (point)))) | 
|---|
| 310 | (insert | 
|---|
| 311 | (texi-docstring-magic-texi-for symbol)))))) | 
|---|
| 312 |  | 
|---|
| 313 | (defun texi-docstring-magic-face-at-point () | 
|---|
| 314 | (ignore-errors | 
|---|
| 315 | (let ((stab (syntax-table))) | 
|---|
| 316 | (unwind-protect | 
|---|
| 317 | (save-excursion | 
|---|
| 318 | (set-syntax-table emacs-lisp-mode-syntax-table) | 
|---|
| 319 | (or (not (zerop (skip-syntax-backward "_w"))) | 
|---|
| 320 | (eq (char-syntax (char-after (point))) ?w) | 
|---|
| 321 | (eq (char-syntax (char-after (point))) ?_) | 
|---|
| 322 | (forward-sexp -1)) | 
|---|
| 323 | (skip-chars-forward "'") | 
|---|
| 324 | (let ((obj (read (current-buffer)))) | 
|---|
| 325 | (and (symbolp obj) (find-face obj) obj))) | 
|---|
| 326 | (set-syntax-table stab))))) | 
|---|
| 327 |  | 
|---|
| 328 | (defun texi-docstring-magic-insert-magic (symbol) | 
|---|
| 329 | (interactive | 
|---|
| 330 | (let* ((v (or (variable-at-point) | 
|---|
| 331 | (function-at-point) | 
|---|
| 332 | (texi-docstring-magic-face-at-point))) | 
|---|
| 333 | (val (let ((enable-recursive-minibuffers t)) | 
|---|
| 334 | (completing-read | 
|---|
| 335 | (if v | 
|---|
| 336 | (format "Magic docstring for symbol (default %s): " v) | 
|---|
| 337 | "Magic docstring for symbol: ") | 
|---|
| 338 | obarray '(lambda (sym) | 
|---|
| 339 | (or (boundp sym) | 
|---|
| 340 | (fboundp sym) | 
|---|
| 341 | (find-face sym))) | 
|---|
| 342 | t nil 'variable-history)))) | 
|---|
| 343 | (list (if (equal val "") v (intern val))))) | 
|---|
| 344 | (insert "\n" texi-docstring-magic-comment " " (symbol-name symbol))) | 
|---|
| 345 |  | 
|---|
| 346 |  | 
|---|
| 347 | (provide 'texi-docstring-magic) | 
|---|