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)
|
---|