Update FSF's address.
[bpt/emacs.git] / lisp / gnus-edit.el
1 ;;; gnus-edit.el --- Gnus SCORE file editing
2
3 ;; Copyright (C) 1995 Free Software Foundation, Inc.
4
5 ;; Author: Per Abrahamsen <abraham@iesd.auc.dk>
6 ;; Keywords: news, help
7 ;; Version: 0.2
8
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
26 ;;; Commentary:
27
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
39 SCORE files allow you to assign a score to each article when you enter
40 a group, and automatically mark the articles as read or delete them
41 based on the score. In the summary buffer you can use the score to
42 sort the articles by score (`C-c C-s C-s') or to jump to the unread
43 article with the highest score (`,').")
44 (type . group)
45 (data "\n"
46 ((header . nil)
47 (doc . "Name of SCORE file to customize.
48
49 Enter the name in the `File' field, then push the [Load] button to
50 load it. When done editing, push the [Save] button to save the file.
51
52 Several score files may apply to each group, and several groups may
53 use the same score file. This is controlled implicitly by the name of
54 the 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 . "\
73 List of score files to load when the the current score file is loaded.
74 You can use this to share score entries between multiple score files.
75
76 Push the `[INS]' button add a score file to the list, or `[DEL]' to
77 delete 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 . "\
86 List of score files to exclude when the the current score file is loaded.
87 You can use this if you have a score file you want to share between a
88 number of newsgroups, except for the newsgroup this score file
89 matches. [ Did anyone get that? ]
90
91 Push the `[INS]' button add a score file to the list, or `[DEL]' to
92 delete 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 . "\
101 Articles below this score will be automatically marked as read.
102
103 This means that when you enter the summary buffer, the articles will
104 be shown but will already be marked as read. You can then press `x'
105 to get rid of them entirely.
106
107 By default articles with a negative score will be marked as read. To
108 change this, push the `Mark' button, and choose `Integer'. You can
109 then enter a value in the `Mark' field.")
110 (type . gnus-score-custom-maybe-type))
111 ((name . expunge)
112 (tag . "Expunge")
113 (doc . "\
114 Articles 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 . "\
119 Articles below this score will be marked as read, but not shown.
120
121 Someone should explain me the difference between this and `expunge'
122 alone or combined with `mark'.")
123 (type . gnus-score-custom-maybe-type))
124 ((name . eval)
125 (tag . "Eval")
126 (doc . "\
127 Evaluate 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.
132 Except from this buffer, of course!")
133 (type . toggle))
134 ((type . doc)
135 (doc . "\
136 Each news header has an associated list of score entries.
137 You can use the [INS] buttons to add new score entries anywhere in the
138 list, or the [DEL] buttons to delete specific score entries.
139
140 Each score entry should specify a string that should be matched with
141 the content actual header in order to determine whether the entry
142 applies to that header. Enter that string in the `Match' field.
143
144 If the score entry matches, the articles score will be adjusted with
145 some amount. Enter that amount in the in the `Score' field. You
146 should specify a positive amount for score entries that matches
147 articles you find interesting, and a negative amount for score entries
148 matching articles you would rather avoid. The final score for the
149 article will be the sum of the score of all score entries that match
150 the article.
151
152 The score entry can be either permanent or expirable. To make the
153 entry permanent, push the `Date' button and choose the `Permanent'
154 entry. To make the entry expirable, choose instead the `Integer'
155 entry. After choosing the you can enter the date the score entry was
156 last matched in the `Date' field. The date will be automatically
157 updated each time the score entry matches an article. When the date
158 become too old, the the score entry will be removed.
159
160 For your convenience, the date is specified as the number of days
161 elapsed since the (imaginary) Gregorian date Sunday, December 31, 1
162 BC.
163
164 Finally, you can choose what kind of match you want to perform by
165 pushing the `Type' button. For most entries you can choose between
166 `Exact' which mean the header content must be exactly identical to the
167 match string, or `Substring' meaning the match string should be
168 somewhere in the header content, or even `Regexp' to use Emacs regular
169 expression matching. The last choice is `Fuzzy' which is like `Exact'
170 except that whitespace derivations, a beginning `Re:' or a terminating
171 parenthetical remark are all ignored. Each of the four types have a
172 variant which will ignore case in the comparison. That variant is
173 indicated 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
186 If you want to see all followups to your own articles, add an entry
187 with a positive score matching your email address here. You can also
188 put an entry with a negative score matching someone who is so annoying
189 that 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
195 If you want to score based on which newsgroups an article is posted
196 to, this is the header to use. The syntax is a little different from
197 the `Newsgroups' header, but scoring in `Xref' is much faster. As an
198 example, 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
205 The `References' header gives you an alternative way to score on
206 followups. If you for example want to see follow all discussions
207 where people from `iesd.auc.dk' school participate, you can add a
208 substring 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
214 This isn't very useful, but Lars like completeness. You can use it to
215 match all messaged generated by recent Gnus version with a `Substring'
216 match on `.fsf@'.")
217 (type . gnus-score-custom-string-type))
218 ((type . doc)
219 (doc . "\
220 WARNING: Scoring on the following three pseudo headers is very slow!
221 Scoring on any of the real headers use a technique that avoids
222 scanning the entire article, only the actual headers you score on are
223 scanned, and this scanning has been heavily optimized. Using just a
224 single entry for one the three pseudo-headers `Head', `Body', and
225 `All' will require GNUS to retrieve and scan the entire article, which
226 can be very slow on large groups. However, if you add one entry for
227 any of these headers, you can just as well add several. Each
228 subsequent entry cost relatively little extra time."))
229 ((name . head)
230 (tag . "Head")
231 (doc . "Scoring based on the article header.
232
233 Instead of matching the content of a single header, the entire header
234 section of the article is matched. You can use this to match on
235 arbitrary headers, foe example to single out TIN lusers, use a substring
236 match 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
242 If you think any article that mentions `Kibo' is inherently
243 interesting, 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
253 You can change the score of articles that have been posted before,
254 after, or at a specific date. You should add the date in the `Match'
255 field, and then select `before', `after', or `at' by pushing the
256 `Type' button. Imagine you want to lower the score of very old
257 articles, or want to raise the score of articles from the future (such
258 things happen!). Then you can't use date scoring for that. In fact,
259 I can't imagine anything you would want to use this for.
260
261 For your convenience, the date is specified in Usenet date format.")
262 (type . gnus-score-custom-date-type))
263 ((type . doc)
264 (doc . "\
265 The Lines and Chars headers use integer based scoring.
266
267 This means that you should write an integer in the `Match' field, and
268 the push the `Type' field to if the `Chars' or `Lines' header should
269 be larger, equal, or smaller than the number you wrote in the match
270 field."))
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
287 When you have finished reading a group GNUS can automatically create
288 new score entries based on which articles you read and which you
289 skipped. This is normally controlled by the two global variables
290 `gnus-use-adaptive-scoring' and `gnus-default-adaptive-score-alist',
291 The first determines whether adaptive scoring should be enabled or
292 not, while the second determines what score entries should be created.
293
294 You can overwrite the setting of `gnus-use-adaptive-scoring' by
295 selecting `Enable' or `Disable' by pressing the `Adapt' button.
296 Selecting `Custom' will allow you to specify the exact adaptation
297 rules (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
311 Each time you read an article it will be marked as read. Likewise, if
312 you delete it it will be marked as deleted, and if you tick it it will
313 be marked as ticked. When you leave a group, GNUS can automatically
314 create score file entries based on these marks, so next time you enter
315 the group articles with subjects that you read last time have higher
316 score and articles with subjects that deleted will have lower score.
317
318 Below is a list of such marks. You can insert new marks to the list
319 by pushing on one of the `[INS]' buttons in the left margin to create
320 a new entry and then pushing the `Mark' button to select the mark.
321 For each mark there is another list, this time of article headers,
322 which determine how the mark should affect that header. The `[INS]'
323 buttons of this list are indented to indicate that the belong to the
324 mark above. Push the `Header' button to choose a header, and then
325 enter a score value in the `Score' field.
326
327 For each article that are marked with `Mark' when you leave the
328 group, a temporary score entry for the articles `Header' with the
329 value of `Score' will be added the adapt file. If the score entry
330 already exists, `Score' will be added to its value. If you understood
331 that, you are smart.
332
333 You can select the special value `Other' when pressing the `Mark' or
334 `Header' buttons. This is because Lars might add more useful values
335 there. 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 . "\
406 List of local variables to set when this score file is loaded.
407
408 Using this entry can provide a convenient way to set variables that
409 will affect the summary mode for only some specific groups, i.e. those
410 groups 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