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 ;; Lisp Package Authors
37 ;; ====================
38 ;; Reporter was written primarily for Emacs Lisp package authors so
39 ;; that their users can easily report bugs. When invoked,
40 ;; reporter-submit-bug-report will set up an outgoing mail buffer with
41 ;; the appropriate bug report address, including a lisp expression the
42 ;; maintainer of the package can eval to completely reproduce the
43 ;; environment in which the bug was observed (e.g. by using
44 ;; eval-last-sexp). This package proved especially useful during my
45 ;; development of cc-mode, which is highly dependent on its
46 ;; configuration variables.
48 ;; Do a "C-h f reporter-submit-bug-report" for more information.
49 ;; Here's an example usage:
51 ;;(defconst mypkg-version "9.801")
52 ;;(defconst mypkg-maintainer-address "mypkg-help@foo.com")
53 ;;(defun mypkg-submit-bug-report ()
54 ;; "Submit via mail a bug report on mypkg"
56 ;; (reporter-submit-bug-report
57 ;; mypkg-maintainer-address
58 ;; (concat "mypkg.el " mypkg-version)
59 ;; (list 'mypkg-variable-1
62 ;; 'mypkg-variable-last)))
66 ;; I've set up a Majordomo mailing list to report bugs or suggest
67 ;; enhancements, etc. This list's intended audience is elisp package
68 ;; authors who are using reporter and want to stay current with
69 ;; releases. Here are the relevant addresses:
71 ;; Administrivia: reporter-request@python.org
72 ;; Submissions: reporter@python.org
74 ;; Packages that currently use reporter are: cc-mode, supercite, elp,
75 ;; tcl, ediff, crypt++ (crypt), dired-x, rmailgen, mode-line, vm,
76 ;; mh-e, edebug, archie, viper, w3-mode, framepop, hl319, hilit19,
77 ;; pgp, eos, hm--html, efs.
79 ;; If you know of others, please email me!
83 ;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
84 ;; Package author interface variables
86 (defvar reporter-prompt-for-summary-p nil
87 "Interface variable controlling prompting for problem summary.
88 When non-nil, `reporter-submit-bug-report' prompts the user for a
89 brief summary of the problem, and puts this summary on the Subject:
90 line. If this variable is a string, that string is used as the prompt
93 Default behavior is to not prompt (i.e. nil). If you want reporter to
94 prompt, you should `let' bind this variable before calling
95 `reporter-submit-bug-report'. Note that this variable is not
96 buffer-local so you should never just `setq' it.")
98 (defvar reporter-dont-compact-list nil
99 "Interface variable controlling compacting of list values.
100 When non-nil, this must be a list of variable symbols. When a
101 variable containing a list value is formatted in the bug report mail
102 buffer, it normally is compacted so that its value fits one the fewest
103 number of lines. If the variable's symbol appears in this list, its
104 value is printed in a more verbose style, specifically, one elemental
107 Note that this variable is not buffer-local so you should never just
108 `setq' it. If you want to changes its default value, you should `let'
111 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
112 ;; End of editable variables
115 (defvar reporter-eval-buffer nil
116 "Buffer to retrieve variable's value from.
117 This is necessary to properly support the printing of buffer-local
118 variables. Current buffer will always be the mail buffer being
121 (defconst reporter-version
"3.2"
122 "Reporter version number.")
124 (defvar reporter-initial-text nil
125 "The automatically created initial text of a bug report.")
126 (make-variable-buffer-local 'reporter-initial-text
)
130 ;; status feedback to the user
131 (defvar reporter-status-message nil
)
132 (defvar reporter-status-count nil
)
134 (defun reporter-update-status ()
135 ;; periodically output a status message
136 (if (zerop (% reporter-status-count
10))
138 (message reporter-status-message
)
139 (setq reporter-status-message
(concat reporter-status-message
"."))))
140 (setq reporter-status-count
(1+ reporter-status-count
)))
143 ;; dumping/pretty printing of values
144 (defun reporter-beautify-list (maxwidth compact-p
)
145 ;; pretty print a list
146 (reporter-update-status)
147 (let (linebreak indent-enclosing-p indent-p here
)
148 (condition-case nil
;loop exit
151 (setq indent-enclosing-p t
)
155 (if (<= maxwidth
(current-column))
158 (goto-char linebreak
)
160 (setq linebreak nil
))
162 (setq indent-p
(reporter-beautify-list maxwidth compact-p
))
166 (newline-and-indent))
169 (setq linebreak
(point))
170 (newline-and-indent))
173 (error indent-enclosing-p
))))
175 (defun reporter-lisp-indent (indent-point state
)
176 ;; a better lisp indentation style for bug reporting
178 (goto-char (1+ (nth 1 state
)))
181 (defun reporter-dump-variable (varsym mailbuf
)
182 ;; Pretty-print the value of the variable in symbol VARSYM. MAILBUF
183 ;; is the mail buffer being composed
184 (reporter-update-status)
186 (let ((val (save-excursion
187 (set-buffer reporter-eval-buffer
)
188 (symbol-value varsym
)))
189 (sym (symbol-name varsym
))
190 (print-escape-newlines t
)
191 (maxwidth (1- (window-width)))
195 ((memq val
'(t nil
)) "")
199 (prin1-to-string val
))
201 ;; clean up lists, but only if the line as printed was long
203 (if (and val
;nil is a list, but short
205 (<= maxwidth
(current-column)))
207 (let ((compact-p (not (memq varsym reporter-dont-compact-list
)))
208 (lisp-indent-function 'reporter-lisp-indent
))
210 (reporter-beautify-list maxwidth compact-p
))))
215 (mail-position-on-field "X-Reporter-Void-Vars-Found")
217 (insert (symbol-name varsym
) " ")))
221 (defun reporter-dump-state (pkgname varlist pre-hooks post-hooks
)
222 ;; Dump the state of the mode specific variables.
223 ;; PKGNAME contains the name of the mode as it will appear in the bug
224 ;; report (you must explicitly concat any version numbers).
226 ;; VARLIST is the list of variables to dump. Each element in
227 ;; VARLIST can be a variable symbol, or a cons cell. If a symbol,
228 ;; this will be passed to `reporter-dump-variable' for insertion
229 ;; into the mail buffer. If a cons cell, the car must be a variable
230 ;; symbol and the cdr must be a function which will be `funcall'd
231 ;; with arguments the symbol and the mail buffer being composed. Use
232 ;; this to write your own custom variable value printers for
233 ;; specific variables.
235 ;; Note that the global variable `reporter-eval-buffer' will be bound to
236 ;; the buffer in which `reporter-submit-bug-report' was invoked. If you
237 ;; want to print the value of a buffer local variable, you should wrap
238 ;; the `eval' call in your custom printer inside a `set-buffer' (and
239 ;; probably a `save-excursion'). `reporter-dump-variable' handles this
242 ;; PRE-HOOKS is run after the emacs-version and PKGNAME are inserted, but
243 ;; before the VARLIST is dumped. POST-HOOKS is run after the VARLIST is
245 (let ((buffer (current-buffer)))
247 (insert "Emacs : " (emacs-version) "\n")
249 (insert "Package: " pkgname
"\n"))
250 (run-hooks 'pre-hooks
)
253 (insert "\ncurrent state:\n==============\n")
254 ;; create an emacs-lisp-mode buffer to contain the output, which
255 ;; we'll later insert into the mail buffer
256 (condition-case fault
257 (let ((mailbuf (current-buffer))
258 (elbuf (get-buffer-create " *tmp-reporter-buffer*")))
267 (lambda (varsym-or-cons-cell)
268 (let ((varsym (or (car-safe varsym-or-cons-cell
)
269 varsym-or-cons-cell
))
270 (printer (or (cdr-safe varsym-or-cons-cell
)
271 'reporter-dump-variable
)))
272 (funcall printer varsym mailbuf
)
277 (insert-buffer elbuf
))
279 (insert "State could not be dumped due to the following error:\n\n"
281 "\n\nYou should still send this bug report."))))
282 (run-hooks 'post-hooks
)
286 (defun reporter-calculate-separator ()
287 ;; returns the string regexp matching the mail separator
291 "^\\(" ;beginning of line
294 (list "[\t ]*" ;simple SMTP form
297 mail-header-separator
)) ;sendmail.el form
298 "\\|") ;or them together
301 'move
) ;search for and move
302 (buffer-substring (match-beginning 0) (match-end 0))))
305 (defun reporter-compose-outgoing ()
306 ;; compose the outgoing mail buffer, and return the selected
307 ;; paradigm, with the current-buffer tacked onto the beginning of
309 (let* ((agent mail-user-agent
)
310 (compose (get mail-user-agent
'composefunc
)))
311 ;; Sanity check. If this fails then we'll try to use the SENDMAIL
312 ;; protocol, otherwise we must signal an error.
313 (if (not (and compose
(fboundp compose
)))
315 (setq agent
'sendmail-user-agent
316 compose
(get agent
'composefunc
))
317 (if (not (and compose
(fboundp compose
)))
318 (error "Could not find a valid `mail-user-agent'")
320 (message "`%s' is an invalid `mail-user-agent'; using `sendmail-user-agent'"
328 (defun reporter-submit-bug-report
329 (address pkgname varlist
&optional pre-hooks post-hooks salutation
)
330 ;; Submit a bug report via mail.
332 ;; ADDRESS is the email address for the package's maintainer. PKGNAME is
333 ;; the name of the mode (you must explicitly concat any version numbers).
334 ;; VARLIST is the list of variables to dump (see `reporter-dump-state'
335 ;; for details). Optional PRE-HOOKS and POST-HOOKS are passed to
336 ;; `reporter-dump-state'. Optional SALUTATION is inserted at the top of the
337 ;; mail buffer, and point is left after the salutation.
339 ;; This function will prompt for a summary if
340 ;; reporter-prompt-for-summary-p is non-nil.
342 ;; The mailer used is described in by the variable `mail-user-agent'.
343 (let ((reporter-eval-buffer (current-buffer))
346 (reporter-status-message "Formatting bug report buffer...")
347 (reporter-status-count 0)
348 (problem (and reporter-prompt-for-summary-p
349 (read-string (if (stringp reporter-prompt-for-summary-p
)
350 reporter-prompt-for-summary-p
351 "(Very) brief summary of problem: "))))
352 (agent (reporter-compose-outgoing))
353 (mailbuf (current-buffer))
357 ;; If mailbuf did not get made visible before, make it visible now.
358 (let (same-window-buffer-names same-window-regexps
)
359 (pop-to-buffer mailbuf
)
360 ;; Just in case the original buffer is not visible now, bring it
362 (display-buffer reporter-eval-buffer
))
363 (goto-char (point-min))
364 ;; different mailers use different separators, some may not even
365 ;; use mail-header-separator, but sendmail.el stuff must have this
367 (let ((mail-header-separator (reporter-calculate-separator)))
368 (mail-position-on-field "to")
370 ;; insert problem summary if available
371 (if (and reporter-prompt-for-summary-p problem pkgname
)
373 (mail-position-on-field "subject")
374 (insert pkgname
"; " problem
)))
375 ;; move point to the body of the message
378 (setq after-sep-pos
(point))
379 (and salutation
(insert "\n" salutation
"\n\n"))
382 (setq final-resting-place
(point-marker))
384 (reporter-dump-state pkgname varlist pre-hooks post-hooks
)
385 (goto-char final-resting-place
))
386 (set-marker final-resting-place nil
)))
388 ;; save initial text and set up the `no-empty-submission' hook.
389 ;; This only works for mailers that support a pre-send hook, and
390 ;; for which the paradigm has a non-nil value for the `hookvar'
391 ;; key in its agent (i.e. sendmail.el's mail-send-hook).
393 (goto-char (point-max))
394 (skip-chars-backward " \t\n")
395 (setq reporter-initial-text
(buffer-substring after-sep-pos
(point))))
396 (if (setq hookvar
(get agent
'hookvar
))
398 (make-variable-buffer-local hookvar
)
399 (add-hook hookvar
'reporter-bug-hook
)))
401 ;; compose the minibuf message and display this.
402 (let* ((sendkey-whereis (where-is-internal
403 (get agent
'sendfunc
) nil t
))
404 (abortkey-whereis (where-is-internal
405 (get agent
'abortfunc
) nil t
))
406 (sendkey (if sendkey-whereis
407 (key-description sendkey-whereis
)
408 "C-c C-c")) ; TBD: BOGUS hardcode
409 (abortkey (if abortkey-whereis
410 (key-description abortkey-whereis
)
411 "M-x kill-buffer")) ; TBD: BOGUS hardcode
413 (message "Please enter your report. Type %s to send, %s to abort."
417 (defun reporter-bug-hook ()
418 ;; prohibit sending mail if empty bug report
421 (beginning-of-buffer)
422 (re-search-forward (reporter-calculate-separator) (point-max) 'move
)
426 (goto-char (point-max))
427 (skip-chars-backward " \t\n")
428 (if (and (= (- (point) after-sep-pos
)
429 (length reporter-initial-text
))
430 (string= (buffer-substring after-sep-pos
(point))
431 reporter-initial-text
))
432 (error "Bug report was empty--not sent"))
437 ;;; reporter.el ends here