1 ;;; reporter.el --- customizable bug reporting of lisp programs
3 ;; Copyright (C) 1993 1994 1995 1996 Free Software Foundation, Inc.
5 ;; Author: 1993-1996 Barry A. Warsaw
6 ;; Created: 19-Apr-1993
8 ;; Last Modified: 1996/07/02 00:39:09
9 ;; Keywords: maint mail tools
11 ;; This file is part of GNU Emacs.
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)
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.
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.
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.
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.
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.
54 ;; Do a "C-h f reporter-submit-bug-report" for more information.
55 ;; Here's an example usage:
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"
62 ;; (reporter-submit-bug-report
63 ;; mypkg-maintainer-address
64 ;; (concat "mypkg.el " mypkg-version)
65 ;; (list 'mypkg-variable-1
68 ;; 'mypkg-variable-last)))
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:
77 ;; Administrivia: reporter-request@python.org
78 ;; Submissions: reporter@python.org
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.
85 ;; If you know of others, please email me!
90 ;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
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.
100 Valid values currently are:
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
106 Additional valid symbols may be available; check with the author of
107 your package for details.")
111 ;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
112 ;; Package author interface variables
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
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.")
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
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'
139 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
140 ;; End of editable variables
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
149 (defconst reporter-version "3.2"
150 "Reporter version number.")
152 (defvar reporter-initial-text nil
153 "The automatically created initial text of a bug report.")
154 (make-variable-buffer-local 'reporter-initial-text)
158 ;; status feedback to the user
159 (defvar reporter-status-message nil)
160 (defvar reporter-status-count nil)
162 (defun reporter-update-status ()
163 ;; periodically output a status message
164 (if (zerop (% reporter-status-count 10))
166 (message reporter-status-message)
167 (setq reporter-status-message (concat reporter-status-message "."))))
168 (setq reporter-status-count (1+ reporter-status-count)))
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
179 (setq indent-enclosing-p t)
183 (if (<= maxwidth (current-column))
186 (goto-char linebreak)
188 (setq linebreak nil))
190 (setq indent-p (reporter-beautify-list maxwidth compact-p))
194 (newline-and-indent))
197 (setq linebreak (point))
198 (newline-and-indent))
201 (error indent-enclosing-p))))
203 (defun reporter-lisp-indent (indent-point state)
204 ;; a better lisp indentation style for bug reporting
206 (goto-char (1+ (nth 1 state)))
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)
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)))
223 ((memq val '(t nil)) "")
227 (prin1-to-string val))
229 ;; clean up lists, but only if the line as printed was long
231 (if (and val ;nil is a list, but short
233 (<= maxwidth (current-column)))
235 (let ((compact-p (not (memq varsym reporter-dont-compact-list)))
236 (lisp-indent-function 'reporter-lisp-indent))
238 (reporter-beautify-list maxwidth compact-p))))
243 (mail-position-on-field "X-Reporter-Void-Vars-Found")
245 (insert (symbol-name varsym) " ")))
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).
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.
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
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
273 (let ((buffer (current-buffer)))
275 (insert "Emacs : " (emacs-version) "\n")
277 (insert "Package: " pkgname "\n"))
278 (run-hooks 'pre-hooks)
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*")))
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)
305 (insert-buffer elbuf))
307 (insert "State could not be dumped due to the following error:\n\n"
309 "\n\nYou should still send this bug report."))))
310 (run-hooks 'post-hooks)
314 (defun reporter-calculate-separator ()
315 ;; returns the string regexp matching the mail separator
319 "^\\(" ;beginning of line
322 (list "[\t ]*" ;simple SMTP form
325 mail-header-separator)) ;sendmail.el form
326 "\\|") ;or them together
329 'move) ;search for and move
330 (buffer-substring (match-beginning 0) (match-end 0))))
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")))
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
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)))
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'.")
354 (message "`%s' is an invalid `mail-user-agent'; using `sendmail-user-agent'."
362 (defun reporter-submit-bug-report
363 (address pkgname varlist &optional pre-hooks post-hooks salutation)
364 ;; Submit a bug report via mail.
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.
373 ;; This function will prompt for a summary if
374 ;; reporter-prompt-for-summary-p is non-nil.
376 ;; The mailer used is described in by the variable `mail-user-agent'.
377 (let ((reporter-eval-buffer (current-buffer))
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))
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
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
401 (let ((mail-header-separator (reporter-calculate-separator)))
402 (mail-position-on-field "to")
404 ;; insert problem summary if available
405 (if (and reporter-prompt-for-summary-p problem pkgname)
407 (mail-position-on-field "subject")
408 (insert pkgname "; " problem)))
409 ;; move point to the body of the message
412 (setq after-sep-pos (point))
413 (and salutation (insert "\n" salutation "\n\n"))
416 (setq final-resting-place (point-marker))
418 (reporter-dump-state pkgname varlist pre-hooks post-hooks)
419 (goto-char final-resting-place))
420 (set-marker final-resting-place nil)))
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).
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))
432 (make-variable-buffer-local hookvar)
433 (add-hook hookvar 'reporter-bug-hook)))
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
447 (message "Please enter your report. Type %s to send, %s to abort."
451 (defun reporter-bug-hook ()
452 ;; prohibit sending mail if empty bug report
455 (beginning-of-buffer)
456 (re-search-forward (reporter-calculate-separator) (point-max) 'move)
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."))
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'.
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
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.
486 SENDFUNC is the command a user would type to send the message.
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).
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)))
501 (define-mail-user-agent 'sendmail-user-agent
502 'reporter-mail 'mail-send-and-exit)
504 (define-mail-user-agent 'vm-user-agent
505 'vm-mail 'vm-mail-send-and-exit)
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)
513 ;;; reporter.el ends here