Update FSF's address.
[bpt/emacs.git] / lisp / gnus-edit.el
CommitLineData
41487370 1;;; gnus-edit.el --- Gnus SCORE file editing
b578f267 2
41487370 3;; Copyright (C) 1995 Free Software Foundation, Inc.
b578f267 4
41487370
LMI
5;; Author: Per Abrahamsen <abraham@iesd.auc.dk>
6;; Keywords: news, help
7;; Version: 0.2
8
b578f267
EN
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation; either version 2, or (at your option)
14;; any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24;; Boston, MA 02111-1307, USA.
25
41487370 26;;; Commentary:
b578f267 27
41487370
LMI
28;; Type `M-x gnus-score-customize RET' to invoke.
29
30;;; Code:
31
32(require 'custom)
33(require 'gnus-score)
34
35(defconst gnus-score-custom-data
36 '((tag . "Score")
37 (doc . "Customization of Gnus SCORE files.
38
39SCORE files allow you to assign a score to each article when you enter
40a group, and automatically mark the articles as read or delete them
41based on the score. In the summary buffer you can use the score to
42sort the articles by score (`C-c C-s C-s') or to jump to the unread
43article with the highest score (`,').")
44 (type . group)
45 (data "\n"
46 ((header . nil)
47 (doc . "Name of SCORE file to customize.
48
49Enter the name in the `File' field, then push the [Load] button to
50load it. When done editing, push the [Save] button to save the file.
51
52Several score files may apply to each group, and several groups may
53use the same score file. This is controlled implicitly by the name of
54the score file and the value of the global variable
55`gnus-score-find-score-files-function', and explicitly by the the
56`Files' and `Exclude Files' entries.")
57 (compact . t)
58 (type . group)
59 (data ((tag . "Load")
60 (type . button)
61 (query . gnus-score-custom-load))
62 ((tag . "Save")
63 (type . button)
64 (query . gnus-score-custom-save))
65 ((name . file)
66 (tag . "File")
67 (directory . "~/News/")
68 (default-file . "SCORE")
69 (type . file))))
70 ((name . files)
71 (tag . "Files")
72 (doc . "\
73List of score files to load when the the current score file is loaded.
74You can use this to share score entries between multiple score files.
75
76Push the `[INS]' button add a score file to the list, or `[DEL]' to
77delete a score file from the list.")
78 (type . list)
79 (data ((type . repeat)
80 (header . nil)
81 (data (type . file)
82 (directory . "~/News/")))))
83 ((name . exclude-files)
84 (tag . "Exclude Files")
85 (doc . "\
86List of score files to exclude when the the current score file is loaded.
87You can use this if you have a score file you want to share between a
88number of newsgroups, except for the newsgroup this score file
89matches. [ Did anyone get that? ]
90
91Push the `[INS]' button add a score file to the list, or `[DEL]' to
92delete a score file from the list.")
93 (type . list)
94 (data ((type . repeat)
95 (header . nil)
96 (data (type . file)
97 (directory . "~/News/")))))
98 ((name . mark)
99 (tag . "Mark")
100 (doc . "\
101Articles below this score will be automatically marked as read.
102
103This means that when you enter the summary buffer, the articles will
104be shown but will already be marked as read. You can then press `x'
105to get rid of them entirely.
106
107By default articles with a negative score will be marked as read. To
108change this, push the `Mark' button, and choose `Integer'. You can
109then enter a value in the `Mark' field.")
110 (type . gnus-score-custom-maybe-type))
111 ((name . expunge)
112 (tag . "Expunge")
113 (doc . "\
114Articles below this score will not be shown in the summary buffer.")
115 (type . gnus-score-custom-maybe-type))
116 ((name . mark-and-expunge)
117 (tag . "Mark and Expunge")
118 (doc . "\
119Articles below this score will be marked as read, but not shown.
120
121Someone should explain me the difference between this and `expunge'
122alone or combined with `mark'.")
123 (type . gnus-score-custom-maybe-type))
124 ((name . eval)
125 (tag . "Eval")
126 (doc . "\
127Evaluate this lisp expression when the entering summary buffer.")
128 (type . sexp))
129 ((name . read-only)
130 (tag . "Read Only")
131 (doc . "Read-only score files will not be updated or saved.
132Except from this buffer, of course!")
133 (type . toggle))
134 ((type . doc)
135 (doc . "\
136Each news header has an associated list of score entries.
137You can use the [INS] buttons to add new score entries anywhere in the
138list, or the [DEL] buttons to delete specific score entries.
139
140Each score entry should specify a string that should be matched with
141the content actual header in order to determine whether the entry
142applies to that header. Enter that string in the `Match' field.
143
144If the score entry matches, the articles score will be adjusted with
145some amount. Enter that amount in the in the `Score' field. You
146should specify a positive amount for score entries that matches
147articles you find interesting, and a negative amount for score entries
148matching articles you would rather avoid. The final score for the
149article will be the sum of the score of all score entries that match
150the article.
151
152The score entry can be either permanent or expirable. To make the
153entry permanent, push the `Date' button and choose the `Permanent'
154entry. To make the entry expirable, choose instead the `Integer'
155entry. After choosing the you can enter the date the score entry was
156last matched in the `Date' field. The date will be automatically
157updated each time the score entry matches an article. When the date
158become too old, the the score entry will be removed.
159
160For your convenience, the date is specified as the number of days
161elapsed since the (imaginary) Gregorian date Sunday, December 31, 1
162BC.
163
164Finally, you can choose what kind of match you want to perform by
165pushing the `Type' button. For most entries you can choose between
166`Exact' which mean the header content must be exactly identical to the
167match string, or `Substring' meaning the match string should be
168somewhere in the header content, or even `Regexp' to use Emacs regular
169expression matching. The last choice is `Fuzzy' which is like `Exact'
170except that whitespace derivations, a beginning `Re:' or a terminating
171parenthetical remark are all ignored. Each of the four types have a
172variant which will ignore case in the comparison. That variant is
173indicated with a `(fold)' after its name."))
174 ((name . from)
175 (tag . "From")
176 (doc . "Scoring based on the authors email address.")
177 (type . gnus-score-custom-string-type))
178 ((name . subject)
179 (tag . "Subject")
180 (doc . "Scoring based on the articles subject.")
181 (type . gnus-score-custom-string-type))
182 ((name . followup)
183 (tag . "Followup")
184 (doc . "Scoring based on who the article is a followup to.
185
186If you want to see all followups to your own articles, add an entry
187with a positive score matching your email address here. You can also
188put an entry with a negative score matching someone who is so annoying
189that you don't even want to see him quoted in followups.")
190 (type . gnus-score-custom-string-type))
191 ((name . xref)
192 (tag . "Xref")
193 (doc . "Scoring based on article crossposting.
194
195If you want to score based on which newsgroups an article is posted
196to, this is the header to use. The syntax is a little different from
197the `Newsgroups' header, but scoring in `Xref' is much faster. As an
198example, to match all crossposted articles match on `:.*:' using the
199`Regexp' type.")
200 (type . gnus-score-custom-string-type))
201 ((name . references)
202 (tag . "References")
203 (doc . "Scoring based on article references.
204
205The `References' header gives you an alternative way to score on
206followups. If you for example want to see follow all discussions
207where people from `iesd.auc.dk' school participate, you can add a
208substring match on `iesd.auc.dk>' on this header.")
209 (type . gnus-score-custom-string-type))
210 ((name . message-id)
211 (tag . "Message-ID")
212 (doc . "Scoring based on the articles message-id.
213
214This isn't very useful, but Lars like completeness. You can use it to
215match all messaged generated by recent Gnus version with a `Substring'
216match on `.fsf@'.")
217 (type . gnus-score-custom-string-type))
218 ((type . doc)
219 (doc . "\
220WARNING: Scoring on the following three pseudo headers is very slow!
221Scoring on any of the real headers use a technique that avoids
222scanning the entire article, only the actual headers you score on are
223scanned, and this scanning has been heavily optimized. Using just a
224single entry for one the three pseudo-headers `Head', `Body', and
225`All' will require GNUS to retrieve and scan the entire article, which
226can be very slow on large groups. However, if you add one entry for
227any of these headers, you can just as well add several. Each
228subsequent entry cost relatively little extra time."))
229 ((name . head)
230 (tag . "Head")
231 (doc . "Scoring based on the article header.
232
233Instead of matching the content of a single header, the entire header
234section of the article is matched. You can use this to match on
235arbitrary headers, foe example to single out TIN lusers, use a substring
236match on `Newsreader: TIN'. That should get 'em!")
237 (type . gnus-score-custom-string-type))
238 ((name . body)
239 (tag . "Body")
240 (doc . "Scoring based on the article body.
241
242If you think any article that mentions `Kibo' is inherently
243interesting, do a substring match on His name. You Are Allowed.")
244 (type . gnus-score-custom-string-type))
245 ((name . all)
246 (tag . "All")
247 (doc . "Scoring based on the whole article.")
248 (type . gnus-score-custom-string-type))
249 ((name . date)
250 (tag . "Date")
251 (doc . "Scoring based on article date.
252
253You can change the score of articles that have been posted before,
254after, or at a specific date. You should add the date in the `Match'
255field, and then select `before', `after', or `at' by pushing the
256`Type' button. Imagine you want to lower the score of very old
257articles, or want to raise the score of articles from the future (such
258things happen!). Then you can't use date scoring for that. In fact,
259I can't imagine anything you would want to use this for.
260
261For your convenience, the date is specified in Usenet date format.")
262 (type . gnus-score-custom-date-type))
263 ((type . doc)
264 (doc . "\
265The Lines and Chars headers use integer based scoring.
266
267This means that you should write an integer in the `Match' field, and
268the push the `Type' field to if the `Chars' or `Lines' header should
269be larger, equal, or smaller than the number you wrote in the match
270field."))
271 ((name . chars)
272 (tag . "Characters")
273 (doc . "Scoring based on the number of characters in the article.")
274 (type . gnus-score-custom-integer-type))
275 ((name . lines)
276 (tag . "Lines")
277 (doc . "Scoring based on the number of lines in the article.")
278 (type . gnus-score-custom-integer-type))
279 ((name . orphan)
280 (tag . "Orphan")
281 (doc . "Score to add to articles with no parents.")
282 (type . gnus-score-custom-maybe-type))
283 ((name . adapt)
284 (tag . "Adapt")
285 (doc . "Adapting the score files to your newsreading habits.
286
287When you have finished reading a group GNUS can automatically create
288new score entries based on which articles you read and which you
abf4db5d 289skipped. This is normally controlled by the two global variables
41487370
LMI
290`gnus-use-adaptive-scoring' and `gnus-default-adaptive-score-alist',
291The first determines whether adaptive scoring should be enabled or
292not, while the second determines what score entries should be created.
293
294You can overwrite the setting of `gnus-use-adaptive-scoring' by
295selecting `Enable' or `Disable' by pressing the `Adapt' button.
abf4db5d 296Selecting `Custom' will allow you to specify the exact adaptation
41487370
LMI
297rules (overwriting `gnus-default-adaptive-score-alist').")
298 (type . choice)
299 (data ((tag . "Default")
300 (default . nil)
301 (type . const))
302 ((tag . "Enable")
303 (default . t)
304 (type . const))
305 ((tag . "Disable")
306 (default . ignore)
307 (type . const))
308 ((tag . "Custom")
309 (doc . "Customization of adaptive scoring.
310
311Each time you read an article it will be marked as read. Likewise, if
312you delete it it will be marked as deleted, and if you tick it it will
313be marked as ticked. When you leave a group, GNUS can automatically
314create score file entries based on these marks, so next time you enter
315the group articles with subjects that you read last time have higher
316score and articles with subjects that deleted will have lower score.
317
318Below is a list of such marks. You can insert new marks to the list
319by pushing on one of the `[INS]' buttons in the left margin to create
320a new entry and then pushing the `Mark' button to select the mark.
321For each mark there is another list, this time of article headers,
322which determine how the mark should affect that header. The `[INS]'
323buttons of this list are indented to indicate that the belong to the
324mark above. Push the `Header' button to choose a header, and then
325enter a score value in the `Score' field.
326
327For each article that are marked with `Mark' when you leave the
328group, a temporary score entry for the articles `Header' with the
329value of `Score' will be added the adapt file. If the score entry
330already exists, `Score' will be added to its value. If you understood
331that, you are smart.
332
333You can select the special value `Other' when pressing the `Mark' or
334`Header' buttons. This is because Lars might add more useful values
335there. If he does, it is up to you to figure out what they are named.")
336 (type . list)
337 (default . ((__uninitialized__)))
338 (data ((type . repeat)
339 (header . nil)
340 (data . ((type . list)
341 (header . nil)
342 (compact . t)
343 (data ((type . choice)
344 (tag . "Mark")
345 (data ((tag . "Unread")
346 (default . gnus-unread-mark)
347 (type . const))
348 ((tag . "Ticked")
349 (default . gnus-ticked-mark)
350 (type . const))
351 ((tag . "Dormant")
352 (default . gnus-dormant-mark)
353 (type . const))
354 ((tag . "Deleted")
355 (default . gnus-del-mark)
356 (type . const))
357 ((tag . "Read")
358 (default . gnus-read-mark)
359 (type . const))
360 ((tag . "Expirable")
361 (default . gnus-expirable-mark)
362 (type . const))
363 ((tag . "Killed")
364 (default . gnus-killed-mark)
365 (type . const))
366 ((tag . "Kill-file")
367 (default . gnus-kill-file-mark)
368 (type . const))
369 ((tag . "Low-score")
370 (default . gnus-low-score-mark)
371 (type . const))
372 ((tag . "Catchup")
373 (default . gnus-catchup-mark)
374 (type . const))
375 ((tag . "Ancient")
376 (default . gnus-ancient-mark)
377 (type . const))
378 ((tag . "Canceled")
379 (default . gnus-canceled-mark)
380 (type . const))
381 ((prompt . "Other")
382 (default . ??)
383 (type . sexp))))
384 ((type . repeat)
385 (prefix . " ")
386 (data . ((type . list)
387 (compact . t)
388 (data ((tag . "Header")
389 (type . choice)
390 (data ((tag . "Subject")
391 (default . subject)
392 (type . const))
393 ((prompt . "From")
394 (tag . "From ")
395 (default . from)
396 (type . const))
397 ((prompt . "Other")
398 (width . 7)
399 (default . nil)
400 (type . symbol))))
401 ((tag . "Score")
402 (type . integer))))))))))))))
403 ((name . local)
404 (tag . "Local")
405 (doc . "\
406List of local variables to set when this score file is loaded.
407
408Using this entry can provide a convenient way to set variables that
409will affect the summary mode for only some specific groups, i.e. those
410groups matched by the current score file.")
411 (type . list)
412 (data ((type . repeat)
413 (header . nil)
414 (data . ((type . list)
415 (compact . t)
416 (data ((tag . "Name")
417 (width . 26)
418 (type . symbol))
419 ((tag . "Value")
420 (width . 26)
421 (type . sexp)))))))))))
422
423(defconst gnus-score-custom-type-properties
424 '((gnus-score-custom-maybe-type
425 (type . choice)
426 (data ((type . integer)
427 (default . 0))
428 ((tag . "Default")
429 (type . const)
430 (default . nil))))
431 (gnus-score-custom-string-type
432 (type . list)
433 (data ((type . repeat)
434 (header . nil)
435 (data . ((type . list)
436 (compact . t)
437 (data ((tag . "Match")
438 (width . 59)
439 (type . string))
440 "\n "
441 ((tag . "Score")
442 (type . integer))
443 ((tag . "Date")
444 (type . choice)
445 (data ((type . integer)
446 (default . 0)
447 (width . 9))
448 ((tag . "Permanent")
449 (type . const)
450 (default . nil))))
451 ((tag . "Type")
452 (type . choice)
453 (data ((tag . "Exact")
454 (default . E)
455 (type . const))
456 ((tag . "Substring")
457 (default . S)
458 (type . const))
459 ((tag . "Regexp")
460 (default . R)
461 (type . const))
462 ((tag . "Fuzzy")
463 (default . F)
464 (type . const))
465 ((tag . "Exact (fold)")
466 (default . e)
467 (type . const))
468 ((tag . "Substring (fold)")
469 (default . s)
470 (type . const))
471 ((tag . "Regexp (fold)")
472 (default . r)
473 (type . const))
474 ((tag . "Fuzzy (fold)")
475 (default . f)
476 (type . const))))))))))
477 (gnus-score-custom-integer-type
478 (type . list)
479 (data ((type . repeat)
480 (header . nil)
481 (data . ((type . list)
482 (compact . t)
483 (data ((tag . "Match")
484 (type . integer))
485 ((tag . "Score")
486 (type . integer))
487 ((tag . "Date")
488 (type . choice)
489 (data ((type . integer)
490 (default . 0)
491 (width . 9))
492 ((tag . "Permanent")
493 (type . const)
494 (default . nil))))
495 ((tag . "Type")
496 (type . choice)
497 (data ((tag . "<")
498 (default . <)
499 (type . const))
500 ((tag . ">")
501 (default . >)
502 (type . const))
503 ((tag . "=")
504 (default . =)
505 (type . const))
506 ((tag . ">=")
507 (default . >=)
508 (type . const))
509 ((tag . "<=")
510 (default . <=)
511 (type . const))))))))))
512 (gnus-score-custom-date-type
513 (type . list)
514 (data ((type . repeat)
515 (header . nil)
516 (data . ((type . list)
517 (compact . t)
518 (data ((tag . "Match")
519 (width . 59)
520 (type . string))
521 "\n "
522 ((tag . "Score")
523 (type . integer))
524 ((tag . "Date")
525 (type . choice)
526 (data ((type . integer)
527 (default . 0)
528 (width . 9))
529 ((tag . "Permanent")
530 (type . const)
531 (default . nil))))
532 ((tag . "Type")
533 (type . choice)
534 (data ((tag . "Before")
535 (default . before)
536 (type . const))
537 ((tag . "After")
538 (default . after)
539 (type . const))
540 ((tag . "At")
541 (default . at)
542 (type . const))))))))))))
543
544(defvar gnus-score-custom-file nil
545 "Name of SCORE file being customized.")
546
547(defun gnus-score-customize ()
548 "Create a buffer for editing gnus SCORE files."
549 (interactive)
550 (let (gnus-score-alist)
551 (custom-buffer-create "*Score Edit*" gnus-score-custom-data
552 gnus-score-custom-type-properties
553 'gnus-score-custom-set
554 'gnus-score-custom-get
555 'gnus-score-custom-save))
556 (make-local-variable 'gnus-score-custom-file)
557 (setq gnus-score-custom-file (expand-file-name "SCORE" "~/News"))
558 (make-local-variable 'gnus-score-alist)
559 (setq gnus-score-alist nil)
560 (custom-reset-all))
561
562(defun gnus-score-custom-get (name)
563 (if (eq name 'file)
564 gnus-score-custom-file
565 (let ((entry (assoc (symbol-name name) gnus-score-alist)))
566 (if entry
567 (mapcar 'gnus-score-custom-sanify (cdr entry))
568 (setq entry (assoc name gnus-score-alist))
569 (if (or (memq name '(files exclude-files local))
570 (and (eq name 'adapt)
571 (not (symbolp (car (cdr entry))))))
572 (cdr entry)
573 (car (cdr entry)))))))
574
575(defun gnus-score-custom-set (name value)
576 (cond ((eq name 'file)
577 (setq gnus-score-custom-file value))
578 ((assoc (symbol-name name) gnus-score-alist)
579 (if value
580 (setcdr (assoc (symbol-name name) gnus-score-alist) value)
581 (setq gnus-score-alist (delq (assoc (symbol-name name)
582 gnus-score-alist)
583 gnus-score-alist))))
584 ((assoc (symbol-name name) gnus-header-index)
585 (if value
586 (setq gnus-score-alist
587 (cons (cons (symbol-name name) value) gnus-score-alist))))
588 ((assoc name gnus-score-alist)
589 (cond ((null value)
590 (setq gnus-score-alist (delq (assoc name gnus-score-alist)
591 gnus-score-alist)))
592 ((and (listp value) (not (eq name 'eval)))
593 (setcdr (assoc name gnus-score-alist) value))
594 (t
595 (setcdr (assoc name gnus-score-alist) (list value)))))
596 ((null value))
597 ((and (listp value) (not (eq name 'eval)))
598 (setq gnus-score-alist (cons (cons name value) gnus-score-alist)))
599 (t
600 (setq gnus-score-alist
601 (cons (cons name (list value)) gnus-score-alist)))))
602
603(defun gnus-score-custom-sanify (entry)
604 (list (nth 0 entry)
605 (or (nth 1 entry) gnus-score-interactive-default-score)
606 (nth 2 entry)
607 (cond ((null (nth 3 entry))
608 's)
609 ((memq (nth 3 entry) '(before after at >= <=))
610 (nth 3 entry))
611 (t
612 (intern (substring (symbol-name (nth 3 entry)) 0 1))))))
613
614(defvar gnus-score-cache nil)
615
616(defun gnus-score-custom-load ()
617 (interactive)
618 (let ((file (custom-name-value 'file)))
619 (if (eq file custom-nil)
620 (error "You must specify a file name"))
621 (setq file (expand-file-name file "~/News"))
622 (gnus-score-load file)
623 (setq gnus-score-custom-file file)
624 (custom-reset-all)
625 (message "Loaded")))
626
627(defun gnus-score-custom-save ()
628 (interactive)
629 (custom-apply-all)
630 (gnus-score-remove-from-cache gnus-score-custom-file)
631 (let ((file gnus-score-custom-file)
632 (score gnus-score-alist)
633 emacs-lisp-mode-hook)
634 (save-excursion
635 (set-buffer (get-buffer-create "*Score*"))
636 (buffer-disable-undo (current-buffer))
637 (erase-buffer)
638 (pp score (current-buffer))
639 (gnus-make-directory (file-name-directory file))
640 (write-region (point-min) (point-max) file nil 'silent)
641 (kill-buffer (current-buffer))))
642 (message "Saved"))
643
644(provide 'gnus-edit)
645
646;;; gnus-edit.el end here