]> code.delx.au - gnu-emacs/blob - lisp/mail/reporter.el
(rmail-ignored-headers): Remove extra paren.
[gnu-emacs] / lisp / mail / reporter.el
1 ;;; reporter.el --- customizable bug reporting of lisp programs
2
3 ;; Copyright (C) 1993 1994 1995 1996 Free Software Foundation, Inc.
4
5 ;; Author: 1993-1996 Barry A. Warsaw
6 ;; Created: 19-Apr-1993
7 ;; Version: 3.3
8 ;; Last Modified: 1996/07/02 00:39:09
9 ;; Keywords: maint mail tools
10
11 ;; This file is part of GNU Emacs.
12
13 ;; GNU Emacs is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 2, or (at your option)
16 ;; any later version.
17
18 ;; GNU Emacs is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
22
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs; see the file COPYING. If not, write to the
25 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26 ;; Boston, MA 02111-1307, USA.
27
28 ;;; Commentary:
29
30 ;; End User Interface
31 ;; ==================
32 ;; The variable `mail-user-agent' contains a symbol indicating which
33 ;; Emacs mail package end users would like to use to compose outgoing
34 ;; mail. See that variable for details.
35
36 ;; Mail Package Interface
37 ;; ======================
38 ;; Mail package authors can configure reporter to support their
39 ;; package by calling the function `define-mail-user-agent' See that
40 ;; function for details.
41
42 ;; Lisp Package Authors
43 ;; ====================
44 ;; Reporter was written primarily for Emacs Lisp package authors so
45 ;; that their users can easily report bugs. When invoked,
46 ;; reporter-submit-bug-report will set up an outgoing mail buffer with
47 ;; the appropriate bug report address, including a lisp expression the
48 ;; maintainer of the package can eval to completely reproduce the
49 ;; environment in which the bug was observed (e.g. by using
50 ;; eval-last-sexp). This package proved especially useful during my
51 ;; development of cc-mode, which is highly dependent on its
52 ;; configuration variables.
53 ;;
54 ;; Do a "C-h f reporter-submit-bug-report" for more information.
55 ;; Here's an example usage:
56 ;;
57 ;;(defconst mypkg-version "9.801")
58 ;;(defconst mypkg-maintainer-address "mypkg-help@foo.com")
59 ;;(defun mypkg-submit-bug-report ()
60 ;; "Submit via mail a bug report on mypkg"
61 ;; (interactive)
62 ;; (reporter-submit-bug-report
63 ;; mypkg-maintainer-address
64 ;; (concat "mypkg.el " mypkg-version)
65 ;; (list 'mypkg-variable-1
66 ;; 'mypkg-variable-2
67 ;; ;; ...
68 ;; 'mypkg-variable-last)))
69
70 ;; Mailing List
71 ;; ============
72 ;; I've set up a Majordomo mailing list to report bugs or suggest
73 ;; enhancements, etc. This list's intended audience is elisp package
74 ;; authors who are using reporter and want to stay current with
75 ;; releases. Here are the relevant addresses:
76 ;;
77 ;; Administrivia: reporter-request@python.org
78 ;; Submissions: reporter@python.org
79
80 ;; Packages that currently use reporter are: cc-mode, supercite, elp,
81 ;; tcl, ediff, crypt++ (crypt), dired-x, rmailgen, mode-line, vm,
82 ;; mh-e, edebug, archie, viper, w3-mode, framepop, hl319, hilit19,
83 ;; pgp, eos, hm--html, efs.
84 ;;
85 ;; If you know of others, please email me!
86
87 ;;; Code:
88
89 \f
90 ;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
91 ;; End user interface
92
93 ;;;###autoload
94 (defvar mail-user-agent 'sendmail-user-agent
95 "*Your preference for a mail composition package.
96 Various Emacs Lisp packages (e.g. reporter) require you to compose an
97 outgoing email message. As there are several such packages available
98 for Emacs, you can indicate your preference by setting this variable.
99
100 Valid values currently are:
101
102 'sendmail-user-agent -- use Emacs built-in Mail package
103 'vm-user-agent -- use Kyle Jones' VM package
104 'mh-e-user-agent -- use the Emacs interface to the MH mail system
105
106 Additional valid symbols may be available; check with the author of
107 your package for details.")
108
109
110 \f
111 ;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
112 ;; Package author interface variables
113
114 (defvar reporter-prompt-for-summary-p nil
115 "Interface variable controlling prompting for problem summary.
116 When non-nil, `reporter-submit-bug-report' prompts the user for a
117 brief summary of the problem, and puts this summary on the Subject:
118 line. If this variable is a string, that string is used as the prompt
119 string.
120
121 Default behavior is to not prompt (i.e. nil). If you want reporter to
122 prompt, you should `let' bind this variable before calling
123 `reporter-submit-bug-report'. Note that this variable is not
124 buffer-local so you should never just `setq' it.")
125
126 (defvar reporter-dont-compact-list nil
127 "Interface variable controlling compacting of list values.
128 When non-nil, this must be a list of variable symbols. When a
129 variable containing a list value is formatted in the bug report mail
130 buffer, it normally is compacted so that its value fits one the fewest
131 number of lines. If the variable's symbol appears in this list, its
132 value is printed in a more verbose style, specifically, one elemental
133 sexp per line.
134
135 Note that this variable is not buffer-local so you should never just
136 `setq' it. If you want to changes its default value, you should `let'
137 bind it.")
138
139 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
140 ;; End of editable variables
141
142 \f
143 (defvar reporter-eval-buffer nil
144 "Buffer to retrieve variable's value from.
145 This is necessary to properly support the printing of buffer-local
146 variables. Current buffer will always be the mail buffer being
147 composed.")
148
149 (defconst reporter-version "3.2"
150 "Reporter version number.")
151
152 (defvar reporter-initial-text nil
153 "The automatically created initial text of a bug report.")
154 (make-variable-buffer-local 'reporter-initial-text)
155
156
157 \f
158 ;; status feedback to the user
159 (defvar reporter-status-message nil)
160 (defvar reporter-status-count nil)
161
162 (defun reporter-update-status ()
163 ;; periodically output a status message
164 (if (zerop (% reporter-status-count 10))
165 (progn
166 (message reporter-status-message)
167 (setq reporter-status-message (concat reporter-status-message "."))))
168 (setq reporter-status-count (1+ reporter-status-count)))
169
170 \f
171 ;; dumping/pretty printing of values
172 (defun reporter-beautify-list (maxwidth compact-p)
173 ;; pretty print a list
174 (reporter-update-status)
175 (let (linebreak indent-enclosing-p indent-p here)
176 (condition-case nil ;loop exit
177 (progn
178 (down-list 1)
179 (setq indent-enclosing-p t)
180 (while t
181 (setq here (point))
182 (forward-sexp 1)
183 (if (<= maxwidth (current-column))
184 (if linebreak
185 (progn
186 (goto-char linebreak)
187 (newline-and-indent)
188 (setq linebreak nil))
189 (goto-char here)
190 (setq indent-p (reporter-beautify-list maxwidth compact-p))
191 (goto-char here)
192 (forward-sexp 1)
193 (if indent-p
194 (newline-and-indent))
195 t)
196 (if compact-p
197 (setq linebreak (point))
198 (newline-and-indent))
199 ))
200 t)
201 (error indent-enclosing-p))))
202
203 (defun reporter-lisp-indent (indent-point state)
204 ;; a better lisp indentation style for bug reporting
205 (save-excursion
206 (goto-char (1+ (nth 1 state)))
207 (current-column)))
208
209 (defun reporter-dump-variable (varsym mailbuf)
210 ;; Pretty-print the value of the variable in symbol VARSYM. MAILBUF
211 ;; is the mail buffer being composed
212 (reporter-update-status)
213 (condition-case nil
214 (let ((val (save-excursion
215 (set-buffer reporter-eval-buffer)
216 (symbol-value varsym)))
217 (sym (symbol-name varsym))
218 (print-escape-newlines t)
219 (maxwidth (1- (window-width)))
220 (here (point)))
221 (insert " " sym " "
222 (cond
223 ((memq val '(t nil)) "")
224 ((listp val) "'")
225 ((symbolp val) "'")
226 (t ""))
227 (prin1-to-string val))
228 (lisp-indent-line)
229 ;; clean up lists, but only if the line as printed was long
230 ;; enough to wrap
231 (if (and val ;nil is a list, but short
232 (listp val)
233 (<= maxwidth (current-column)))
234 (save-excursion
235 (let ((compact-p (not (memq varsym reporter-dont-compact-list)))
236 (lisp-indent-function 'reporter-lisp-indent))
237 (goto-char here)
238 (reporter-beautify-list maxwidth compact-p))))
239 (insert "\n"))
240 (void-variable
241 (save-excursion
242 (set-buffer mailbuf)
243 (mail-position-on-field "X-Reporter-Void-Vars-Found")
244 (end-of-line)
245 (insert (symbol-name varsym) " ")))
246 (error
247 (error ""))))
248
249 (defun reporter-dump-state (pkgname varlist pre-hooks post-hooks)
250 ;; Dump the state of the mode specific variables.
251 ;; PKGNAME contains the name of the mode as it will appear in the bug
252 ;; report (you must explicitly concat any version numbers).
253
254 ;; VARLIST is the list of variables to dump. Each element in
255 ;; VARLIST can be a variable symbol, or a cons cell. If a symbol,
256 ;; this will be passed to `reporter-dump-variable' for insertion
257 ;; into the mail buffer. If a cons cell, the car must be a variable
258 ;; symbol and the cdr must be a function which will be `funcall'd
259 ;; with arguments the symbol and the mail buffer being composed. Use
260 ;; this to write your own custom variable value printers for
261 ;; specific variables.
262
263 ;; Note that the global variable `reporter-eval-buffer' will be bound to
264 ;; the buffer in which `reporter-submit-bug-report' was invoked. If you
265 ;; want to print the value of a buffer local variable, you should wrap
266 ;; the `eval' call in your custom printer inside a `set-buffer' (and
267 ;; probably a `save-excursion'). `reporter-dump-variable' handles this
268 ;; properly.
269
270 ;; PRE-HOOKS is run after the emacs-version and PKGNAME are inserted, but
271 ;; before the VARLIST is dumped. POST-HOOKS is run after the VARLIST is
272 ;; dumped.
273 (let ((buffer (current-buffer)))
274 (set-buffer buffer)
275 (insert "Emacs : " (emacs-version) "\n")
276 (and pkgname
277 (insert "Package: " pkgname "\n"))
278 (run-hooks 'pre-hooks)
279 (if (not varlist)
280 nil
281 (insert "\ncurrent state:\n==============\n")
282 ;; create an emacs-lisp-mode buffer to contain the output, which
283 ;; we'll later insert into the mail buffer
284 (condition-case fault
285 (let ((mailbuf (current-buffer))
286 (elbuf (get-buffer-create " *tmp-reporter-buffer*")))
287 (save-excursion
288 (set-buffer elbuf)
289 (emacs-lisp-mode)
290 (erase-buffer)
291 (insert "(setq\n")
292 (lisp-indent-line)
293 (mapcar
294 (function
295 (lambda (varsym-or-cons-cell)
296 (let ((varsym (or (car-safe varsym-or-cons-cell)
297 varsym-or-cons-cell))
298 (printer (or (cdr-safe varsym-or-cons-cell)
299 'reporter-dump-variable)))
300 (funcall printer varsym mailbuf)
301 )))
302 varlist)
303 (lisp-indent-line)
304 (insert ")\n"))
305 (insert-buffer elbuf))
306 (error
307 (insert "State could not be dumped due to the following error:\n\n"
308 (format "%s" fault)
309 "\n\nYou should still send this bug report."))))
310 (run-hooks 'post-hooks)
311 ))
312
313 \f
314 (defun reporter-calculate-separator ()
315 ;; returns the string regexp matching the mail separator
316 (save-excursion
317 (re-search-forward
318 (concat
319 "^\\(" ;beginning of line
320 (mapconcat
321 'identity
322 (list "[\t ]*" ;simple SMTP form
323 "-+" ;mh-e form
324 (regexp-quote
325 mail-header-separator)) ;sendmail.el form
326 "\\|") ;or them together
327 "\\)$") ;end of line
328 nil
329 'move) ;search for and move
330 (buffer-substring (match-beginning 0) (match-end 0))))
331
332 \f
333 ;; Serves as an interface to `mail' (sendmail.el), but when the user
334 ;; answers "no" to discarding an unsent message, it gives an error.
335 (defun reporter-mail (&rest args)
336 (or (apply 'mail args)
337 (error "Bug report aborted")))
338
339 (defun reporter-compose-outgoing ()
340 ;; compose the outgoing mail buffer, and return the selected
341 ;; paradigm, with the current-buffer tacked onto the beginning of
342 ;; the list.
343 (let* ((agent mail-user-agent)
344 (compose (get mail-user-agent 'composefunc)))
345 ;; Sanity check. If this fails then we'll try to use the SENDMAIL
346 ;; protocol, otherwise we must signal an error.
347 (if (not (and compose (fboundp compose)))
348 (progn
349 (setq agent 'sendmail-user-agent
350 compose (get agent 'composefunc))
351 (if (not (and compose (fboundp compose)))
352 (error "Could not find a valid `mail-user-agent'.")
353 (ding)
354 (message "`%s' is an invalid `mail-user-agent'; using `sendmail-user-agent'."
355 mail-user-agent)
356 )))
357 (funcall compose)
358 agent))
359
360 \f
361 ;;;###autoload
362 (defun reporter-submit-bug-report
363 (address pkgname varlist &optional pre-hooks post-hooks salutation)
364 ;; Submit a bug report via mail.
365
366 ;; ADDRESS is the email address for the package's maintainer. PKGNAME is
367 ;; the name of the mode (you must explicitly concat any version numbers).
368 ;; VARLIST is the list of variables to dump (see `reporter-dump-state'
369 ;; for details). Optional PRE-HOOKS and POST-HOOKS are passed to
370 ;; `reporter-dump-state'. Optional SALUTATION is inserted at the top of the
371 ;; mail buffer, and point is left after the salutation.
372
373 ;; This function will prompt for a summary if
374 ;; reporter-prompt-for-summary-p is non-nil.
375
376 ;; The mailer used is described in by the variable `mail-user-agent'.
377 (let ((reporter-eval-buffer (current-buffer))
378 final-resting-place
379 after-sep-pos
380 (reporter-status-message "Formatting bug report buffer...")
381 (reporter-status-count 0)
382 (problem (and reporter-prompt-for-summary-p
383 (read-string (if (stringp reporter-prompt-for-summary-p)
384 reporter-prompt-for-summary-p
385 "(Very) brief summary of problem: "))))
386 (agent (reporter-compose-outgoing))
387 (mailbuf (current-buffer))
388 hookvar)
389 ;; do the work
390 (require 'sendmail)
391 ;; If mailbuf did not get made visible before, make it visible now.
392 (let (same-window-buffer-names same-window-regexps)
393 (pop-to-buffer mailbuf)
394 ;; Just in case the original buffer is not visible now, bring it
395 ;; back somewhere
396 (display-buffer reporter-eval-buffer))
397 (goto-char (point-min))
398 ;; different mailers use different separators, some may not even
399 ;; use mail-header-separator, but sendmail.el stuff must have this
400 ;; variable bound.
401 (let ((mail-header-separator (reporter-calculate-separator)))
402 (mail-position-on-field "to")
403 (insert address)
404 ;; insert problem summary if available
405 (if (and reporter-prompt-for-summary-p problem pkgname)
406 (progn
407 (mail-position-on-field "subject")
408 (insert pkgname "; " problem)))
409 ;; move point to the body of the message
410 (mail-text)
411 (forward-line 1)
412 (setq after-sep-pos (point))
413 (and salutation (insert "\n" salutation "\n\n"))
414 (unwind-protect
415 (progn
416 (setq final-resting-place (point-marker))
417 (insert "\n\n")
418 (reporter-dump-state pkgname varlist pre-hooks post-hooks)
419 (goto-char final-resting-place))
420 (set-marker final-resting-place nil)))
421
422 ;; save initial text and set up the `no-empty-submission' hook.
423 ;; This only works for mailers that support a pre-send hook, and
424 ;; for which the paradigm has a non-nil value for the `hookvar'
425 ;; key in its agent (i.e. sendmail.el's mail-send-hook).
426 (save-excursion
427 (goto-char (point-max))
428 (skip-chars-backward " \t\n")
429 (setq reporter-initial-text (buffer-substring after-sep-pos (point))))
430 (if (setq hookvar (get agent 'hookvar))
431 (progn
432 (make-variable-buffer-local hookvar)
433 (add-hook hookvar 'reporter-bug-hook)))
434
435 ;; compose the minibuf message and display this.
436 (let* ((sendkey-whereis (where-is-internal
437 (get agent 'sendfunc) nil t))
438 (abortkey-whereis (where-is-internal
439 (get agent 'abortfunc) nil t))
440 (sendkey (if sendkey-whereis
441 (key-description sendkey-whereis)
442 "C-c C-c")) ; TBD: BOGUS hardcode
443 (abortkey (if abortkey-whereis
444 (key-description abortkey-whereis)
445 "M-x kill-buffer")) ; TBD: BOGUS hardcode
446 )
447 (message "Please enter your report. Type %s to send, %s to abort."
448 sendkey abortkey))
449 ))
450
451 (defun reporter-bug-hook ()
452 ;; prohibit sending mail if empty bug report
453 (let ((after-sep-pos
454 (save-excursion
455 (beginning-of-buffer)
456 (re-search-forward (reporter-calculate-separator) (point-max) 'move)
457 (forward-line 1)
458 (point))))
459 (save-excursion
460 (goto-char (point-max))
461 (skip-chars-backward " \t\n")
462 (if (and (= (- (point) after-sep-pos)
463 (length reporter-initial-text))
464 (string= (buffer-substring after-sep-pos (point))
465 reporter-initial-text))
466 (error "Empty bug report cannot be sent."))
467 )))
468
469 \f
470 ;; paradigm definitions
471 (defun define-mail-user-agent (symbol composefunc sendfunc
472 &optional abortfunc hookvar)
473 "Define a symbol appropriate for `mail-user-agent'.
474
475 SYMBOL can be any meaningful lisp symbol. It need not have a function
476 or variable definition, as it is only used for its property list.
477 The property names are equivalent to the formal argument described
478 below (but in lower case). Additional properties can be placed on the
479 symbol.
480
481 COMPOSEFUNC is program callable function that composes an outgoing
482 mail message buffer. This function should set up the basics of the
483 buffer without requiring user interaction. It should populate the
484 standard mail headers, leaving the `to:' and `subject:' headers blank.
485
486 SENDFUNC is the command a user would type to send the message.
487
488 Optional ABORTFUNC is the command a user would type to abort the
489 message. For mail packages that don't have a separate abort function,
490 this can be `kill-buffer' (the equivalent of omitting this argument).
491
492 Optional HOOKVAR is a hook variable that gets run before the message
493 is actually sent. Reporter will install `reporter-bug-hook' onto this
494 hook so that empty bug reports can be suppressed by raising an error.
495 If not supplied, `mail-send-hook' will be used."
496 (put symbol 'composefunc composefunc)
497 (put symbol 'sendfunc sendfunc)
498 (put symbol 'abortfunc (or abortfunc 'kill-buffer))
499 (put symbol 'hookvar (or hookvar 'mail-send-hook)))
500
501 (define-mail-user-agent 'sendmail-user-agent
502 'reporter-mail 'mail-send-and-exit)
503
504 (define-mail-user-agent 'vm-user-agent
505 'vm-mail 'vm-mail-send-and-exit)
506
507 (define-mail-user-agent 'mh-e-user-agent
508 'mh-smail-batch 'mh-send-letter 'mh-fully-kill-draft
509 'mh-before-send-letter-hook)
510
511 \f
512 (provide 'reporter)
513 ;;; reporter.el ends here