comparison lisp/gnus/spam-stat.el @ 85712:a3c27999decb

Update Gnus to No Gnus 0.7 from the Gnus CVS trunk Revision: emacs@sv.gnu.org/emacs--devo--0--patch-911
author Miles Bader <miles@gnu.org>
date Sun, 28 Oct 2007 09:18:39 +0000
parents 24202b793a08
children 8bb336490b3c
comparison
equal deleted inserted replaced
85711:b6f5dc84b2e1 85712:a3c27999decb
120 ;; Milan Zamazal <pdm@zamazal.org> 120 ;; Milan Zamazal <pdm@zamazal.org>
121 121
122 122
123 123
124 ;;; Code: 124 ;;; Code:
125 (require 'mail-parse)
125 126
126 (defvar gnus-original-article-buffer) 127 (defvar gnus-original-article-buffer)
127 128
128 (defgroup spam-stat nil 129 (defgroup spam-stat nil
129 "Statistical spam detection for Emacs. 130 "Statistical spam detection for Emacs.
161 This variable says how many characters this will be." 162 This variable says how many characters this will be."
162 :type 'integer 163 :type 'integer
163 :group 'spam-stat) 164 :group 'spam-stat)
164 165
165 (defcustom spam-stat-split-fancy-spam-group "mail.spam" 166 (defcustom spam-stat-split-fancy-spam-group "mail.spam"
166 "Name of the group where spam should be stored, if 167 "Name of the group where spam should be stored.
167 `spam-stat-split-fancy' is used in fancy splitting rules. Has no 168 If `spam-stat-split-fancy' is used in fancy splitting rules. Has
168 effect when spam-stat is invoked through spam.el." 169 no effect when spam-stat is invoked through spam.el."
169 :type 'string 170 :type 'string
170 :group 'spam-stat) 171 :group 'spam-stat)
171 172
172 (defcustom spam-stat-split-fancy-spam-threshhold 0.9 173 (defcustom spam-stat-split-fancy-spam-threshold 0.9
173 "Spam score threshhold in spam-stat-split-fancy." 174 "Spam score threshold in spam-stat-split-fancy."
174 :type 'number 175 :type 'number
175 :group 'spam-stat) 176 :group 'spam-stat)
177
178 (defcustom spam-stat-washing-hook nil
179 "Hook applied to each message before analysis."
180 :type 'hook
181 :group 'spam-stat)
182
183 (defcustom spam-stat-score-buffer-user-functions nil
184 "List of additional scoring functions.
185 Called one by one on the buffer.
186
187 If all of these functions return non-nil answers, these numerical
188 answers are added to the computed spam stat score on the buffer. If
189 you defun such functions, make sure they don't return the buffer in a
190 narrowed state or such: use, for example, `save-excursion'. Each of
191 your functions is also passed the initial spam-stat score which might
192 aid in your scoring.
193
194 Also be careful when defining such functions. If they take a long
195 time, they will slow down your mail splitting. Thus, if the buffer is
196 large, don't forget to use smaller regions, by wrapping your work in,
197 say, `with-spam-stat-max-buffer-size'."
198 :type '(repeat sexp)
199 :group 'spam-stat)
200
201 (defcustom spam-stat-process-directory-age 90
202 "Max. age of files to be processed in directory, in days.
203 When using `spam-stat-process-spam-directory' or
204 `spam-stat-process-non-spam-directory', only files that have
205 been touched in this many days will be considered. Without
206 this filter, re-training spam-stat with several thousand messages
207 will start to take a very long time."
208 :type 'number
209 :group 'spam-stat)
210
211 (defvar spam-stat-last-saved-at nil
212 "Time stamp of last change of spam-stat-file on this run")
176 213
177 (defvar spam-stat-syntax-table 214 (defvar spam-stat-syntax-table
178 (let ((table (copy-syntax-table text-mode-syntax-table))) 215 (let ((table (copy-syntax-table text-mode-syntax-table)))
179 (modify-syntax-entry ?- "w" table) 216 (modify-syntax-entry ?- "w" table)
180 (modify-syntax-entry ?_ "w" table) 217 (modify-syntax-entry ?_ "w" table)
194 This is set by hooking into Gnus.") 231 This is set by hooking into Gnus.")
195 232
196 (defvar spam-stat-buffer-name " *spam stat buffer*" 233 (defvar spam-stat-buffer-name " *spam stat buffer*"
197 "Name of the `spam-stat-buffer'.") 234 "Name of the `spam-stat-buffer'.")
198 235
199 ;; Functions missing in Emacs 20 236 (defvar spam-stat-coding-system
200 237 (if (mm-coding-system-p 'emacs-mule) 'emacs-mule 'raw-text)
201 (when (memq nil (mapcar 'fboundp 238 "Coding system used for `spam-stat-file'.")
202 '(gethash hash-table-count make-hash-table
203 mapc puthash)))
204 (require 'cl)
205 (unless (fboundp 'puthash)
206 ;; alias puthash is missing from Emacs 20 cl-extra.el
207 (defalias 'puthash 'cl-puthash)))
208
209 (eval-when-compile
210 (unless (fboundp 'with-syntax-table)
211 ;; Imported from Emacs 21.2
212 (defmacro with-syntax-table (table &rest body) "\
213 Evaluate BODY with syntax table of current buffer set to a copy of TABLE.
214 The syntax table of the current buffer is saved, BODY is evaluated, and the
215 saved table is restored, even in case of an abnormal exit.
216 Value is what BODY returns."
217 (let ((old-table (make-symbol "table"))
218 (old-buffer (make-symbol "buffer")))
219 `(let ((,old-table (syntax-table))
220 (,old-buffer (current-buffer)))
221 (unwind-protect
222 (progn
223 (set-syntax-table (copy-syntax-table ,table))
224 ,@body)
225 (save-current-buffer
226 (set-buffer ,old-buffer)
227 (set-syntax-table ,old-table))))))))
228 239
229 ;; Hooking into Gnus 240 ;; Hooking into Gnus
230 241
231 (defun spam-stat-store-current-buffer () 242 (defun spam-stat-store-current-buffer ()
232 "Store a copy of the current buffer in `spam-stat-buffer'." 243 "Store a copy of the current buffer in `spam-stat-buffer'."
233 (save-excursion 244 (let ((buf (current-buffer)))
234 (let ((str (buffer-string))) 245 (with-current-buffer (get-buffer-create spam-stat-buffer-name)
235 (set-buffer (get-buffer-create spam-stat-buffer-name))
236 (erase-buffer) 246 (erase-buffer)
237 (insert str) 247 (insert-buffer-substring buf)
238 (setq spam-stat-buffer (current-buffer))))) 248 (setq spam-stat-buffer (current-buffer)))))
239 249
240 (defun spam-stat-store-gnus-article-buffer () 250 (defun spam-stat-store-gnus-article-buffer ()
241 "Store a copy of the current article in `spam-stat-buffer'. 251 "Store a copy of the current article in `spam-stat-buffer'.
242 This uses `gnus-article-buffer'." 252 This uses `gnus-article-buffer'."
243 (save-excursion 253 (with-current-buffer gnus-original-article-buffer
244 (set-buffer gnus-original-article-buffer)
245 (spam-stat-store-current-buffer))) 254 (spam-stat-store-current-buffer)))
246 255
247 ;; Data -- not using defstruct in order to save space and time 256 ;; Data -- not using defstruct in order to save space and time
248 257
249 (defvar spam-stat (make-hash-table :test 'equal) 258 (defvar spam-stat (make-hash-table :test 'equal)
256 (defvar spam-stat-ngood 0 265 (defvar spam-stat-ngood 0
257 "The number of good mails in the dictionary.") 266 "The number of good mails in the dictionary.")
258 267
259 (defvar spam-stat-nbad 0 268 (defvar spam-stat-nbad 0
260 "The number of bad mails in the dictionary.") 269 "The number of bad mails in the dictionary.")
270
271 (defvar spam-stat-error-holder nil
272 "A holder for condition-case errors while scoring buffers.")
261 273
262 (defsubst spam-stat-good (entry) 274 (defsubst spam-stat-good (entry)
263 "Return the number of times this word belongs to good mails." 275 "Return the number of times this word belongs to good mails."
264 (aref entry 0)) 276 (aref entry 0))
265 277
311 (/ b spam-stat-nbad))))))))) 323 (/ b spam-stat-nbad)))))))))
312 324
313 ;; Parsing 325 ;; Parsing
314 326
315 (defmacro with-spam-stat-max-buffer-size (&rest body) 327 (defmacro with-spam-stat-max-buffer-size (&rest body)
316 "Narrows the buffer down to the first 4k characters, then evaluates BODY." 328 "Narrow the buffer down to the first 4k characters, then evaluate BODY."
317 `(save-restriction 329 `(save-restriction
318 (when (> (- (point-max) 330 (when (> (- (point-max)
319 (point-min)) 331 (point-min))
320 spam-stat-max-buffer-length) 332 spam-stat-max-buffer-length)
321 (narrow-to-region (point-min) 333 (narrow-to-region (point-min)
322 (+ (point-min) spam-stat-max-buffer-length))) 334 (+ (point-min) spam-stat-max-buffer-length)))
323 ,@body)) 335 ,@body))
324 336
325 (defun spam-stat-buffer-words () 337 (defun spam-stat-buffer-words ()
326 "Return a hash table of words and number of occurrences in the buffer." 338 "Return a hash table of words and number of occurrences in the buffer."
339 (run-hooks 'spam-stat-washing-hook)
327 (with-spam-stat-max-buffer-size 340 (with-spam-stat-max-buffer-size
328 (with-syntax-table spam-stat-syntax-table 341 (with-syntax-table spam-stat-syntax-table
329 (goto-char (point-min)) 342 (goto-char (point-min))
330 (let ((result (make-hash-table :test 'equal)) 343 (let ((result (make-hash-table :test 'equal))
331 word count) 344 word count)
370 spam-stat-ngood (1- spam-stat-ngood)) 383 spam-stat-ngood (1- spam-stat-ngood))
371 (maphash 384 (maphash
372 (lambda (word count) 385 (lambda (word count)
373 (let ((entry (gethash word spam-stat))) 386 (let ((entry (gethash word spam-stat)))
374 (if (not entry) 387 (if (not entry)
375 (error "This buffer has unknown words in it") 388 (gnus-message 8 "This buffer has unknown words in it")
376 (spam-stat-set-good entry (- (spam-stat-good entry) count)) 389 (spam-stat-set-good entry (- (spam-stat-good entry) count))
377 (spam-stat-set-bad entry (+ (spam-stat-bad entry) count)) 390 (spam-stat-set-bad entry (+ (spam-stat-bad entry) count))
378 (spam-stat-set-score entry (spam-stat-compute-score entry)) 391 (spam-stat-set-score entry (spam-stat-compute-score entry))
379 (puthash word entry spam-stat)))) 392 (puthash word entry spam-stat))))
380 (spam-stat-buffer-words)) 393 (spam-stat-buffer-words))
386 spam-stat-ngood (1+ spam-stat-ngood)) 399 spam-stat-ngood (1+ spam-stat-ngood))
387 (maphash 400 (maphash
388 (lambda (word count) 401 (lambda (word count)
389 (let ((entry (gethash word spam-stat))) 402 (let ((entry (gethash word spam-stat)))
390 (if (not entry) 403 (if (not entry)
391 (error "This buffer has unknown words in it") 404 (gnus-message 8 "This buffer has unknown words in it")
392 (spam-stat-set-good entry (+ (spam-stat-good entry) count)) 405 (spam-stat-set-good entry (+ (spam-stat-good entry) count))
393 (spam-stat-set-bad entry (- (spam-stat-bad entry) count)) 406 (spam-stat-set-bad entry (- (spam-stat-bad entry) count))
394 (spam-stat-set-score entry (spam-stat-compute-score entry)) 407 (spam-stat-set-score entry (spam-stat-compute-score entry))
395 (puthash word entry spam-stat)))) 408 (puthash word entry spam-stat))))
396 (spam-stat-buffer-words)) 409 (spam-stat-buffer-words))
401 (defun spam-stat-save (&optional force) 414 (defun spam-stat-save (&optional force)
402 "Save the `spam-stat' hash table as lisp file. 415 "Save the `spam-stat' hash table as lisp file.
403 With a prefix argument save unconditionally." 416 With a prefix argument save unconditionally."
404 (interactive "P") 417 (interactive "P")
405 (when (or force spam-stat-dirty) 418 (when (or force spam-stat-dirty)
406 (with-temp-buffer 419 (let ((coding-system-for-write spam-stat-coding-system))
407 (let ((standard-output (current-buffer)) 420 (with-temp-file spam-stat-file
408 (font-lock-maximum-size 0)) 421 (let ((standard-output (current-buffer))
409 (insert "(setq spam-stat-ngood " 422 (font-lock-maximum-size 0))
410 (number-to-string spam-stat-ngood) 423 (insert (format ";-*- coding: %s; -*-\n" spam-stat-coding-system))
411 " spam-stat-nbad " 424 (insert (format "(setq spam-stat-ngood %d spam-stat-nbad %d
412 (number-to-string spam-stat-nbad) 425 spam-stat (spam-stat-to-hash-table '(" spam-stat-ngood spam-stat-nbad))
413 " spam-stat (spam-stat-to-hash-table '(") 426 (maphash (lambda (word entry)
414 (maphash (lambda (word entry) 427 (prin1 (list word
415 (prin1 (list word 428 (spam-stat-good entry)
416 (spam-stat-good entry) 429 (spam-stat-bad entry))))
417 (spam-stat-bad entry)))) 430 spam-stat)
418 spam-stat) 431 (insert ")))"))))
419 (insert ")))") 432 (message "Saved %s." spam-stat-file)
420 (write-file spam-stat-file))) 433 (setq spam-stat-dirty nil
421 (setq spam-stat-dirty nil))) 434 spam-stat-last-saved-at (nth 5 (file-attributes spam-stat-file)))))
422 435
423 (defun spam-stat-load () 436 (defun spam-stat-load ()
424 "Read the `spam-stat' hash table from disk." 437 "Read the `spam-stat' hash table from disk."
425 ;; TODO: maybe we should warn the user if spam-stat-dirty is t? 438 ;; TODO: maybe we should warn the user if spam-stat-dirty is t?
426 (load-file spam-stat-file) 439 (let ((coding-system-for-read spam-stat-coding-system))
427 (setq spam-stat-dirty nil)) 440 (cond (spam-stat-dirty (message "Spam stat not loaded: spam-stat-dirty t"))
441 ((or (not (boundp 'spam-stat-last-saved-at))
442 (null spam-stat-last-saved-at)
443 (not (equal spam-stat-last-saved-at
444 (nth 5 (file-attributes spam-stat-file)))))
445 (progn
446 (load-file spam-stat-file)
447 (setq spam-stat-dirty nil
448 spam-stat-last-saved-at
449 (nth 5 (file-attributes spam-stat-file)))))
450 (t (message "Spam stat file not loaded: no change in disk..")))))
428 451
429 (defun spam-stat-to-hash-table (entries) 452 (defun spam-stat-to-hash-table (entries)
430 "Turn list ENTRIES into a hash table and store as `spam-stat'. 453 "Turn list ENTRIES into a hash table and store as `spam-stat'.
431 Every element in ENTRIES has the form \(WORD GOOD BAD) where WORD is 454 Every element in ENTRIES has the form \(WORD GOOD BAD) where WORD is
432 the word string, NGOOD is the number of good mails it has appeared in, 455 the word string, NGOOD is the number of good mails it has appeared in,
433 NBAD is the number of bad mails it has appeared in, GOOD is the number 456 NBAD is the number of bad mails it has appeared in, GOOD is the number
434 of times it appeared in good mails, and BAD is the number of times it 457 of times it appeared in good mails, and BAD is the number of times it
435 has appeared in bad mails." 458 has appeared in bad mails."
436 (let ((table (make-hash-table :test 'equal))) 459 (let ((table (make-hash-table :size (length entries)
460 :test 'equal)))
437 (mapc (lambda (l) 461 (mapc (lambda (l)
438 (puthash (car l) 462 (puthash (car l)
439 (spam-stat-make-entry (nth 1 l) (nth 2 l)) 463 (spam-stat-make-entry (nth 1 l) (nth 2 l))
440 table)) 464 table))
441 entries) 465 entries)
464 (defun spam-stat-buffer-words-with-scores () 488 (defun spam-stat-buffer-words-with-scores ()
465 "Process current buffer, return the 15 most conspicuous words. 489 "Process current buffer, return the 15 most conspicuous words.
466 These are the words whose spam-stat differs the most from 0.5. 490 These are the words whose spam-stat differs the most from 0.5.
467 The list returned contains elements of the form \(WORD SCORE DIFF), 491 The list returned contains elements of the form \(WORD SCORE DIFF),
468 where DIFF is the difference between SCORE and 0.5." 492 where DIFF is the difference between SCORE and 0.5."
469 (with-spam-stat-max-buffer-size 493 (let (result word score)
470 (with-syntax-table spam-stat-syntax-table 494 (maphash (lambda (word ignore)
471 (let (result word score) 495 (setq score (spam-stat-score-word word)
472 (maphash (lambda (word ignore) 496 result (cons (list word score (abs (- score 0.5)))
473 (setq score (spam-stat-score-word word) 497 result)))
474 result (cons (list word score (abs (- score 0.5))) 498 (spam-stat-buffer-words))
475 result))) 499 (setq result (sort result (lambda (a b) (< (nth 2 b) (nth 2 a)))))
476 (spam-stat-buffer-words)) 500 (setcdr (nthcdr 14 result) nil)
477 (setq result (sort result (lambda (a b) (< (nth 2 b) (nth 2 a))))) 501 result))
478 (setcdr (nthcdr 14 result) nil)
479 result))))
480 502
481 (defun spam-stat-score-buffer () 503 (defun spam-stat-score-buffer ()
482 "Return a score describing the spam-probability for this buffer." 504 "Return a score describing the spam-probability for this buffer.
505 Add user supplied modifications if supplied."
506 (interactive) ; helps in debugging.
483 (setq spam-stat-score-data (spam-stat-buffer-words-with-scores)) 507 (setq spam-stat-score-data (spam-stat-buffer-words-with-scores))
484 (let* ((probs (mapcar (lambda (e) (cadr e)) spam-stat-score-data)) 508 (let* ((probs (mapcar 'cadr spam-stat-score-data))
485 (prod (apply #'* probs))) 509 (prod (apply #'* probs))
486 (/ prod (+ prod (apply #'* (mapcar #'(lambda (x) (- 1 x)) 510 (score0
487 probs)))))) 511 (/ prod (+ prod (apply #'* (mapcar #'(lambda (x) (- 1 x))
512 probs)))))
513 (score1s
514 (condition-case
515 spam-stat-error-holder
516 (spam-stat-score-buffer-user score0)
517 (error nil)))
518 (ans
519 (if score1s (+ score0 score1s) score0)))
520 (when (interactive-p)
521 (message "%S" ans))
522 ans))
523
524 (defun spam-stat-score-buffer-user (&rest args)
525 (let* ((scores
526 (mapcar
527 (lambda (fn)
528 (apply fn args))
529 spam-stat-score-buffer-user-functions)))
530 (if (memq nil scores) nil
531 (apply #'+ scores))))
488 532
489 (defun spam-stat-split-fancy () 533 (defun spam-stat-split-fancy ()
490 "Return the name of the spam group if the current mail is spam. 534 "Return the name of the spam group if the current mail is spam.
491 Use this function on `nnmail-split-fancy'. If you are interested in 535 Use this function on `nnmail-split-fancy'. If you are interested in
492 the raw data used for the last run of `spam-stat-score-buffer', 536 the raw data used for the last run of `spam-stat-score-buffer',
493 check the variable `spam-stat-score-data'." 537 check the variable `spam-stat-score-data'."
494 (condition-case var 538 (condition-case spam-stat-error-holder
495 (progn 539 (progn
496 (set-buffer spam-stat-buffer) 540 (set-buffer spam-stat-buffer)
497 (goto-char (point-min)) 541 (goto-char (point-min))
498 (when (> (spam-stat-score-buffer) spam-stat-split-fancy-spam-threshhold) 542 (when (> (spam-stat-score-buffer) spam-stat-split-fancy-spam-threshold)
499 (when (boundp 'nnmail-split-trace) 543 (when (boundp 'nnmail-split-trace)
500 (mapc (lambda (entry) 544 (mapc (lambda (entry)
501 (push entry nnmail-split-trace)) 545 (push entry nnmail-split-trace))
502 spam-stat-score-data)) 546 spam-stat-score-data))
503 spam-stat-split-fancy-spam-group)) 547 spam-stat-split-fancy-spam-group))
504 (error (message "Error in spam-stat-split-fancy: %S" var) 548 (error (message "Error in spam-stat-split-fancy: %S" spam-stat-error-holder)
505 nil))) 549 nil)))
506 550
507 ;; Testing 551 ;; Testing
552
553 (defun spam-stat-strip-xref ()
554 "Strip the the Xref header."
555 (save-restriction
556 (mail-narrow-to-head)
557 (when (re-search-forward "^Xref:.*\n" nil t)
558 (delete-region (match-beginning 0) (match-end 0)))))
508 559
509 (defun spam-stat-process-directory (dir func) 560 (defun spam-stat-process-directory (dir func)
510 "Process all the regular files in directory DIR using function FUNC." 561 "Process all the regular files in directory DIR using function FUNC."
511 (let* ((files (directory-files dir t "^[^.]")) 562 (let* ((files (directory-files dir t "^[^.]"))
512 (max (/ (length files) 100.0)) 563 (max (/ (length files) 100.0))
513 (count 0)) 564 (count 0))
514 (with-temp-buffer 565 (with-temp-buffer
515 (dolist (f files) 566 (dolist (f files)
516 (when (and (file-readable-p f) 567 (when (and (file-readable-p f)
517 (file-regular-p f) 568 (file-regular-p f)
518 (> (nth 7 (file-attributes f)) 0)) 569 (> (nth 7 (file-attributes f)) 0)
570 (< (time-to-number-of-days (time-since (nth 5 (file-attributes f))))
571 spam-stat-process-directory-age))
519 (setq count (1+ count)) 572 (setq count (1+ count))
520 (message "Reading %s: %.2f%%" dir (/ count max)) 573 (message "Reading %s: %.2f%%" dir (/ count max))
521 (insert-file-contents f) 574 (insert-file-contents-literally f)
575 (spam-stat-strip-xref)
522 (funcall func) 576 (funcall func)
523 (erase-buffer)))))) 577 (erase-buffer))))))
524 578
525 (defun spam-stat-process-spam-directory (dir) 579 (defun spam-stat-process-spam-directory (dir)
526 "Process all the regular files in directory DIR as spam." 580 "Process all the regular files in directory DIR as spam."
535 (defun spam-stat-count () 589 (defun spam-stat-count ()
536 "Return size of `spam-stat'." 590 "Return size of `spam-stat'."
537 (interactive) 591 (interactive)
538 (hash-table-count spam-stat)) 592 (hash-table-count spam-stat))
539 593
540 (defun spam-stat-test-directory (dir) 594 (defun spam-stat-test-directory (dir &optional verbose)
541 "Test all the regular files in directory DIR for spam. 595 "Test all the regular files in directory DIR for spam.
542 If the result is 1.0, then all files are considered spam. 596 If the result is 1.0, then all files are considered spam.
543 If the result is 0.0, non of the files is considered spam. 597 If the result is 0.0, non of the files is considered spam.
544 You can use this to determine error rates." 598 You can use this to determine error rates.
545 (interactive "D") 599
600 If VERBOSE is non-nil display names of files detected as spam or
601 non-spam in a temporary buffer. If it is the symbol `ham',
602 display non-spam files; otherwise display spam files."
603 (interactive "DDirectory: ")
546 (let* ((files (directory-files dir t "^[^.]")) 604 (let* ((files (directory-files dir t "^[^.]"))
605 display-files
606 buffer-score
547 (total (length files)) 607 (total (length files))
548 (score 0.0); float 608 (score 0.0); float
549 (max (/ total 100.0)); float 609 (max (/ total 100.0)); float
550 (count 0)) 610 (count 0))
551 (with-temp-buffer 611 (with-temp-buffer
552 (dolist (f files) 612 (dolist (f files)
553 (when (and (file-readable-p f) 613 (when (and (file-readable-p f)
554 (file-regular-p f) 614 (file-regular-p f)
555 (> (nth 7 (file-attributes f)) 0)) 615 (> (nth 7 (file-attributes f)) 0))
556 (setq count (1+ count)) 616 (setq count (1+ count))
557 (message "Reading %.2f%%, score %.2f%%" 617 (message "Reading %.2f%%, score %.2f"
558 (/ count max) (/ score count)) 618 (/ count max) (/ score count))
559 (insert-file-contents f) 619 (insert-file-contents-literally f)
560 (when (> (spam-stat-score-buffer) 0.9) 620 (setq buffer-score (spam-stat-score-buffer))
621 (when (> buffer-score 0.9)
561 (setq score (1+ score))) 622 (setq score (1+ score)))
623 (when verbose
624 (if (> buffer-score 0.9)
625 (unless (eq verbose 'ham) (push f display-files))
626 (when (eq verbose 'ham) (push f display-files))))
562 (erase-buffer)))) 627 (erase-buffer))))
628 (when display-files
629 (with-output-to-temp-buffer "*spam-stat results*"
630 (dolist (file display-files)
631 (princ file)
632 (terpri))))
563 (message "Final score: %d / %d = %f" score total (/ score total)))) 633 (message "Final score: %d / %d = %f" score total (/ score total))))
564 634
565 ;; Shrinking the dictionary 635 ;; Shrinking the dictionary
566 636
567 (defun spam-stat-reduce-size (&optional count) 637 (defun spam-stat-reduce-size (&optional count)
577 (remhash key spam-stat))) 647 (remhash key spam-stat)))
578 spam-stat) 648 spam-stat)
579 (setq spam-stat-dirty t)) 649 (setq spam-stat-dirty t))
580 650
581 (defun spam-stat-install-hooks-function () 651 (defun spam-stat-install-hooks-function ()
582 "Install the spam-stat function hooks" 652 "Install the spam-stat function hooks."
583 (interactive) 653 (interactive)
584 (add-hook 'nnmail-prepare-incoming-message-hook 654 (add-hook 'nnmail-prepare-incoming-message-hook
585 'spam-stat-store-current-buffer) 655 'spam-stat-store-current-buffer)
586 (add-hook 'gnus-select-article-hook 656 (add-hook 'gnus-select-article-hook
587 'spam-stat-store-gnus-article-buffer)) 657 'spam-stat-store-gnus-article-buffer))
588 658
589 (when spam-stat-install-hooks 659 (when spam-stat-install-hooks
590 (spam-stat-install-hooks-function)) 660 (spam-stat-install-hooks-function))
591 661
592 (defun spam-stat-unload-hook () 662 (defun spam-stat-unload-hook ()
593 "Uninstall the spam-stat function hooks" 663 "Uninstall the spam-stat function hooks."
594 (interactive) 664 (interactive)
595 (remove-hook 'nnmail-prepare-incoming-message-hook 665 (remove-hook 'nnmail-prepare-incoming-message-hook
596 'spam-stat-store-current-buffer) 666 'spam-stat-store-current-buffer)
597 (remove-hook 'gnus-select-article-hook 667 (remove-hook 'gnus-select-article-hook
598 'spam-stat-store-gnus-article-buffer)) 668 'spam-stat-store-gnus-article-buffer))