1 ;;; reporter.el --- customizable bug reporting of lisp programs
3 ;; Copyright (C) 1993, 1994 Free Software Foundation, Inc.
5 ;; Author: 1993 Barry A. Warsaw <bwarsaw@cnri.reston.va.us>
6 ;; Maintainer: bwarsaw@cnri.reston.va.us
7 ;; Created: 19-Apr-1993
9 ;; Last Modified: 1994/11/29 16:13:50
10 ;; Keywords: bug reports lisp
12 ;; This file is part of GNU Emacs.
14 ;; GNU Emacs is free software; you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation; either version 2, or (at your option)
19 ;; GNU Emacs is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs; see the file COPYING. If not, write to the
26 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
27 ;; Boston, MA 02111-1307, USA.
33 ;; This program is for lisp package authors and can be used to ease
34 ;; reporting of bugs. When invoked, reporter-submit-bug-report will
35 ;; set up a mail buffer with the appropriate bug report address,
36 ;; including a lisp expression the maintainer of the package can eval
37 ;; to completely reproduce the environment in which the bug was
38 ;; observed (e.g. by using eval-last-sexp). This package proved
39 ;; especially useful during my development of cc-mode.el, which is
40 ;; highly dependent on its configuration variables.
42 ;; Do a "C-h f reporter-submit-bug-report" for more information.
43 ;; Here's an example usage:
45 ;;(defconst mypkg-version "9.801")
46 ;;(defconst mypkg-maintainer-address "mypkg-help@foo.com")
47 ;;(defun mypkg-submit-bug-report ()
48 ;; "Submit via mail a bug report on mypkg"
50 ;; (require 'reporter)
51 ;; (reporter-submit-bug-report
52 ;; mypkg-maintainer-address
53 ;; (concat "mypkg.el " mypkg-version)
54 ;; (list 'mypkg-variable-1
57 ;; 'mypkg-variable-last)))
61 ;; I've set up a mailing list to report bugs or suggest enhancements,
62 ;; etc. This list's intended audience is elisp package authors who are
63 ;; using reporter and want to stay current with releases. Here are the
64 ;; relevant addresses:
66 ;; Administrivia: reporter-request@anthem.nlm.nih.gov
67 ;; Submissions: reporter@anthem.nlm.nih.gov
69 ;; Packages that currently use reporter are: cc-mode, supercite, elp,
70 ;; tcl, ediff, crypt, vm, edebug, archie, and efs. If you know of
71 ;; others, please email me!
74 ;; reporter|Barry A. Warsaw|bwarsaw@cnri.reston.va.us|
75 ;; Customizable bug reporting of lisp programs.|
76 ;; 1994/11/29 16:13:50|2.21|~/misc/reporter.el.Z|
81 ;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
82 ;; user defined variables
84 (defvar reporter-mailer '(vm-mail reporter-mail)
85 "*Mail package to use to generate bug report buffer.
86 This can either be a function symbol or a list of function symbols.
87 If a list, it tries to use each specified mailer in order until an
88 existing one is found.
90 MH-E users may want to use `mh-smail'.")
92 (defvar reporter-prompt-for-summary-p nil
93 "Interface variable controlling prompting for problem summary.
94 When non-nil, `reporter-submit-bug-report' prompts the user for a
95 brief summary of the problem, and puts this summary on the Subject:
98 Default behavior is to not prompt (i.e. nil). If you want reporter to
99 prompt, you should `let' bind this variable to t before calling
100 `reporter-submit-bug-report'. Note that this variable is not
101 buffer-local so you should never just `setq' it.")
103 (defvar reporter-dont-compact-list nil
104 "Interface variable controlling compacting of list values.
105 When non-nil, this must be a list of variable symbols. When a
106 variable containing a list value is formatted in the bug report mail
107 buffer, it normally is compacted so that its value fits one the fewest
108 number of lines. If the variable's symbol appears in this list, its
109 value is printed in a more verbose style, specifically, one elemental
112 Note that this variable is not buffer-local so you should never just
113 `setq' it. If you want to changes its default value, you should `let'
116 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
117 ;; end of user defined variables
119 (defvar reporter-eval-buffer nil
120 "Buffer to retrieve variable's value from.
121 This is necessary to properly support the printing of buffer-local
122 variables. Current buffer will always be the mail buffer being
125 (defconst reporter-version "2.21"
126 "Reporter version number.")
128 (defvar reporter-initial-text nil
129 "The automatically created initial text of a bug report.")
130 (make-variable-buffer-local 'reporter-initial-text)
133 (defvar reporter-status-message nil)
134 (defvar reporter-status-count nil)
136 (defun reporter-update-status ()
137 ;; periodically output a status message
138 (if (zerop (% reporter-status-count 10))
140 (message reporter-status-message)
141 (setq reporter-status-message (concat reporter-status-message "."))))
142 (setq reporter-status-count (1+ reporter-status-count)))
145 (defun reporter-beautify-list (maxwidth compact-p)
146 ;; pretty print a list
147 (reporter-update-status)
148 (let (linebreak indent-enclosing-p indent-p here)
149 (condition-case nil ;loop exit
152 (setq indent-enclosing-p t)
156 (if (<= maxwidth (current-column))
159 (goto-char linebreak)
161 (setq linebreak nil))
163 (setq indent-p (reporter-beautify-list maxwidth compact-p))
167 (newline-and-indent))
170 (setq linebreak (point))
171 (newline-and-indent))
174 (error indent-enclosing-p))))
176 (defun reporter-lisp-indent (indent-point state)
177 ;; a better lisp indentation style for bug reporting
179 (goto-char (1+ (nth 1 state)))
182 (defun reporter-dump-variable (varsym mailbuf)
183 ;; Pretty-print the value of the variable in symbol VARSYM. MAILBUF
184 ;; is the mail buffer being composed
185 (reporter-update-status)
187 (let ((val (save-excursion
188 (set-buffer reporter-eval-buffer)
189 (symbol-value varsym)))
190 (sym (symbol-name varsym))
191 (print-escape-newlines t)
192 (maxwidth (1- (window-width)))
196 ((memq val '(t nil)) "")
200 (prin1-to-string val))
202 ;; clean up lists, but only if the line as printed was long
204 (if (and val ;nil is a list, but short
206 (<= maxwidth (current-column)))
208 (let ((compact-p (not (memq varsym reporter-dont-compact-list)))
209 (lisp-indent-function 'reporter-lisp-indent))
211 (reporter-beautify-list maxwidth compact-p))))
216 (mail-position-on-field "X-Reporter-Void-Vars-Found")
218 (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))))
304 ;; Serves as an interface to `mail',
305 ;; but when the user says "no" to discarding an unset message,
306 ;; it gives an error.
307 (defun reporter-mail (&rest args)
309 (or (apply 'mail args)
310 (error "Bug report aborted")))
313 (defun reporter-submit-bug-report
314 (address pkgname varlist &optional pre-hooks post-hooks salutation)
315 ;; Submit a bug report via mail.
317 ;; ADDRESS is the email address for the package's maintainer. PKGNAME is
318 ;; the name of the mode (you must explicitly concat any version numbers).
319 ;; VARLIST is the list of variables to dump (see `reporter-dump-state'
320 ;; for details). Optional PRE-HOOKS and POST-HOOKS are passed to
321 ;; `reporter-dump-state'. Optional SALUTATION is inserted at the top of the
322 ;; mail buffer, and point is left after the salutation.
324 ;; This function will prompt for a summary if
325 ;; reporter-prompt-for-summary-p is non-nil.
327 ;; The mailer used is described in the variable `reporter-mailer'.
328 (let ((reporter-eval-buffer (current-buffer))
331 (reporter-status-message "Formatting bug report buffer...")
332 (reporter-status-count 0)
333 (problem (and reporter-prompt-for-summary-p
334 (read-string "(Very) brief summary of problem: ")))
336 ;; Normally *mail* is directed to appear in the same window,
337 ;; but we don't want that to happen here.
338 (let (same-window-buffer-names
341 (if (nlistp reporter-mailer)
343 (let ((mlist reporter-mailer)
346 (if (commandp (car mlist))
347 (setq mailer (car mlist)
349 (setq mlist (cdr mlist))))
352 "Variable `%s' does not contain a command for mailing"
357 ;; If mailbuf did not get made visible before,
358 ;; make it visible now.
359 (let (same-window-buffer-names
361 (pop-to-buffer mailbuf)
362 ;; Just in case the original buffer is not visible now,
363 ;; bring it back somewhere.
364 (display-buffer reporter-eval-buffer))
365 (goto-char (point-min))
366 ;; different mailers use different separators, some may not even
367 ;; use m-h-s, but sendmail.el stuff must have m-h-s bound.
368 (let ((mail-header-separator (reporter-calculate-separator)))
369 (mail-position-on-field "to")
371 ;; insert problem summary if available
372 (if (and reporter-prompt-for-summary-p problem pkgname)
374 (mail-position-on-field "subject")
375 (insert pkgname "; " problem)))
376 ;; move point to the body of the message
379 (setq after-sep-pos (point))
380 (and salutation (insert "\n" salutation "\n\n"))
383 (setq final-resting-place (point-marker))
385 (reporter-dump-state pkgname varlist pre-hooks post-hooks)
386 (goto-char final-resting-place))
387 (set-marker final-resting-place nil)))
389 ;; save initial text and set up the `no-empty-submission' hook.
390 ;; This only works for mailers that support mail-send-hook,
392 (if (fboundp 'add-hook)
395 (goto-char (point-max))
396 (skip-chars-backward " \t\n")
397 (setq reporter-initial-text
398 (buffer-substring after-sep-pos (point))))
399 (make-variable-buffer-local 'mail-send-hook)
400 (add-hook 'mail-send-hook 'reporter-bug-hook)))
403 ;; C-c C-c can't be generalized because they don't always run
404 ;; mail-send-and-exit. E.g. vm-mail-send-and-exit. I don't want
405 ;; to hard code these.
406 (let* ((sendkey "C-c C-c")
407 (killkey-whereis (where-is-internal 'kill-buffer nil t))
408 (killkey (if killkey-whereis
409 (key-description killkey-whereis)
411 (message "Please type in your report. Hit %s to send, %s to abort."
415 (defun reporter-bug-hook ()
416 ;; prohibit sending mail if empty bug report
419 (beginning-of-buffer)
420 (re-search-forward (reporter-calculate-separator) (point-max) 'move)
424 (goto-char (point-max))
425 (skip-chars-backward " \t\n")
426 (if (and (= (- (point) after-sep-pos)
427 (length reporter-initial-text))
428 (string= (buffer-substring after-sep-pos (point))
429 reporter-initial-text))
430 (error "Empty bug report cannot be sent"))
436 ;;; reporter.el ends here