*** empty log message ***
[gnus] / lisp / gnus-edit.el
1 ;;; gnus-edit.el --- Gnus SCORE file editing.
2 ;; Copyright (C) 1995 Free Software Foundation, Inc.
3 ;;
4 ;; Author: Per Abrahamsen <abraham@iesd.auc.dk>
5 ;; Keywords: news, help
6 ;; Version: 0.0
7
8 ;;; Commentary:
9 ;;
10 ;; Type `M-x gnus-score-customize RET' to invoke.
11
12 ;;; Code:
13
14 (require 'custom)
15
16 (autoload 'gnus-score-load "gnus-score")
17
18 (defconst gnus-score-custom-data
19   '((tag . "Score")
20     (doc . "Customization of Gnus SCORE files.
21
22 SCORE files allow you to assign a score to each article when you enter
23 a group, and automatically mark the articles as read or delete them
24 based on the score.  In the summary buffer you can use the score to
25 sort the articles by score (`C-c C-s C-s') or to jump to the unread
26 article with the highest score (`,').")
27     (type . group)
28     (data ""
29           ((header . nil)
30            (doc . "Name of SCORE file to customize.
31
32 Enter the name in the `File' field, then push the [Load] button to
33 load it.  When done editing, push the [Save] button to save the file.
34
35 Several score files may apply to each group, and several groups may
36 use the same score file.  This is controlled implicitly by the name of
37 the score file and the value of the global variable
38 `gnus-score-find-score-files-function', and explicitly by the the
39 `Files' and `Exclude Files' entries.") 
40            (compact . t)
41            (type . group)
42            (data ((tag . "Load")
43                   (type . button)
44                   (query . gnus-score-custom-load))
45                  ((tag . "Save")
46                   (type . button)
47                   (query . gnus-score-custom-save))
48                  ((name . file)
49                   (tag . "File")
50                   (directory . "~/News/")
51                   (default-file . "SCORE")
52                   (type . file))))
53           ((name . files)
54            (tag . "Files")
55            (doc . "\
56 List of score files to load when the the current score file is loaded.
57 You can use this to share score entries between multiple score files.
58
59 Push the `[INS]' button add a score file to the list, or `[DEL]' to
60 delete a score file from the list.")
61            (type . list)
62            (data ((type . repeat)
63                   (header . nil)
64                   (data (type . file)
65                         (directory . "~/News/")))))
66           ((name . exclude-files)
67            (tag . "Exclude Files")
68            (doc . "\
69 List of score files to exclude when the the current score file is loaded.
70 You can use this if you have a score file you want to share between a
71 number of newsgroups, except for the newsgroup this score file
72 matches.  [ Did anyone get that? ]
73
74 Push the `[INS]' button add a score file to the list, or `[DEL]' to
75 delete a score file from the list.")
76            (type . list)
77            (data ((type . repeat)
78                   (header . nil)
79                   (data (type . file)
80                         (directory . "~/News/")))))
81           ((name . mark)
82            (tag . "Mark")
83            (doc . "\
84 Articles below this score will be automatically marked as read.
85
86 This means that when you enter the summary buffer, the articles will
87 be shown but will already be marked as read.  You can then press `x'
88 to get rid of them entirely.
89
90 By default articles with a negative score will be marked as read.  To
91 change this, push the `Mark' button, and choose `Integer'.  You can
92 then enter a value in the `Mark' field.")
93            (type . gnus-score-custom-maybe-type))
94           ((name . expunge)
95            (tag . "Expunge")
96            (doc . "\
97 Articles below this score will not be shown in the summary buffer.")
98            (type . gnus-score-custom-maybe-type))
99           ((name . mark-and-expunge)
100            (tag . "Mark and Expunge")
101            (doc . "\
102 Articles below this score will be marked as read, but not shown.
103
104 Someone should explain me the difference between this and `expunge'
105 alone or combined with `mark'.")
106            (type . gnus-score-custom-maybe-type))
107 ;         ;; Sexp type isn't implemented yet.
108 ;         ((name . eval)
109 ;          (tag . "Eval")
110 ;          (doc . "Evaluate this expression when the entering sumamry buffer.")
111 ;          (type . sexp))
112           ;; Toggle type isn't implemented yet.
113           ((name . read-only)
114            (tag . "Read Only")
115            (doc . "Read-only score files will not be updated or saved.
116 Except from this buffer, of course!")
117            (type . toggle))
118           ((type . doc)
119            (header . nil)
120            (doc . "\
121 Each news header has an associated list of score entries.  
122 You can use the [INS] buttons to add new score entries anywhere in the
123 list, or the [DEL] buttons to delete specific score entries.
124
125 Each score entry should specify a string that should be matched with
126 the content actual header in order to determine whether the entry
127 applies to that header.  Enter that string in the `Match' field.
128
129 If the score entry matches, the articles score will be adjusted with
130 some amount.  Enter that amount in the in the `Score' field.  You
131 should specify a positive amount for score entries that matches
132 articles you find interesting, and a negative amount for score entries
133 matching articles you would rather avoid.  The final score for the
134 article will be the sum of the score of all score entries that match
135 the article. 
136
137 The score entry can be either permanent or expirable.  To make the
138 entry permanent, push the `Date' button and choose the `Permanent'
139 entry.  To make the entry expirable, choose instead the `Integer'
140 entry.  After choosing the you can enter the date the score entry was
141 last matched in the `Date' field.  The date will be automatically
142 updated each time the score entry matches an article.  When the date
143 become too old, the the score entry will be removed.
144
145 For your convenience, the date is specified as the number of days
146 elapsed since the (imaginary) Gregorian date Sunday, December 31, 1
147 BC.
148
149 Finally, you can choose what kind of match you want to perform by
150 pushing the `Type' button.  For most entries you can choose between
151 `Exact' which mean the header content must be exactly identical to the
152 match string, or `Substring' meaning the match string should be
153 somewhere in the header content, or even `Regexp' to use Emacs regular
154 expression matching.  The last choice is `Fuzzy' which is like `Exact'
155 except that whitespace derivations, a beginning `Re:' or a terminating
156 parenthetical remark are all ignored.  Each of the four types have a
157 variant which will ignore case in the comparison.  That variant is
158 indicated with a `(fold)' after its name."))
159           ((name . from)
160            (tag . "From")
161            (doc . "Scoring based on the authors email address.")
162            (type . gnus-score-custom-string-type))
163           ((name . subject)
164            (tag . "Subject")
165            (doc . "Scoring based on the articles subject.")
166            (type . gnus-score-custom-string-type))
167           ((name . followup)
168            (tag . "Followup")
169            (doc . "Scoring based on who the article is a followup to.
170
171 If you want to see all followups to your own articles, add an entry
172 with a positive score matching your email address here.  You can also
173 put an entry with a negative score matching someone who is so annoying
174 that you don't even want to see him quoted in followups.")
175            (type . gnus-score-custom-string-type))
176           ((name . xref)
177            (tag . "Xref")
178            (doc . "Scoring based on article crossposting.
179
180 If you want to score based on which newsgroups an article is posted
181 to, this is the header to use.  The syntax is a little different from
182 the `Newsgroups' header, but scoring in `Xref' is much faster.  As an
183 example, to match all crossposted articles match on `:.*:' using the
184 `Regexp' type.")
185            (type . gnus-score-custom-string-type))
186           ((name . references)
187            (tag . "References")
188            (doc . "Scoring based on article references.
189
190 The `References' header gives you an alternative way to score on
191 followups.  If you for example want to see follow all discussions
192 where people from `iesd.auc.dk' school participate, you can add a
193 substring match on `iesd.auc.dk>' on this header.")
194            (type . gnus-score-custom-string-type))
195           ((name . message-id)
196            (tag . "Message-ID")
197            (doc . "Scoring based on the articles message-id.
198
199 This isn't very useful, but Lars like completeness.  You can use it to
200 match all messaged generated by recent Gnus version with a `Substring'
201 match on `.fsf@'.")
202            (type . gnus-score-custom-string-type))
203           ((type . doc)
204            (header . nil)
205            (doc . "\
206 WARNING:  Scoring on the following three pseudo headers is very slow!
207 Scoring on any of the real headers use a technique that avoids
208 scanning the entire article, only the actual headers you score on are
209 scanned, and this scanning has been heavily optimized.  Using just a
210 single entry for one the three pseudo-headers `Head', `Body', and
211 `All' will require GNUS to retrieve and scan the entire article, which
212 can be very slow on large groups.  However, if you add one entry for
213 any of these headers, you can just as well add several.  Each
214 subsequent entry cost relatively little extra time."))
215           ((name . head)
216            (tag . "Head")
217            (doc . "Scoring based on the article header.
218
219 Instead of matching the content of a single header, the entire header
220 section of the article is matched.  You can use this to match on
221 arbitrary headers, foe example to single out TIN lusers, use a substring
222 match on `Newsreader: TIN'.  That should get 'em!")
223            (type . gnus-score-custom-string-type))
224           ((name . body)
225            (tag . "Body")
226            (doc . "Scoring based on the article body.
227
228 If you think any article that mentions `Kibo' is inherently
229 interesting, do a substring match on His name.  You Are Allowed.")
230            (type . gnus-score-custom-string-type))
231           ((name . all)
232            (tag . "All")
233            (doc . "Scoring based on the whole article.")
234            (type . gnus-score-custom-string-type))
235           ((name . date)
236            (tag . "Date")
237            (doc . "Scoring based on article date.
238
239 You can change the score of articles that have been posted before,
240 after, or at a specific date.  You should add the date in the `Match'
241 field, and then select `before', `after', or `at' by pushing the
242 `Type' button.  Imagine you want to lower the score of very old
243 articles, or want to raise the score of articles from the future (such
244 things happen!).  Then you can't use date scoring for that.  In fact,
245 I can't imagine anything you would want to use this for.   
246
247 For your convenience, the date is specified in Usenet date format.")
248            (type . gnus-score-custom-date-type))
249           ((type . doc)
250            (header . nil)
251            (doc . "\
252 The Lines and Chars headers use integer based scoring.  
253
254 This means that you should write an integer in the `Match' field, and
255 the push the `Type' field to if the `Chars' or `Lines' header should
256 be larger, equal, or smaller than the number you wrote in the match
257 field."))
258           ((name . chars)
259            (tag . "Characters")
260            (doc . "Scoring based on the number of characters in the article.")
261            (type . gnus-score-custom-integer-type))
262           ((name . lines)
263            (tag . "Lines")
264            (doc . "Scoring based on the number of lines in the article.")
265            (type . gnus-score-custom-integer-type))
266           ((name . orphan)
267            (tag . "Orphan")
268            (doc . "Score to add to articles with no parents.")
269            (type . gnus-score-custom-maybe-type)))))  
270 ;; This is to complex for me to figure out right now.
271 ;`adapt'
272 ;     This entry controls the adaptive scoring.  If it is `t', the
273 ;     default adaptive scoring rules will be used.  If it is `ignore', no
274 ;     adaptive scoring will be performed on this group.  If it is a
275 ;     list, this list will be used as the adaptive scoring rules.  If it
276 ;     isn't present, or is something other than `t' or `ignore', the
277 ;     default adaptive scoring rules will be used.  If you want to use
278 ;     adaptive scoring on most groups, you'd set
279 ;     `gnus-use-adaptive-scoring' to `t', and insert an `(adapt ignore)'
280 ;     in the groups where you do not want adaptive scoring.  If you only
281 ;     want adaptive scoring in a few groups, you'd set
282 ;     `gnus-use-adaptive-scoring' to `nil', and insert `(adapt t)' in
283 ;     the score files of the groups where you want it.
284 ;; This isn't implemented in the old version of (ding) I use.
285 ;`local'
286 ;  List of local variables to bind in the summary buffer.
287
288 (defconst gnus-score-custom-type-properties
289   '((gnus-score-custom-maybe-type
290      (type . choice)
291      (data ((type . integer)
292             (default . 0))
293            ((tag . "Default")
294             (type . const)
295             (default . nil))))
296     (gnus-score-custom-string-type
297      (type . list)
298      (data ((type . repeat)
299             (header . nil)
300             (data . ((type . list)
301                      (compact . t)
302                      (data ((tag . "Match")
303                             (width . 59)
304                             (type . string))
305                            "\n           "
306                            ((tag . "Score")
307                             (type . integer))
308                            ((tag . "Date")
309                             (type . choice)
310                             (data ((type . integer)
311                                    (default . 0)
312                                    (width . 9))
313                                   ((tag . "Permanent")
314                                    (type . const)
315                                    (default . nil))))
316                            ((tag . "Type")
317                             (type . choice)
318                             (data ((tag . "Exact")
319                                    (default . e)
320                                    (type . const))
321                                   ((tag . "Substring")
322                                    (default . s) 
323                                    (type . const))
324                                   ((tag . "Regexp")
325                                    (default . r)
326                                    (type . const))
327                                   ((tag . "Fuzzy")
328                                    (default . f)
329                                    (type . const))
330                                   ((tag . "Exact (fold)")
331                                    (default . E)
332                                    (type . const))
333                                   ((tag . "Substring (fold)")
334                                    (default . S) 
335                                    (type . const))
336                                   ((tag . "Regexp (fold)")
337                                    (default . R)
338                                    (type . const))
339                                   ((tag . "Fuzzy  (fold)")
340                                    (default . F)
341                                    (type . const))))))))))
342     (gnus-score-custom-integer-type
343      (type . list)
344      (data ((type . repeat)
345             (header . nil)
346             (data . ((type . list)
347                      (compact . t)
348                      (data ((tag . "Match")
349                             (type . integer))
350                            ((tag . "Score")
351                             (type . integer))
352                            ((tag . "Date")
353                             (type . choice)
354                             (data ((type . integer)
355                                    (default . 0)
356                                    (width . 9))
357                                   ((tag . "Permanent")
358                                    (type . const)
359                                    (default . nil))))
360                            ((tag . "Type")
361                             (type . choice)
362                             (data ((tag . "<")
363                                    (default . <)
364                                    (type . const))
365                                   ((tag . ">")
366                                    (default . >) 
367                                    (type . const))
368                                   ((tag . "=")
369                                    (default . =)
370                                    (type . const))
371                                   ((tag . ">=")
372                                    (default . >=)
373                                    (type . const))
374                                   ((tag . "<=")
375                                    (default . <=)
376                                    (type . const))))))))))
377     (gnus-score-custom-date-type
378      (type . list)
379      (data ((type . repeat)
380             (header . nil)
381             (data . ((type . list)
382                      (compact . t)
383                      (data ((tag . "Match")
384                             (width . 59)
385                             (type . string))
386                            "\n           "
387                            ((tag . "Score")
388                             (type . integer))
389                            ((tag . "Date")
390                             (type . choice)
391                             (data ((type . integer)
392                                    (default . 0)
393                                    (width . 9))
394                                   ((tag . "Permanent")
395                                    (type . const)
396                                    (default . nil))))
397                            ((tag . "Type")
398                             (type . choice)
399                             (data ((tag . "Before")
400                                    (default . before)
401                                    (type . const))
402                                   ((tag . "After")
403                                    (default . after) 
404                                    (type . const))
405                                   ((tag . "At")
406                                    (default . at)
407                                    (type . const))))))))))))
408
409 (defvar gnus-score-custom-file nil
410   "Name of SCORE file being customized.")
411
412 (defun gnus-score-customize ()
413   "Create a buffer for editing gnus SCORE files."
414   (interactive)
415   (let (gnus-score-alist)
416     (custom-buffer-create "*Score Edit*" gnus-score-custom-data
417                           gnus-score-custom-type-properties
418                           'gnus-score-custom-set
419                           'gnus-score-custom-get))
420   (make-local-variable 'gnus-score-custom-file)
421   (setq gnus-score-custom-file "SCORE")
422   (make-local-variable 'gnus-score-alist)
423   (setq gnus-score-alist nil)
424   (custom-reset-all))
425
426 (defun gnus-score-custom-get (name)
427   (if (eq name 'file)
428       gnus-score-custom-file
429     (let ((entry (assoc (symbol-name name) gnus-score-alist)))
430       (if entry 
431           (mapcar 'gnus-score-custom-sanify (cdr entry))
432         (setq entry (assoc name gnus-score-alist))
433         (if (memq name '(files))
434             (cdr entry)
435           (car (cdr entry)))))))
436
437 (defun gnus-score-custom-set (name value)
438   (cond ((eq name 'file)
439          (setq gnus-score-custom-file value))
440         ((assoc (symbol-name name) gnus-score-alist)
441          (if value
442              (setcdr (assoc (symbol-name name) gnus-score-alist) value)
443            (setq gnus-score-alist (delq (assoc (symbol-name name) 
444                                                gnus-score-alist) 
445                                         gnus-score-alist))))
446         ((assoc (symbol-name name) gnus-header-index)
447          (if value
448              (setq gnus-score-alist 
449                    (cons (cons (symbol-name name) value) gnus-score-alist))))
450         ((assoc name gnus-score-alist)
451          (cond ((null value)
452                 (setq gnus-score-alist (delq (assoc name gnus-score-alist)
453                                              gnus-score-alist)))
454                ((listp value)
455                 (setcdr (assoc name gnus-score-alist) value))
456                (t
457                 (setcdr (assoc name gnus-score-alist) (list value)))))
458         ((null value))
459         ((litsp value)
460          (setq gnus-score-alist (cons (cons name value) gnus-score-alist)))
461         (t
462          (setq gnus-score-alist 
463                (cons (cons name (list value)) gnus-score-alist)))))
464
465 (defun gnus-score-custom-sanify (entry)
466   (list (nth 0 entry)
467         (or (nth 1 entry) gnus-score-interactive-default-score)
468         (nth 2 entry)
469         (if (null (nth 3 entry)) 
470             's
471           (intern (substring (symbol-name (nth 3 entry)) 0 1)))))
472
473 (defvar gnus-score-cache nil)
474
475 (defun gnus-score-custom-load ()
476   (interactive)
477   (let ((file (custom-name-value 'file)))
478     (if (eq file custom-nil)
479         (error "You must specify a file name"))
480     (setq file (expand-file-name file "~/News"))
481     (gnus-score-load file)
482     (setq gnus-score-custom-file file)
483     (custom-reset-all)
484     (message "Loaded")))
485
486 (defun gnus-score-custom-save ()
487   (interactive)
488   (custom-apply-all)
489   (gnus-score-remove-from-cache gnus-score-custom-file)
490   (let ((file gnus-score-custom-file)
491         (score gnus-score-alist)
492         emacs-lisp-mode-hook)
493     (save-excursion
494       (set-buffer (get-buffer-create "*Score*"))
495       (buffer-disable-undo (current-buffer))
496       (erase-buffer)
497       (pp score (current-buffer))
498       (gnus-make-directory (file-name-directory file))
499       (write-region (point-min) (point-max) file nil 'silent)
500       (kill-buffer (current-buffer))))
501   (message "Saved"))
502
503 (provide 'gnus-edit)
504
505 ;;; gnus-edit.el end here