1 ;; texi-docstring-magic.el -- munge internal docstrings into texi
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>
8 ;; Id: texi-docstring-magic.el,v 1.2 2004/04/11 17:56:47 karl Exp
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.
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
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:
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.
32 ;; Write comments of the form:
34 ;; @c TEXI DOCSTRING MAGIC: my-package-function-or-variable-name
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.
40 ;; This will insert @defopt, @deffn and the like underneath the
41 ;; magic comment strings.
43 ;; The default value for user options will be printed.
45 ;; Symbols are recognized if they are defined for faces, functions,
46 ;; or variables (in that order).
48 ;; Automatic markup rules:
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}.
62 ;; Useful key binding when writing Texinfo:
64 ;; (define-key TeXinfo-mode-map "C-cC-d" 'texi-docstring-magic-insert-magic)
68 ;; Useful enhancements to do:
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
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.
80 (defun texi-docstring-magic-splice-sep (strings sep
)
81 "Return concatenation of STRINGS spliced together with separator SEP."
84 (setq str
(concat str
(car strings
)))
86 (setq str
(concat str sep
)))
87 (setq strings
(cdr strings
)))
90 (defconst texi-docstring-magic-munge-table
91 '(;; 1. Indented lines are gathered into @lisp environment.
95 ((line (match-string 0 docstring
)))
96 (if (eq (char-syntax (string-to-char line
)) ?\
)
100 (setq in-quoted-region t
)
101 (concat "@lisp\n" line
))
105 (setq in-quoted-region nil
)
106 (concat "@end lisp\n" 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
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 ("\\(`\\([^']+\\)'\\)"
118 (concat "@samp{" (match-string 2 docstring
) "}"))
119 ;; 3. Words *emphasized* are made @strong{emphasized}
120 ("\\(\\*\\(\\w+\\)\\*\\)"
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
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
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
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
)))
147 ;; 6. Words 'sym which are lisp quoted are
148 ;; marked with @code.
149 ("\\(\\(\\s-\\|^\\)'\\(\\(\\w\\|\\-\\)+\\)\\)\\(\\s\)\\|\\s-\\|\\s.\\|$\\)"
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
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.")
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
))
182 (< i
(length docstring
))
183 (string-match regexp docstring i
))
184 (setq i
(match-end 1))
186 (let* ((origlength (- (match-end 0) (match-beginning 0)))
187 (replacement (eval replace
))
188 (newlength (length replacement
)))
190 (replace-match replacement t t docstring
))
191 (setq i
(+ i
(- newlength origlength
))))))
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.
197 ((pos (string-match "\n" docstring
))
203 (if (and pos needscr
)
204 (concat (substring docstring
0 pos
)
206 (substring docstring
(1+ pos
)))
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
213 (texi-docstring-magic-splice-sep args
" ")
215 ;; (texi-docstring-magic-splice-sep extras " ")
217 (texi-docstring-magic-munge-docstring docstring args
)
220 "@end def" env
"\n"))
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
)))
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")))))
236 (defun texi-docstring-magic-texi-for (symbol)
242 (name (symbol-name face
))
243 (docstring (or (face-doc-string face
)
245 (useropt (eq ?
* (string-to-char docstring
))))
246 ;; Chop off user option setting
248 (setq docstring
(substring docstring
1)))
249 (texi-docstring-magic-texi "fn" "Face" name docstring nil
)))
252 ;; We don't handle macros, aliases, or compiled fns properly.
255 (name (symbol-name function
))
256 (docstring (or (documentation function
)
258 (def (symbol-function function
))
259 (argsyms (cond ((eq (car-safe def
) 'lambda
)
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
))))
269 (name (symbol-name variable
))
270 (docstring (or (documentation-property variable
271 'variable-documentation
)
273 (useropt (eq ?
* (string-to-char docstring
)))
275 (texi-docstring-magic-format-default
276 (default-value symbol
)))))
277 ;; Chop off user option setting
279 (setq docstring
(substring docstring
1)))
280 (texi-docstring-magic-texi
281 (if useropt
"opt" "var") nil name docstring nil default
)))
283 (error "Don't know anything about symbol %s" (symbol-name symbol
)))))
285 (defconst texi-docstring-magic-comment
286 "@c TEXI DOCSTRING MAGIC:"
287 "Magic string in a texi buffer expanded into @defopt, or @deffn.")
289 (defun texi-docstring-magic ()
290 "Update all texi docstring magic annotations in buffer."
293 (goto-char (point-min))
294 (let ((magic (concat "^"
295 (regexp-quote texi-docstring-magic-comment
)
296 "\\s-*\\(\\(\\w\\|\\-\\)+\\)$"))
299 (while (re-search-forward magic nil t
)
300 (setq symbol
(intern (match-string 1)))
303 ;; If comment already followed by an environment, delete it.
305 (looking-at "@def\\(\\w+\\)\\s-")
306 (search-forward (concat "@end def" (match-string 1)) nil t
))
309 (delete-region p
(point))))
311 (texi-docstring-magic-texi-for symbol
))))))
313 (defun texi-docstring-magic-face-at-point ()
315 (let ((stab (syntax-table)))
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))) ?_
)
323 (skip-chars-forward "'")
324 (let ((obj (read (current-buffer))))
325 (and (symbolp obj
) (find-face obj
) obj
)))
326 (set-syntax-table stab
)))))
328 (defun texi-docstring-magic-insert-magic (symbol)
330 (let* ((v (or (variable-at-point)
332 (texi-docstring-magic-face-at-point)))
333 (val (let ((enable-recursive-minibuffers t
))
336 (format "Magic docstring for symbol (default %s): " v
)
337 "Magic docstring for symbol: ")
338 obarray
'(lambda (sym)
342 t nil
'variable-history
))))
343 (list (if (equal val
"") v
(intern val
)))))
344 (insert "\n" texi-docstring-magic-comment
" " (symbol-name symbol
)))
347 (provide 'texi-docstring-magic
)