]> code.delx.au - gnu-emacs/blob - lisp/mail/reporter.el
(rmail-make-basic-summary-line): Accept ISO 8601 dates as well.
[gnu-emacs] / lisp / mail / reporter.el
1 ;;; reporter.el --- customizable bug reporting of lisp programs
2
3 ;; Copyright (C) 1993, 1994 Free Software Foundation, Inc.
4
5 ;; Author: 1993 Barry A. Warsaw <bwarsaw@cnri.reston.va.us>
6 ;; Maintainer: bwarsaw@cnri.reston.va.us
7 ;; Created: 19-Apr-1993
8 ;; Version: 2.21
9 ;; Last Modified: 1994/11/29 16:13:50
10 ;; Keywords: bug reports lisp
11
12 ;; This file is part of GNU Emacs.
13
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)
17 ;; any later version.
18
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.
23
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.
28
29 ;;; Commentary:
30
31 ;; Introduction
32 ;; ============
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.
41 ;;
42 ;; Do a "C-h f reporter-submit-bug-report" for more information.
43 ;; Here's an example usage:
44 ;;
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"
49 ;; (interactive)
50 ;; (require 'reporter)
51 ;; (reporter-submit-bug-report
52 ;; mypkg-maintainer-address
53 ;; (concat "mypkg.el " mypkg-version)
54 ;; (list 'mypkg-variable-1
55 ;; 'mypkg-variable-2
56 ;; ;; ...
57 ;; 'mypkg-variable-last)))
58
59 ;; Mailing List
60 ;; ============
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:
65 ;;
66 ;; Administrivia: reporter-request@anthem.nlm.nih.gov
67 ;; Submissions: reporter@anthem.nlm.nih.gov
68
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!
72
73 ;; LCD Archive Entry:
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|
77
78 ;;; Code:
79
80 \f
81 ;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
82 ;; user defined variables
83
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.
89
90 MH-E users may want to use `mh-smail'.")
91
92
93 ;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
94 ;; Package author interface variables
95
96 (defvar reporter-prompt-for-summary-p nil
97 "Interface variable controlling prompting for problem summary.
98 When non-nil, `reporter-submit-bug-report' prompts the user for a
99 brief summary of the problem, and puts this summary on the Subject:
100 line.
101
102 Default behavior is to not prompt (i.e. nil). If you want reporter to
103 prompt, you should `let' bind this variable to t before calling
104 `reporter-submit-bug-report'. Note that this variable is not
105 buffer-local so you should never just `setq' it.")
106
107 (defvar reporter-dont-compact-list nil
108 "Interface variable controlling compacting of list values.
109 When non-nil, this must be a list of variable symbols. When a
110 variable containing a list value is formatted in the bug report mail
111 buffer, it normally is compacted so that its value fits one the fewest
112 number of lines. If the variable's symbol appears in this list, its
113 value is printed in a more verbose style, specifically, one elemental
114 sexp per line.
115
116 Note that this variable is not buffer-local so you should never just
117 `setq' it. If you want to changes its default value, you should `let'
118 bind it.")
119
120 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
121 ;; End of editable variables
122
123
124 (defvar reporter-eval-buffer nil
125 "Buffer to retrieve variable's value from.
126 This is necessary to properly support the printing of buffer-local
127 variables. Current buffer will always be the mail buffer being
128 composed.")
129
130 (defconst reporter-version "2.21"
131 "Reporter version number.")
132
133 (defvar reporter-initial-text nil
134 "The automatically created initial text of a bug report.")
135 (make-variable-buffer-local 'reporter-initial-text)
136
137 \f
138 (defvar reporter-status-message nil)
139 (defvar reporter-status-count nil)
140
141 (defun reporter-update-status ()
142 ;; periodically output a status message
143 (if (zerop (% reporter-status-count 10))
144 (progn
145 (message reporter-status-message)
146 (setq reporter-status-message (concat reporter-status-message "."))))
147 (setq reporter-status-count (1+ reporter-status-count)))
148
149 \f
150 (defun reporter-beautify-list (maxwidth compact-p)
151 ;; pretty print a list
152 (reporter-update-status)
153 (let (linebreak indent-enclosing-p indent-p here)
154 (condition-case nil ;loop exit
155 (progn
156 (down-list 1)
157 (setq indent-enclosing-p t)
158 (while t
159 (setq here (point))
160 (forward-sexp 1)
161 (if (<= maxwidth (current-column))
162 (if linebreak
163 (progn
164 (goto-char linebreak)
165 (newline-and-indent)
166 (setq linebreak nil))
167 (goto-char here)
168 (setq indent-p (reporter-beautify-list maxwidth compact-p))
169 (goto-char here)
170 (forward-sexp 1)
171 (if indent-p
172 (newline-and-indent))
173 t)
174 (if compact-p
175 (setq linebreak (point))
176 (newline-and-indent))
177 ))
178 t)
179 (error indent-enclosing-p))))
180
181 (defun reporter-lisp-indent (indent-point state)
182 ;; a better lisp indentation style for bug reporting
183 (save-excursion
184 (goto-char (1+ (nth 1 state)))
185 (current-column)))
186
187 (defun reporter-dump-variable (varsym mailbuf)
188 ;; Pretty-print the value of the variable in symbol VARSYM. MAILBUF
189 ;; is the mail buffer being composed
190 (reporter-update-status)
191 (condition-case nil
192 (let ((val (save-excursion
193 (set-buffer reporter-eval-buffer)
194 (symbol-value varsym)))
195 (sym (symbol-name varsym))
196 (print-escape-newlines t)
197 (maxwidth (1- (window-width)))
198 (here (point)))
199 (insert " " sym " "
200 (cond
201 ((memq val '(t nil)) "")
202 ((listp val) "'")
203 ((symbolp val) "'")
204 (t ""))
205 (prin1-to-string val))
206 (lisp-indent-line)
207 ;; clean up lists, but only if the line as printed was long
208 ;; enough to wrap
209 (if (and val ;nil is a list, but short
210 (listp val)
211 (<= maxwidth (current-column)))
212 (save-excursion
213 (let ((compact-p (not (memq varsym reporter-dont-compact-list)))
214 (lisp-indent-function 'reporter-lisp-indent))
215 (goto-char here)
216 (reporter-beautify-list maxwidth compact-p))))
217 (insert "\n"))
218 (void-variable
219 (save-excursion
220 (set-buffer mailbuf)
221 (mail-position-on-field "X-Reporter-Void-Vars-Found")
222 (end-of-line)
223 (insert (symbol-name varsym) " ")))
224 (error (error ""))))
225
226 (defun reporter-dump-state (pkgname varlist pre-hooks post-hooks)
227 ;; Dump the state of the mode specific variables.
228 ;; PKGNAME contains the name of the mode as it will appear in the bug
229 ;; report (you must explicitly concat any version numbers).
230
231 ;; VARLIST is the list of variables to dump. Each element in
232 ;; VARLIST can be a variable symbol, or a cons cell. If a symbol,
233 ;; this will be passed to `reporter-dump-variable' for insertion
234 ;; into the mail buffer. If a cons cell, the car must be a variable
235 ;; symbol and the cdr must be a function which will be `funcall'd
236 ;; with arguments the symbol and the mail buffer being composed. Use
237 ;; this to write your own custom variable value printers for
238 ;; specific variables.
239
240 ;; Note that the global variable `reporter-eval-buffer' will be bound to
241 ;; the buffer in which `reporter-submit-bug-report' was invoked. If you
242 ;; want to print the value of a buffer local variable, you should wrap
243 ;; the `eval' call in your custom printer inside a `set-buffer' (and
244 ;; probably a `save-excursion'). `reporter-dump-variable' handles this
245 ;; properly.
246
247 ;; PRE-HOOKS is run after the emacs-version and PKGNAME are inserted, but
248 ;; before the VARLIST is dumped. POST-HOOKS is run after the VARLIST is
249 ;; dumped.
250 (let ((buffer (current-buffer)))
251 (set-buffer buffer)
252 (insert "Emacs : " (emacs-version) "\n")
253 (and pkgname
254 (insert "Package: " pkgname "\n"))
255 (run-hooks 'pre-hooks)
256 (if (not varlist)
257 nil
258 (insert "\ncurrent state:\n==============\n")
259 ;; create an emacs-lisp-mode buffer to contain the output, which
260 ;; we'll later insert into the mail buffer
261 (condition-case fault
262 (let ((mailbuf (current-buffer))
263 (elbuf (get-buffer-create " *tmp-reporter-buffer*")))
264 (save-excursion
265 (set-buffer elbuf)
266 (emacs-lisp-mode)
267 (erase-buffer)
268 (insert "(setq\n")
269 (lisp-indent-line)
270 (mapcar
271 (function
272 (lambda (varsym-or-cons-cell)
273 (let ((varsym (or (car-safe varsym-or-cons-cell)
274 varsym-or-cons-cell))
275 (printer (or (cdr-safe varsym-or-cons-cell)
276 'reporter-dump-variable)))
277 (funcall printer varsym mailbuf)
278 )))
279 varlist)
280 (lisp-indent-line)
281 (insert ")\n"))
282 (insert-buffer elbuf))
283 (error
284 (insert "State could not be dumped due to the following error:\n\n"
285 (format "%s" fault)
286 "\n\nYou should still send this bug report."))))
287 (run-hooks 'post-hooks)
288 ))
289
290 \f
291 (defun reporter-calculate-separator ()
292 ;; returns the string regexp matching the mail separator
293 (save-excursion
294 (re-search-forward
295 (concat
296 "^\\(" ;beginning of line
297 (mapconcat
298 'identity
299 (list "[\t ]*" ;simple SMTP form
300 "-+" ;mh-e form
301 (regexp-quote
302 mail-header-separator)) ;sendmail.el form
303 "\\|") ;or them together
304 "\\)$") ;end of line
305 nil
306 'move) ;search for and move
307 (buffer-substring (match-beginning 0) (match-end 0))))
308
309 ;; Serves as an interface to `mail',
310 ;; but when the user says "no" to discarding an unset message,
311 ;; it gives an error.
312 (defun reporter-mail (&rest args)
313 (interactive "P")
314 (or (apply 'mail args)
315 (error "Bug report aborted")))
316
317 ;;;###autoload
318 (defun reporter-submit-bug-report
319 (address pkgname varlist &optional pre-hooks post-hooks salutation)
320 ;; Submit a bug report via mail.
321
322 ;; ADDRESS is the email address for the package's maintainer. PKGNAME is
323 ;; the name of the mode (you must explicitly concat any version numbers).
324 ;; VARLIST is the list of variables to dump (see `reporter-dump-state'
325 ;; for details). Optional PRE-HOOKS and POST-HOOKS are passed to
326 ;; `reporter-dump-state'. Optional SALUTATION is inserted at the top of the
327 ;; mail buffer, and point is left after the salutation.
328
329 ;; This function will prompt for a summary if
330 ;; reporter-prompt-for-summary-p is non-nil.
331
332 ;; The mailer used is described in the variable `reporter-mailer'.
333 (let ((reporter-eval-buffer (current-buffer))
334 final-resting-place
335 after-sep-pos
336 (reporter-status-message "Formatting bug report buffer...")
337 (reporter-status-count 0)
338 (problem (and reporter-prompt-for-summary-p
339 (read-string "(Very) brief summary of problem: ")))
340 (mailbuf
341 ;; Normally *mail* is directed to appear in the same window,
342 ;; but we don't want that to happen here.
343 (let (same-window-buffer-names
344 same-window-regexps)
345 (call-interactively
346 (if (nlistp reporter-mailer)
347 reporter-mailer
348 (let ((mlist reporter-mailer)
349 (mailer nil))
350 (while mlist
351 (if (commandp (car mlist))
352 (setq mailer (car mlist)
353 mlist nil)
354 (setq mlist (cdr mlist))))
355 (if (not mailer)
356 (error
357 "Variable `%s' does not contain a command for mailing"
358 "reporter-mailer"))
359 mailer)))
360 (current-buffer))))
361 (require 'sendmail)
362 ;; If mailbuf did not get made visible before,
363 ;; make it visible now.
364 (let (same-window-buffer-names
365 same-window-regexps)
366 (pop-to-buffer mailbuf)
367 ;; Just in case the original buffer is not visible now,
368 ;; bring it back somewhere.
369 (display-buffer reporter-eval-buffer))
370 (goto-char (point-min))
371 ;; different mailers use different separators, some may not even
372 ;; use m-h-s, but sendmail.el stuff must have m-h-s bound.
373 (let ((mail-header-separator (reporter-calculate-separator)))
374 (mail-position-on-field "to")
375 (insert address)
376 ;; insert problem summary if available
377 (if (and reporter-prompt-for-summary-p problem pkgname)
378 (progn
379 (mail-position-on-field "subject")
380 (insert pkgname "; " problem)))
381 ;; move point to the body of the message
382 (mail-text)
383 (forward-line 1)
384 (setq after-sep-pos (point))
385 (and salutation (insert "\n" salutation "\n\n"))
386 (unwind-protect
387 (progn
388 (setq final-resting-place (point-marker))
389 (insert "\n\n")
390 (reporter-dump-state pkgname varlist pre-hooks post-hooks)
391 (goto-char final-resting-place))
392 (set-marker final-resting-place nil)))
393
394 ;; save initial text and set up the `no-empty-submission' hook.
395 ;; This only works for mailers that support mail-send-hook,
396 ;; e.g. sendmail.el
397 (if (fboundp 'add-hook)
398 (progn
399 (save-excursion
400 (goto-char (point-max))
401 (skip-chars-backward " \t\n")
402 (setq reporter-initial-text
403 (buffer-substring after-sep-pos (point))))
404 (make-variable-buffer-local 'mail-send-hook)
405 (add-hook 'mail-send-hook 'reporter-bug-hook)))
406
407 ;; minibuf message
408 ;; C-c C-c can't be generalized because they don't always run
409 ;; mail-send-and-exit. E.g. vm-mail-send-and-exit. I don't want
410 ;; to hard code these.
411 (let* ((sendkey "C-c C-c")
412 (killkey-whereis (where-is-internal 'kill-buffer nil t))
413 (killkey (if killkey-whereis
414 (key-description killkey-whereis)
415 "M-x kill-buffer")))
416 (message "Please type in your report. Hit %s to send, %s to abort."
417 sendkey killkey))
418 ))
419
420 (defun reporter-bug-hook ()
421 ;; prohibit sending mail if empty bug report
422 (let ((after-sep-pos
423 (save-excursion
424 (beginning-of-buffer)
425 (re-search-forward (reporter-calculate-separator) (point-max) 'move)
426 (forward-line 1)
427 (point))))
428 (save-excursion
429 (goto-char (point-max))
430 (skip-chars-backward " \t\n")
431 (if (and (= (- (point) after-sep-pos)
432 (length reporter-initial-text))
433 (string= (buffer-substring after-sep-pos (point))
434 reporter-initial-text))
435 (error "Empty bug report cannot be sent"))
436 )))
437
438 \f
439 (provide 'reporter)
440
441 ;;; reporter.el ends here