(reporter-compose-outgoing): Fix error syntax.
[emacs.git] / lisp / mail / reporter.el
blob76d7108d1a0ed83593d64d40503f4d04d91bd71a
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
7 ;; Version: 3.3
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)
16 ;; any later version.
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.
28 ;;; Commentary:
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.
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"
55 ;; (interactive)
56 ;; (reporter-submit-bug-report
57 ;; mypkg-maintainer-address
58 ;; (concat "mypkg.el " mypkg-version)
59 ;; (list 'mypkg-variable-1
60 ;; 'mypkg-variable-2
61 ;; ;; ...
62 ;; 'mypkg-variable-last)))
64 ;; Mailing List
65 ;; ============
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!
81 ;;; Code:
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
91 string.
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
105 sexp per line.
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'
109 bind it.")
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
119 composed.")
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))
137 (progn
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
149 (progn
150 (down-list 1)
151 (setq indent-enclosing-p t)
152 (while t
153 (setq here (point))
154 (forward-sexp 1)
155 (if (<= maxwidth (current-column))
156 (if linebreak
157 (progn
158 (goto-char linebreak)
159 (newline-and-indent)
160 (setq linebreak nil))
161 (goto-char here)
162 (setq indent-p (reporter-beautify-list maxwidth compact-p))
163 (goto-char here)
164 (forward-sexp 1)
165 (if indent-p
166 (newline-and-indent))
168 (if compact-p
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
177 (save-excursion
178 (goto-char (1+ (nth 1 state)))
179 (current-column)))
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)
185 (condition-case nil
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)))
192 (here (point)))
193 (insert " " sym " "
194 (cond
195 ((memq val '(t nil)) "")
196 ((listp val) "'")
197 ((symbolp val) "'")
198 (t ""))
199 (prin1-to-string val))
200 (lisp-indent-line)
201 ;; clean up lists, but only if the line as printed was long
202 ;; enough to wrap
203 (if (and val ;nil is a list, but short
204 (listp val)
205 (<= maxwidth (current-column)))
206 (save-excursion
207 (let ((compact-p (not (memq varsym reporter-dont-compact-list)))
208 (lisp-indent-function 'reporter-lisp-indent))
209 (goto-char here)
210 (reporter-beautify-list maxwidth compact-p))))
211 (insert "\n"))
212 (void-variable
213 (save-excursion
214 (set-buffer mailbuf)
215 (mail-position-on-field "X-Reporter-Void-Vars-Found")
216 (end-of-line)
217 (insert (symbol-name varsym) " ")))
218 (error
219 (error ""))))
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
240 ;; properly.
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
244 ;; dumped.
245 (let ((buffer (current-buffer)))
246 (set-buffer buffer)
247 (insert "Emacs : " (emacs-version) "\n")
248 (and pkgname
249 (insert "Package: " pkgname "\n"))
250 (run-hooks 'pre-hooks)
251 (if (not varlist)
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*")))
259 (save-excursion
260 (set-buffer elbuf)
261 (emacs-lisp-mode)
262 (erase-buffer)
263 (insert "(setq\n")
264 (lisp-indent-line)
265 (mapcar
266 (function
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)
274 varlist)
275 (lisp-indent-line)
276 (insert ")\n"))
277 (insert-buffer elbuf))
278 (error
279 (insert "State could not be dumped due to the following error:\n\n"
280 (format "%s" fault)
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
288 (save-excursion
289 (re-search-forward
290 (concat
291 "^\\(" ;beginning of line
292 (mapconcat
293 'identity
294 (list "[\t ]*" ;simple SMTP form
295 "-+" ;mh-e form
296 (regexp-quote
297 mail-header-separator)) ;sendmail.el form
298 "\\|") ;or them together
299 "\\)$") ;end of line
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
308 ;; the list.
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)))
314 (progn
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'")
319 (ding)
320 (message "`%s' is an invalid `mail-user-agent'; using `sendmail-user-agent'"
321 mail-user-agent)
323 (funcall compose)
324 agent))
327 ;;;###autoload
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))
344 final-resting-place
345 after-sep-pos
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))
354 hookvar)
355 ;; do the work
356 (require 'sendmail)
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
361 ;; back somewhere
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
366 ;; variable bound.
367 (let ((mail-header-separator (reporter-calculate-separator)))
368 (mail-position-on-field "to")
369 (insert address)
370 ;; insert problem summary if available
371 (if (and reporter-prompt-for-summary-p problem pkgname)
372 (progn
373 (mail-position-on-field "subject")
374 (insert pkgname "; " problem)))
375 ;; move point to the body of the message
376 (mail-text)
377 (forward-line 1)
378 (setq after-sep-pos (point))
379 (and salutation (insert "\n" salutation "\n\n"))
380 (unwind-protect
381 (progn
382 (setq final-resting-place (point-marker))
383 (insert "\n\n")
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).
392 (save-excursion
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))
397 (progn
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."
414 sendkey abortkey))
417 (defun reporter-bug-hook ()
418 ;; prohibit sending mail if empty bug report
419 (let ((after-sep-pos
420 (save-excursion
421 (beginning-of-buffer)
422 (re-search-forward (reporter-calculate-separator) (point-max) 'move)
423 (forward-line 1)
424 (point))))
425 (save-excursion
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"))
436 (provide 'reporter)
437 ;;; reporter.el ends here