comparison lisp/gnus/gnus-score.el @ 98:0d2f883870bc r20-1b1

Import from CVS: tag r20-1b1
author cvs
date Mon, 13 Aug 2007 09:13:56 +0200
parents 131b0175ea99
children 4be1180a9e89
comparison
equal deleted inserted replaced
97:498bf5da1c90 98:0d2f883870bc
1 ;;; gnus-score.el --- scoring code for Gnus 1 ;;; gnus-score.el --- scoring code for Gnus
2 ;; Copyright (C) 1995,96 Free Software Foundation, Inc. 2 ;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
3 3
4 ;; Author: Per Abrahamsen <amanda@iesd.auc.dk> 4 ;; Author: Per Abrahamsen <amanda@iesd.auc.dk>
5 ;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no> 5 ;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
6 ;; Keywords: news 6 ;; Keywords: news
7 7
25 ;;; Commentary: 25 ;;; Commentary:
26 26
27 ;;; Code: 27 ;;; Code:
28 28
29 (require 'gnus) 29 (require 'gnus)
30 (eval-when-compile (require 'cl)) 30 (require 'gnus-sum)
31 31 (require 'gnus-range)
32 (defvar gnus-global-score-files nil 32
33 "*List of global score files and directories. 33 (defcustom gnus-global-score-files nil
34 "List of global score files and directories.
34 Set this variable if you want to use people's score files. One entry 35 Set this variable if you want to use people's score files. One entry
35 for each score file or each score file directory. Gnus will decide 36 for each score file or each score file directory. Gnus will decide
36 by itself what score files are applicable to which group. 37 by itself what score files are applicable to which group.
37 38
38 Say you want to use the single score file 39 Say you want to use the single score file
39 \"/ftp.ifi.uio.no@ftp:/pub/larsi/ding/score/soc.motss.SCORE\" and all 40 \"/ftp.ifi.uio.no@ftp:/pub/larsi/ding/score/soc.motss.SCORE\" and all
40 score files in the \"/ftp.some-where:/pub/score\" directory. 41 score files in the \"/ftp.some-where:/pub/score\" directory.
41 42
42 (setq gnus-global-score-files 43 (setq gnus-global-score-files
43 '(\"/ftp.ifi.uio.no:/pub/larsi/ding/score/soc.motss.SCORE\" 44 '(\"/ftp.ifi.uio.no:/pub/larsi/ding/score/soc.motss.SCORE\"
44 \"/ftp.some-where:/pub/score\"))") 45 \"/ftp.some-where:/pub/score\"))"
45 46 :group 'gnus-score
46 (defvar gnus-score-file-single-match-alist nil 47 :type '(repeat file))
47 "*Alist mapping regexps to lists of score files. 48
49 (defcustom gnus-score-file-single-match-alist nil
50 "Alist mapping regexps to lists of score files.
48 Each element of this alist should be of the form 51 Each element of this alist should be of the form
49 (\"REGEXP\" [ \"SCORE-FILE-1\" ] [ \"SCORE-FILE-2\" ] ... ) 52 (\"REGEXP\" [ \"SCORE-FILE-1\" ] [ \"SCORE-FILE-2\" ] ... )
50 53
51 If the name of a group is matched by REGEXP, the corresponding scorefiles 54 If the name of a group is matched by REGEXP, the corresponding scorefiles
52 will be used for that group. 55 will be used for that group.
53 The first match found is used, subsequent matching entries are ignored (to 56 The first match found is used, subsequent matching entries are ignored (to
54 use multiple matches, see gnus-score-file-multiple-match-alist). 57 use multiple matches, see gnus-score-file-multiple-match-alist).
55 58
56 These score files are loaded in addition to any files returned by 59 These score files are loaded in addition to any files returned by
57 gnus-score-find-score-files-function (which see).") 60 gnus-score-find-score-files-function (which see)."
58 61 :group 'gnus-score
59 (defvar gnus-score-file-multiple-match-alist nil 62 :type '(repeat (cons regexp (repeat file))))
60 "*Alist mapping regexps to lists of score files. 63
64 (defcustom gnus-score-file-multiple-match-alist nil
65 "Alist mapping regexps to lists of score files.
61 Each element of this alist should be of the form 66 Each element of this alist should be of the form
62 (\"REGEXP\" [ \"SCORE-FILE-1\" ] [ \"SCORE-FILE-2\" ] ... ) 67 (\"REGEXP\" [ \"SCORE-FILE-1\" ] [ \"SCORE-FILE-2\" ] ... )
63 68
64 If the name of a group is matched by REGEXP, the corresponding scorefiles 69 If the name of a group is matched by REGEXP, the corresponding scorefiles
65 will be used for that group. 70 will be used for that group.
66 If multiple REGEXPs match a group, the score files corresponding to each 71 If multiple REGEXPs match a group, the score files corresponding to each
67 match will be used (for only one match to be used, see 72 match will be used (for only one match to be used, see
68 gnus-score-file-single-match-alist). 73 gnus-score-file-single-match-alist).
69 74
70 These score files are loaded in addition to any files returned by 75 These score files are loaded in addition to any files returned by
71 gnus-score-find-score-files-function (which see).") 76 gnus-score-find-score-files-function (which see)."
72 77 :group 'gnus-score
73 (defvar gnus-score-file-suffix "SCORE" 78 :type '(repeat (cons regexp (repeat file))))
74 "*Suffix of the score files.") 79
75 80 (defcustom gnus-score-file-suffix "SCORE"
76 (defvar gnus-adaptive-file-suffix "ADAPT" 81 "Suffix of the score files."
77 "*Suffix of the adaptive score files.") 82 :group 'gnus-score
78 83 :type 'string)
79 (defvar gnus-score-find-score-files-function 'gnus-score-find-bnews 84
80 "*Function used to find score files. 85 (defcustom gnus-adaptive-file-suffix "ADAPT"
86 "Suffix of the adaptive score files."
87 :group 'gnus-score
88 :type 'string)
89
90 (defcustom gnus-score-find-score-files-function 'gnus-score-find-bnews
91 "Function used to find score files.
81 The function will be called with the group name as the argument, and 92 The function will be called with the group name as the argument, and
82 should return a list of score files to apply to that group. The score 93 should return a list of score files to apply to that group. The score
83 files do not actually have to exist. 94 files do not actually have to exist.
84 95
85 Predefined values are: 96 Predefined values are:
90 101
91 See the documentation to these functions for more information. 102 See the documentation to these functions for more information.
92 103
93 This variable can also be a list of functions to be called. Each 104 This variable can also be a list of functions to be called. Each
94 function should either return a list of score files, or a list of 105 function should either return a list of score files, or a list of
95 score alists.") 106 score alists."
96 107 :group 'gnus-score
97 (defvar gnus-score-interactive-default-score 1000 108 :type '(radio (function-item gnus-score-find-single)
98 "*Scoring commands will raise/lower the score with this number as the default.") 109 (function-item gnus-score-find-hierarchical)
99 110 (function-item gnus-score-find-bnews)
100 (defvar gnus-score-expiry-days 7 111 (function :tag "Other")))
112
113 (defcustom gnus-score-interactive-default-score 1000
114 "*Scoring commands will raise/lower the score with this number as the default."
115 :group 'gnus-score
116 :type 'integer)
117
118 (defcustom gnus-score-expiry-days 7
101 "*Number of days before unused score file entries are expired. 119 "*Number of days before unused score file entries are expired.
102 If this variable is nil, no score file entries will be expired.") 120 If this variable is nil, no score file entries will be expired."
103 121 :group 'gnus-score
104 (defvar gnus-update-score-entry-dates t 122 :type '(choice (const :tag "never" nil)
123 number))
124
125 (defcustom gnus-update-score-entry-dates t
105 "*In non-nil, update matching score entry dates. 126 "*In non-nil, update matching score entry dates.
106 If this variable is nil, then score entries that provide matches 127 If this variable is nil, then score entries that provide matches
107 will be expired along with non-matching score entries.") 128 will be expired along with non-matching score entries."
108 129 :group 'gnus-score
109 (defvar gnus-orphan-score nil 130 :type 'boolean)
110 "*All orphans get this score added. Set in the score file.") 131
111 132 (defcustom gnus-orphan-score nil
112 (defvar gnus-default-adaptive-score-alist 133 "*All orphans get this score added. Set in the score file."
134 :group 'gnus-score
135 :type 'integer)
136
137 (defcustom gnus-decay-scores nil
138 "*If non-nil, decay non-permanent scores."
139 :group 'gnus-score
140 :type 'boolean)
141
142 (defcustom gnus-decay-score-function 'gnus-decay-score
143 "*Function called to decay a score.
144 It is called with one parameter -- the score to be decayed."
145 :group 'gnus-score
146 :type '(radio (function-item gnus-decay-score)
147 (function :tag "Other")))
148
149 (defcustom gnus-score-decay-constant 3
150 "*Decay all \"small\" scores with this amount."
151 :group 'gnus-score
152 :type 'integer)
153
154 (defcustom gnus-score-decay-scale .05
155 "*Decay all \"big\" scores with this factor."
156 :group 'gnus-score
157 :type 'number)
158
159 (defcustom gnus-home-score-file nil
160 "Variable to control where interactive score entries are to go.
161 It can be:
162
163 * A string
164 This file file will be used as the home score file.
165
166 * A function
167 The result of this function will be used as the home score file.
168 The function will be passed the name of the group as its
169 parameter.
170
171 * A list
172 The elements in this list can be:
173
174 * `(regexp file-name ...)'
175 If the `regexp' matches the group name, the first `file-name' will
176 will be used as the home score file. (Multiple filenames are
177 allowed so that one may use gnus-score-file-single-match-alist to
178 set this variable.)
179
180 * A function.
181 If the function returns non-nil, the result will be used
182 as the home score file. The function will be passed the
183 name of the group as its parameter.
184
185 * A string. Use the string as the home score file.
186
187 The list will be traversed from the beginning towards the end looking
188 for matches."
189 :group 'gnus-score
190 :type '(choice string
191 (repeat (choice string
192 (cons regexp (repeat file))
193 function))
194 function))
195
196 (defcustom gnus-home-adapt-file nil
197 "Variable to control where new adaptive score entries are to go.
198 This variable allows the same syntax as `gnus-home-score-file'."
199 :group 'gnus-score
200 :type '(choice string
201 (repeat (choice string
202 (cons regexp (repeat file))
203 function))
204 function))
205
206 (defcustom gnus-default-adaptive-score-alist
113 '((gnus-kill-file-mark) 207 '((gnus-kill-file-mark)
114 (gnus-unread-mark) 208 (gnus-unread-mark)
115 (gnus-read-mark (from 3) (subject 30)) 209 (gnus-read-mark (from 3) (subject 30))
116 (gnus-catchup-mark (subject -10)) 210 (gnus-catchup-mark (subject -10))
117 (gnus-killed-mark (from -1) (subject -20)) 211 (gnus-killed-mark (from -1) (subject -20))
118 (gnus-del-mark (from -2) (subject -15))) 212 (gnus-del-mark (from -2) (subject -15)))
119 "*Alist of marks and scores.") 213 "Alist of marks and scores."
120 214 :group 'gnus-score
121 (defvar gnus-score-mimic-keymap nil 215 :type '(repeat (cons (symbol :tag "Mark")
122 "*Have the score entry functions pretend that they are a keymap.") 216 (repeat (list (choice :tag "Header"
123 217 (const from)
124 (defvar gnus-score-exact-adapt-limit 10 218 (const subject)
219 (symbol :tag "other"))
220 (integer :tag "Score"))))))
221
222 (defcustom gnus-ignored-adaptive-words nil
223 "List of words to be ignored when doing adaptive word scoring."
224 :group 'gnus-score
225 :type '(repeat string))
226
227 (defcustom gnus-default-ignored-adaptive-words
228 '("a" "i" "the" "to" "of" "and" "in" "is" "it" "for" "that" "if" "you"
229 "this" "be" "on" "with" "not" "have" "are" "or" "as" "from" "can"
230 "but" "by" "at" "an" "will" "no" "all" "was" "do" "there" "my" "one"
231 "so" "we" "they" "what" "would" "any" "which" "about" "get" "your"
232 "use" "some" "me" "then" "name" "like" "out" "when" "up" "time"
233 "other" "more" "only" "just" "end" "also" "know" "how" "new" "should"
234 "been" "than" "them" "he" "who" "make" "may" "people" "these" "now"
235 "their" "here" "into" "first" "could" "way" "had" "see" "work" "well"
236 "were" "two" "very" "where" "while" "us" "because" "good" "same"
237 "even" "much" "most" "many" "such" "long" "his" "over" "last" "since"
238 "right" "before" "our" "without" "too" "those" "why" "must" "part"
239 "being" "current" "back" "still" "go" "point" "value" "each" "did"
240 "both" "true" "off" "say" "another" "state" "might" "under" "start"
241 "try" "re")
242 "Default list of words to be ignored when doing adaptive word scoring."
243 :group 'gnus-score
244 :type '(repeat string))
245
246 (defcustom gnus-default-adaptive-word-score-alist
247 `((,gnus-read-mark . 30)
248 (,gnus-catchup-mark . -10)
249 (,gnus-killed-mark . -20)
250 (,gnus-del-mark . -15))
251 "Alist of marks and scores."
252 :group 'gnus-score
253 :type '(repeat (cons (character :tag "Mark")
254 (integer :tag "Score"))))
255
256 (defcustom gnus-score-mimic-keymap nil
257 "*Have the score entry functions pretend that they are a keymap."
258 :group 'gnus-score
259 :type 'boolean)
260
261 (defcustom gnus-score-exact-adapt-limit 10
125 "*Number that says how long a match has to be before using substring matching. 262 "*Number that says how long a match has to be before using substring matching.
126 When doing adaptive scoring, one normally uses fuzzy or substring 263 When doing adaptive scoring, one normally uses fuzzy or substring
127 matching. However, if the header one matches is short, the possibility 264 matching. However, if the header one matches is short, the possibility
128 for false positives is great, so if the length of the match is less 265 for false positives is great, so if the length of the match is less
129 than this variable, exact matching will be used. 266 than this variable, exact matching will be used.
130 267
131 If this variable is nil, exact matching will always be used.") 268 If this variable is nil, exact matching will always be used."
132 269 :group 'gnus-score
133 (defvar gnus-score-uncacheable-files "ADAPT$" 270 :type '(choice (const nil) integer))
134 "*All score files that match this regexp will not be cached.") 271
135 272 (defcustom gnus-score-uncacheable-files "ADAPT$"
136 (defvar gnus-score-default-header nil 273 "All score files that match this regexp will not be cached."
274 :group 'gnus-score
275 :type 'regexp)
276
277 (defcustom gnus-score-default-header nil
137 "Default header when entering new scores. 278 "Default header when entering new scores.
138 279
139 Should be one of the following symbols. 280 Should be one of the following symbols.
140 281
141 a: from 282 a: from
147 x: xref 288 x: xref
148 l: lines 289 l: lines
149 d: date 290 d: date
150 f: followup 291 f: followup
151 292
152 If nil, the user will be asked for a header.") 293 If nil, the user will be asked for a header."
153 294 :group 'gnus-score
154 (defvar gnus-score-default-type nil 295 :type '(choice (const :tag "from" a)
296 (const :tag "subject" s)
297 (const :tag "body" b)
298 (const :tag "head" h)
299 (const :tag "message-id" i)
300 (const :tag "references" t)
301 (const :tag "xref" x)
302 (const :tag "lines" l)
303 (const :tag "date" d)
304 (const :tag "followup" f)))
305
306 (defcustom gnus-score-default-type nil
155 "Default match type when entering new scores. 307 "Default match type when entering new scores.
156 308
157 Should be one of the following symbols. 309 Should be one of the following symbols.
158 310
159 s: substring 311 s: substring
165 n: this date 317 n: this date
166 <: less than number 318 <: less than number
167 >: greater than number 319 >: greater than number
168 =: equal to number 320 =: equal to number
169 321
170 If nil, the user will be asked for a match type.") 322 If nil, the user will be asked for a match type."
171 323 :group 'gnus-score
172 (defvar gnus-score-default-fold nil 324 :type '(choice (const :tag "substring" s)
173 "Use case folding for new score file entries iff not nil.") 325 (const :tag "exact string" e)
174 326 (const :tag "fuzzy string" f)
175 (defvar gnus-score-default-duration nil 327 (const :tag "regexp string" r)
328 (const :tag "before date" b)
329 (const :tag "at date" a)
330 (const :tag "this date" n)
331 (const :tag "less than number" <)
332 (const :tag "greater than number" >)
333 (const :tag "equal than number" =)))
334
335 (defcustom gnus-score-default-fold nil
336 "Use case folding for new score file entries iff not nil."
337 :group 'gnus-score
338 :type 'boolean)
339
340 (defcustom gnus-score-default-duration nil
176 "Default duration of effect when entering new scores. 341 "Default duration of effect when entering new scores.
177 342
178 Should be one of the following symbols. 343 Should be one of the following symbols.
179 344
180 t: temporary 345 t: temporary
181 p: permanent 346 p: permanent
182 i: immediate 347 i: immediate
183 348
184 If nil, the user will be asked for a duration.") 349 If nil, the user will be asked for a duration."
185 350 :group 'gnus-score
186 (defvar gnus-score-after-write-file-function nil 351 :type '(choice (const :tag "temporary" t)
187 "*Function called with the name of the score file just written to disk.") 352 (const :tag "permanent" p)
353 (const :tag "immediate" i)))
354
355 (defcustom gnus-score-after-write-file-function nil
356 "Function called with the name of the score file just written to disk."
357 :group 'gnus-score
358 :type 'function)
188 359
189 360
190 361
191 ;; Internal variables. 362 ;; Internal variables.
192 363
364 (defvar gnus-adaptive-word-syntax-table
365 (let ((table (copy-syntax-table (standard-syntax-table)))
366 (numbers '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)))
367 (while numbers
368 (modify-syntax-entry (pop numbers) " " table))
369 (modify-syntax-entry ?' "w" table)
370 table)
371 "Syntax table used when doing adaptive word scoring.")
372
373 (defvar gnus-scores-exclude-files nil)
193 (defvar gnus-internal-global-score-files nil) 374 (defvar gnus-internal-global-score-files nil)
194 (defvar gnus-score-file-list nil) 375 (defvar gnus-score-file-list nil)
195 376
196 (defvar gnus-short-name-score-file-cache nil) 377 (defvar gnus-short-name-score-file-cache nil)
197 378
198 (defvar gnus-score-help-winconf nil) 379 (defvar gnus-score-help-winconf nil)
199 (defvar gnus-adaptive-score-alist gnus-default-adaptive-score-alist) 380 (defvar gnus-adaptive-score-alist gnus-default-adaptive-score-alist)
381 (defvar gnus-adaptive-word-score-alist gnus-default-adaptive-word-score-alist)
200 (defvar gnus-score-trace nil) 382 (defvar gnus-score-trace nil)
201 (defvar gnus-score-edit-buffer nil) 383 (defvar gnus-score-edit-buffer nil)
202 384
203 (defvar gnus-score-alist nil 385 (defvar gnus-score-alist nil
204 "Alist containing score information. 386 "Alist containing score information.
208 mark: Automatically mark articles below this. 390 mark: Automatically mark articles below this.
209 expunge: Automatically expunge articles below this. 391 expunge: Automatically expunge articles below this.
210 files: List of other score files to load when loading this one. 392 files: List of other score files to load when loading this one.
211 eval: Sexp to be evaluated when the score file is loaded. 393 eval: Sexp to be evaluated when the score file is loaded.
212 394
213 String entries have the form (HEADER (MATCH TYPE SCORE DATE) ...) 395 String entries have the form (HEADER (MATCH TYPE SCORE DATE) ...)
214 where HEADER is the header being scored, MATCH is the string we are 396 where HEADER is the header being scored, MATCH is the string we are
215 looking for, TYPE is a flag indicating whether it should use regexp or 397 looking for, TYPE is a flag indicating whether it should use regexp or
216 substring matching, SCORE is the score to add and DATE is the date 398 substring matching, SCORE is the score to add and DATE is the date
217 of the last successful match.") 399 of the last successful match.")
218 400
225 ;; Name to index alist. 407 ;; Name to index alist.
226 '(("number" 0 gnus-score-integer) 408 '(("number" 0 gnus-score-integer)
227 ("subject" 1 gnus-score-string) 409 ("subject" 1 gnus-score-string)
228 ("from" 2 gnus-score-string) 410 ("from" 2 gnus-score-string)
229 ("date" 3 gnus-score-date) 411 ("date" 3 gnus-score-date)
230 ("message-id" 4 gnus-score-string) 412 ("message-id" 4 gnus-score-string)
231 ("references" 5 gnus-score-string) 413 ("references" 5 gnus-score-string)
232 ("chars" 6 gnus-score-integer) 414 ("chars" 6 gnus-score-integer)
233 ("lines" 7 gnus-score-integer) 415 ("lines" 7 gnus-score-integer)
234 ("xref" 8 gnus-score-string) 416 ("xref" 8 gnus-score-string)
235 ("head" -1 gnus-score-body) 417 ("head" -1 gnus-score-body)
236 ("body" -1 gnus-score-body) 418 ("body" -1 gnus-score-body)
237 ("all" -1 gnus-score-body) 419 ("all" -1 gnus-score-body)
238 ("followup" 2 gnus-score-followup) 420 ("followup" 2 gnus-score-followup)
239 ("thread" 5 gnus-score-thread))) 421 ("thread" 5 gnus-score-thread)))
240 422
241 (eval-and-compile
242 (autoload 'gnus-uu-ctl-map "gnus-uu" nil nil 'keymap))
243
244 ;;; Summary mode score maps. 423 ;;; Summary mode score maps.
245 424
246 (gnus-define-keys 425 (gnus-define-keys (gnus-summary-score-map "V" gnus-summary-mode-map)
247 (gnus-summary-score-map "V" gnus-summary-mode-map) 426 "s" gnus-summary-set-score
248 "s" gnus-summary-set-score 427 "a" gnus-summary-score-entry
249 "a" gnus-summary-score-entry 428 "S" gnus-summary-current-score
250 "S" gnus-summary-current-score 429 "c" gnus-score-change-score-file
251 "c" gnus-score-change-score-file 430 "C" gnus-score-customize
252 "m" gnus-score-set-mark-below 431 "m" gnus-score-set-mark-below
253 "x" gnus-score-set-expunge-below 432 "x" gnus-score-set-expunge-below
254 "R" gnus-summary-rescore 433 "R" gnus-summary-rescore
255 "e" gnus-score-edit-current-scores 434 "e" gnus-score-edit-current-scores
256 "f" gnus-score-edit-file 435 "f" gnus-score-edit-file
257 "F" gnus-score-flush-cache 436 "F" gnus-score-flush-cache
258 "t" gnus-score-find-trace 437 "t" gnus-score-find-trace
259 "C" gnus-score-customize) 438 "w" gnus-score-find-favourite-words)
260 439
261 ;; Summary score file commands 440 ;; Summary score file commands
262 441
263 ;; Much modification of the kill (ahem, score) code and lots of the 442 ;; Much modification of the kill (ahem, score) code and lots of the
264 ;; functions are written by Per Abrahamsen <amanda@iesd.auc.dk>. 443 ;; functions are written by Per Abrahamsen <amanda@iesd.auc.dk>.
269 permanence, and the string to be used. The numerical prefix will be 448 permanence, and the string to be used. The numerical prefix will be
270 used as score." 449 used as score."
271 (interactive "P") 450 (interactive "P")
272 (gnus-summary-increase-score (- (gnus-score-default score)))) 451 (gnus-summary-increase-score (- (gnus-score-default score))))
273 452
274 (defvar gnus-score-default-header nil
275 "*The default header to score on when entering a score rule interactively.")
276
277 (defvar gnus-score-default-type nil
278 "*The default score type to use when entering a score rule interactively.")
279
280 (defvar gnus-score-default-duration nil
281 "*The default score duration to use on when entering a score rule interactively.")
282
283 (defun gnus-score-kill-help-buffer () 453 (defun gnus-score-kill-help-buffer ()
284 (when (get-buffer "*Score Help*") 454 (when (get-buffer "*Score Help*")
285 (kill-buffer "*Score Help*") 455 (kill-buffer "*Score Help*")
286 (and gnus-score-help-winconf 456 (when gnus-score-help-winconf
287 (set-window-configuration gnus-score-help-winconf)))) 457 (set-window-configuration gnus-score-help-winconf))))
288 458
289 (defun gnus-summary-increase-score (&optional score) 459 (defun gnus-summary-increase-score (&optional score)
290 "Make a score entry based on the current article. 460 "Make a score entry based on the current article.
291 The user will be prompted for header to score on, match type, 461 The user will be prompted for header to score on, match type,
292 permanence, and the string to be used. The numerical prefix will be 462 permanence, and the string to be used. The numerical prefix will be
312 '((?s s "substring" string) 482 '((?s s "substring" string)
313 (?e e "exact string" string) 483 (?e e "exact string" string)
314 (?f f "fuzzy string" string) 484 (?f f "fuzzy string" string)
315 (?r r "regexp string" string) 485 (?r r "regexp string" string)
316 (?z s "substring" body-string) 486 (?z s "substring" body-string)
317 (?p s "regexp string" body-string) 487 (?p r "regexp string" body-string)
318 (?b before "before date" date) 488 (?b before "before date" date)
319 (?a at "at date" date) 489 (?a at "at date" date)
320 (?n now "this date" date) 490 (?n now "this date" date)
321 (?< < "less than number" number) 491 (?< < "less than number" number)
322 (?> > "greater than number" number) 492 (?> > "greater than number" number)
323 (?= = "equal to number" number))) 493 (?= = "equal to number" number)))
324 (char-to-perm 494 (char-to-perm
325 (list (list ?t (current-time-string) "temporary") 495 (list (list ?t (current-time-string) "temporary")
326 '(?p perm "permanent") '(?i now "immediate"))) 496 '(?p perm "permanent") '(?i now "immediate")))
327 (mimic gnus-score-mimic-keymap) 497 (mimic gnus-score-mimic-keymap)
328 (hchar (and gnus-score-default-header 498 (hchar (and gnus-score-default-header
329 (aref (symbol-name gnus-score-default-header) 0))) 499 (aref (symbol-name gnus-score-default-header) 0)))
330 (tchar (and gnus-score-default-type 500 (tchar (and gnus-score-default-type
353 (gnus-score-kill-help-buffer) 523 (gnus-score-kill-help-buffer)
354 (unless (setq entry (assq (downcase hchar) char-to-header)) 524 (unless (setq entry (assq (downcase hchar) char-to-header))
355 (if mimic (error "%c %c" prefix hchar) (error ""))) 525 (if mimic (error "%c %c" prefix hchar) (error "")))
356 526
357 (when (/= (downcase hchar) hchar) 527 (when (/= (downcase hchar) hchar)
358 ;; This was a majuscle, so we end reading and set the defaults. 528 ;; This was a majuscule, so we end reading and set the defaults.
359 (if mimic (message "%c %c" prefix hchar) (message "")) 529 (if mimic (message "%c %c" prefix hchar) (message ""))
360 (setq tchar (or tchar ?s) 530 (setq tchar (or tchar ?s)
361 pchar (or pchar ?t))) 531 pchar (or pchar ?t)))
362 532
363 ;; We continue reading - the type. 533 ;; We continue reading - the type.
366 (progn 536 (progn
367 (sit-for 1) (message "%c %c-" prefix hchar)) 537 (sit-for 1) (message "%c %c-" prefix hchar))
368 (message "%s header '%s' with match type (%s?): " 538 (message "%s header '%s' with match type (%s?): "
369 (if increase "Increase" "Lower") 539 (if increase "Increase" "Lower")
370 (nth 1 entry) 540 (nth 1 entry)
371 (mapconcat (lambda (s) 541 (mapconcat (lambda (s)
372 (if (eq (nth 4 entry) 542 (if (eq (nth 4 entry)
373 (nth 3 s)) 543 (nth 3 s))
374 (char-to-string (car s)) 544 (char-to-string (car s))
375 "")) 545 ""))
376 char-to-type ""))) 546 char-to-type "")))
377 (setq tchar (read-char)) 547 (setq tchar (read-char))
378 (when (or (= tchar ??) (= tchar ?\C-h)) 548 (when (or (= tchar ??) (= tchar ?\C-h))
379 (setq tchar nil) 549 (setq tchar nil)
380 (gnus-score-insert-help 550 (gnus-score-insert-help
381 "Match type" 551 "Match type"
382 (delq nil 552 (delq nil
383 (mapcar (lambda (s) 553 (mapcar (lambda (s)
384 (if (eq (nth 4 entry) 554 (if (eq (nth 4 entry)
385 (nth 3 s)) 555 (nth 3 s))
386 s nil)) 556 s nil))
387 char-to-type )) 557 char-to-type))
388 2))) 558 2)))
389 559
390 (gnus-score-kill-help-buffer) 560 (gnus-score-kill-help-buffer)
391 (unless (setq type (nth 1 (assq (downcase tchar) char-to-type))) 561 (unless (setq type (nth 1 (assq (downcase tchar) char-to-type)))
392 (if mimic (error "%c %c" prefix hchar) (error ""))) 562 (if mimic (error "%c %c" prefix hchar) (error "")))
393 563
394 (when (/= (downcase tchar) tchar) 564 (when (/= (downcase tchar) tchar)
395 ;; It was a majuscle, so we end reading and use the default. 565 ;; It was a majuscule, so we end reading and use the default.
396 (if mimic (message "%c %c %c" prefix hchar tchar) 566 (if mimic (message "%c %c %c" prefix hchar tchar)
397 (message "")) 567 (message ""))
398 (setq pchar (or pchar ?p))) 568 (setq pchar (or pchar ?p)))
399 569
400 ;; We continue reading. 570 ;; We continue reading.
412 582
413 (gnus-score-kill-help-buffer) 583 (gnus-score-kill-help-buffer)
414 (if mimic (message "%c %c %c" prefix hchar tchar pchar) 584 (if mimic (message "%c %c %c" prefix hchar tchar pchar)
415 (message "")) 585 (message ""))
416 (unless (setq temporary (cadr (assq pchar char-to-perm))) 586 (unless (setq temporary (cadr (assq pchar char-to-perm)))
587 ;; Deal with der(r)ided superannuated paradigms.
588 (when (and (eq (1+ prefix) 77)
589 (eq (+ hchar 12) 109)
590 (eq tchar 114)
591 (eq (- pchar 4) 111))
592 (error "You rang?"))
417 (if mimic 593 (if mimic
418 (error "%c %c %c %c" prefix hchar tchar pchar) 594 (error "%c %c %c %c" prefix hchar tchar pchar)
419 (error "")))) 595 (error ""))))
420 ;; Always kill the score help buffer. 596 ;; Always kill the score help buffer.
421 (gnus-score-kill-help-buffer)) 597 (gnus-score-kill-help-buffer))
437 613
438 (gnus-summary-score-entry 614 (gnus-summary-score-entry
439 (nth 1 entry) ; Header 615 (nth 1 entry) ; Header
440 match ; Match 616 match ; Match
441 type ; Type 617 type ; Type
442 (if (eq 's score) nil score) ; Score 618 (if (eq score 's) nil score) ; Score
443 (if (eq 'perm temporary) ; Temp 619 (if (eq temporary 'perm) ; Temp
444 nil 620 nil
445 temporary) 621 temporary)
446 (not (nth 3 entry))) ; Prompt 622 (not (nth 3 entry))) ; Prompt
447 )) 623 ))
448 624
449 (defun gnus-score-insert-help (string alist idx) 625 (defun gnus-score-insert-help (string alist idx)
450 (setq gnus-score-help-winconf (current-window-configuration)) 626 (setq gnus-score-help-winconf (current-window-configuration))
459 (i 0) 635 (i 0)
460 n width pad format) 636 n width pad format)
461 ;; find the longest string to display 637 ;; find the longest string to display
462 (while list 638 (while list
463 (setq n (length (nth idx (car list)))) 639 (setq n (length (nth idx (car list))))
464 (or (> max n) 640 (unless (> max n)
465 (setq max n)) 641 (setq max n))
466 (setq list (cdr list))) 642 (setq list (cdr list)))
467 (setq max (+ max 4)) ; %c, `:', SPACE, a SPACE at end 643 (setq max (+ max 4)) ; %c, `:', SPACE, a SPACE at end
468 (setq n (/ (1- (window-width)) max)) ; items per line 644 (setq n (/ (1- (window-width)) max)) ; items per line
469 (setq width (/ (1- (window-width)) n)) ; width of each item 645 (setq width (/ (1- (window-width)) n)) ; width of each item
470 ;; insert `n' items, each in a field of width `width' 646 ;; insert `n' items, each in a field of width `width'
471 (while alist 647 (while alist
472 (if (< i n) 648 (if (< i n)
473 () 649 ()
502 (error "No article on current line") 678 (error "No article on current line")
503 nil)))) 679 nil))))
504 680
505 (defun gnus-newsgroup-score-alist () 681 (defun gnus-newsgroup-score-alist ()
506 (or 682 (or
507 (let ((param-file (gnus-group-get-parameter 683 (let ((param-file (gnus-group-find-parameter
508 gnus-newsgroup-name 'score-file))) 684 gnus-newsgroup-name 'score-file)))
509 (when param-file 685 (when param-file
510 (gnus-score-load param-file))) 686 (gnus-score-load param-file)))
511 (gnus-score-load 687 (gnus-score-load
512 (gnus-score-file-name gnus-newsgroup-name))) 688 (gnus-score-file-name gnus-newsgroup-name)))
517 (cdr (assoc symbol 693 (cdr (assoc symbol
518 (or alist 694 (or alist
519 gnus-score-alist 695 gnus-score-alist
520 (gnus-newsgroup-score-alist))))) 696 (gnus-newsgroup-score-alist)))))
521 697
522 (defun gnus-summary-score-entry 698 (defun gnus-summary-score-entry (header match type score date
523 (header match type score date &optional prompt silent) 699 &optional prompt silent)
524 "Enter score file entry. 700 "Enter score file entry.
525 HEADER is the header being scored. 701 HEADER is the header being scored.
526 MATCH is the string we are looking for. 702 MATCH is the string we are looking for.
527 TYPE is the match type: substring, regexp, exact, fuzzy. 703 TYPE is the match type: substring, regexp, exact, fuzzy.
528 SCORE is the score to add. 704 SCORE is the score to add.
542 'now) 718 'now)
543 ((y-or-n-p "Expire kill? ") 719 ((y-or-n-p "Expire kill? ")
544 (current-time-string)) 720 (current-time-string))
545 (t nil)))) 721 (t nil))))
546 ;; Regexp is the default type. 722 ;; Regexp is the default type.
547 (if (eq type t) (setq type 'r)) 723 (when (eq type t)
724 (setq type 'r))
548 ;; Simplify matches... 725 ;; Simplify matches...
549 (cond ((or (eq type 'r) (eq type 's) (eq type nil)) 726 (cond ((or (eq type 'r) (eq type 's) (eq type nil))
550 (setq match (if match (gnus-simplify-subject-re match) ""))) 727 (setq match (if match (gnus-simplify-subject-re match) "")))
551 ((eq type 'f) 728 ((eq type 'f)
552 (setq match (gnus-simplify-subject-fuzzy match)))) 729 (setq match (gnus-simplify-subject-fuzzy match))))
553 (let ((score (gnus-score-default score)) 730 (let ((score (gnus-score-default score))
554 (header (format "%s" (downcase header))) 731 (header (format "%s" (downcase header)))
555 new) 732 new)
556 (and prompt (setq match (read-string 733 (when prompt
557 (format "Match %s on %s, %s: " 734 (setq match (read-string
558 (cond ((eq date 'now) 735 (format "Match %s on %s, %s: "
559 "now") 736 (cond ((eq date 'now)
560 ((stringp date) 737 "now")
561 "temp") 738 ((stringp date)
562 (t "permanent")) 739 "temp")
563 header 740 (t "permanent"))
564 (if (< score 0) "lower" "raise")) 741 header
565 (if (numberp match) 742 (if (< score 0) "lower" "raise"))
566 (int-to-string match) 743 (if (numberp match)
567 match)))) 744 (int-to-string match)
745 match))))
568 746
569 ;; Get rid of string props. 747 ;; Get rid of string props.
570 (setq match (format "%s" match)) 748 (setq match (format "%s" match))
571 749
572 ;; If this is an integer comparison, we transform from string to int. 750 ;; If this is an integer comparison, we transform from string to int.
573 (and (eq (nth 2 (assoc header gnus-header-index)) 'gnus-score-integer) 751 (when (eq (nth 2 (assoc header gnus-header-index)) 'gnus-score-integer)
574 (setq match (string-to-int match))) 752 (setq match (string-to-int match)))
575 753
576 (unless (eq date 'now) 754 (unless (eq date 'now)
577 ;; Add the score entry to the score file. 755 ;; Add the score entry to the score file.
578 (when (= score gnus-score-interactive-default-score) 756 (when (= score gnus-score-interactive-default-score)
579 (setq score nil)) 757 (setq score nil))
580 (let ((old (gnus-score-get header)) 758 (let ((old (gnus-score-get header))
581 elem) 759 elem)
582 (setq new 760 (setq new
583 (cond 761 (cond
584 (type (list match score (and date (gnus-day-number date)) type)) 762 (type
763 (list match score
764 (and date (if (numberp date) date
765 (gnus-day-number date)))
766 type))
585 (date (list match score (gnus-day-number date))) 767 (date (list match score (gnus-day-number date)))
586 (score (list match score)) 768 (score (list match score))
587 (t (list match)))) 769 (t (list match))))
588 ;; We see whether we can collapse some score entries. 770 ;; We see whether we can collapse some score entries.
589 ;; This isn't quite correct, because there may be more elements 771 ;; This isn't quite correct, because there may be more elements
590 ;; later on with the same key that have matching elems... Hm. 772 ;; later on with the same key that have matching elems... Hm.
591 (if (and old 773 (if (and old
592 (setq elem (assoc match old)) 774 (setq elem (assoc match old))
593 (eq (nth 3 elem) (nth 3 new)) 775 (eq (nth 3 elem) (nth 3 new))
594 (or (and (numberp (nth 2 elem)) (numberp (nth 2 new))) 776 (or (and (numberp (nth 2 elem)) (numberp (nth 2 new)))
595 (and (not (nth 2 elem)) (not (nth 2 new))))) 777 (and (not (nth 2 elem)) (not (nth 2 new)))))
596 ;; Yup, we just add this new score to the old elem. 778 ;; Yup, we just add this new score to the old elem.
597 (setcar (cdr elem) (+ (or (nth 1 elem) 779 (setcar (cdr elem) (+ (or (nth 1 elem)
598 gnus-score-interactive-default-score) 780 gnus-score-interactive-default-score)
599 (or (nth 1 new) 781 (or (nth 1 new)
600 gnus-score-interactive-default-score))) 782 gnus-score-interactive-default-score)))
601 ;; Nope, we have to add a new elem. 783 ;; Nope, we have to add a new elem.
602 (gnus-score-set header (if old (cons new old) (list new)))) 784 (gnus-score-set header (if old (cons new old) (list new))))
615 797
616 (defun gnus-summary-score-effect (header match type score) 798 (defun gnus-summary-score-effect (header match type score)
617 "Simulate the effect of a score file entry. 799 "Simulate the effect of a score file entry.
618 HEADER is the header being scored. 800 HEADER is the header being scored.
619 MATCH is the string we are looking for. 801 MATCH is the string we are looking for.
620 TYPE is a flag indicating if it is a regexp or substring. 802 TYPE is the score type.
621 SCORE is the score to add." 803 SCORE is the score to add."
622 (interactive (list (completing-read "Header: " 804 (interactive (list (completing-read "Header: "
623 gnus-header-index 805 gnus-header-index
624 (lambda (x) (fboundp (nth 2 x))) 806 (lambda (x) (fboundp (nth 2 x)))
625 t) 807 t)
626 (read-string "Match: ") 808 (read-string "Match: ")
627 (y-or-n-p "Use regexp match? ") 809 (y-or-n-p "Use regexp match? ")
628 (prefix-numeric-value current-prefix-arg))) 810 (prefix-numeric-value current-prefix-arg)))
629 (save-excursion 811 (save-excursion
630 (or (and (stringp match) (> (length match) 0)) 812 (unless (and (stringp match) (> (length match) 0))
631 (error "No match")) 813 (error "No match"))
632 (goto-char (point-min)) 814 (goto-char (point-min))
633 (let ((regexp (cond ((eq type 'f) 815 (let ((regexp (cond ((eq type 'f)
634 (gnus-simplify-subject-fuzzy match)) 816 (gnus-simplify-subject-fuzzy match))
635 ((eq type 'r) 817 ((eq type 'r)
636 match) 818 match)
637 ((eq type 'e) 819 ((eq type 'e)
638 (concat "\\`" (regexp-quote match) "\\'")) 820 (concat "\\`" (regexp-quote match) "\\'"))
639 (t 821 (t
640 (regexp-quote match))))) 822 (regexp-quote match)))))
641 (while (not (eobp)) 823 (while (not (eobp))
642 (let ((content (gnus-summary-header header 'noerr)) 824 (let ((content (gnus-summary-header header 'noerr))
643 (case-fold-search t)) 825 (case-fold-search t))
644 (and content 826 (and content
645 (if (if (eq type 'f) 827 (when (if (eq type 'f)
646 (string-equal (gnus-simplify-subject-fuzzy content) 828 (string-equal (gnus-simplify-subject-fuzzy content)
647 regexp) 829 regexp)
648 (string-match regexp content)) 830 (string-match regexp content))
649 (gnus-summary-raise-score score)))) 831 (gnus-summary-raise-score score))))
650 (beginning-of-line 2))))) 832 (beginning-of-line 2))))
833 (gnus-set-mode-line 'summary))
651 834
652 (defun gnus-summary-score-crossposting (score date) 835 (defun gnus-summary-score-crossposting (score date)
653 ;; Enter score file entry for current crossposting. 836 ;; Enter score file entry for current crossposting.
654 ;; SCORE is the score to add. 837 ;; SCORE is the score to add.
655 ;; DATE is the expire date. 838 ;; DATE is the expire date.
656 (let ((xref (gnus-summary-header "xref")) 839 (let ((xref (gnus-summary-header "xref"))
657 (start 0) 840 (start 0)
658 group) 841 group)
659 (or xref (error "This article is not crossposted")) 842 (unless xref
843 (error "This article is not crossposted"))
660 (while (string-match " \\([^ \t]+\\):" xref start) 844 (while (string-match " \\([^ \t]+\\):" xref start)
661 (setq start (match-end 0)) 845 (setq start (match-end 0))
662 (if (not (string= 846 (when (not (string=
663 (setq group 847 (setq group
664 (substring xref (match-beginning 1) (match-end 1))) 848 (substring xref (match-beginning 1) (match-end 1)))
665 gnus-newsgroup-name)) 849 gnus-newsgroup-name))
666 (gnus-summary-score-entry 850 (gnus-summary-score-entry
667 "xref" (concat " " group ":") nil score date t))))) 851 "xref" (concat " " group ":") nil score date t)))))
668 852
669 853
670 ;;; 854 ;;;
671 ;;; Gnus Score Files 855 ;;; Gnus Score Files
672 ;;; 856 ;;;
722 (interactive "P") 906 (interactive "P")
723 (setq score (gnus-score-default score)) 907 (setq score (gnus-score-default score))
724 (when (gnus-buffer-live-p gnus-summary-buffer) 908 (when (gnus-buffer-live-p gnus-summary-buffer)
725 (save-excursion 909 (save-excursion
726 (save-restriction 910 (save-restriction
727 (goto-char (point-min)) 911 (message-narrow-to-headers)
728 (let ((id (mail-fetch-field "message-id"))) 912 (let ((id (mail-fetch-field "message-id")))
729 (when id 913 (when id
730 (set-buffer gnus-summary-buffer) 914 (set-buffer gnus-summary-buffer)
731 (gnus-summary-score-entry 915 (gnus-summary-score-entry
732 "references" (concat id "[ \t]*$") 'r 916 "references" (concat id "[ \t]*$") 'r
767 951
768 (defun gnus-summary-raise-score (n) 952 (defun gnus-summary-raise-score (n)
769 "Raise the score of the current article by N." 953 "Raise the score of the current article by N."
770 (interactive "p") 954 (interactive "p")
771 (gnus-set-global-variables) 955 (gnus-set-global-variables)
772 (gnus-summary-set-score (+ (gnus-summary-article-score) 956 (gnus-summary-set-score (+ (gnus-summary-article-score)
773 (or n gnus-score-interactive-default-score )))) 957 (or n gnus-score-interactive-default-score ))))
774 958
775 (defun gnus-summary-set-score (n) 959 (defun gnus-summary-set-score (n)
776 "Set the score of the current article to N." 960 "Set the score of the current article to N."
777 (interactive "p") 961 (interactive "p")
781 (let ((buffer-read-only nil)) 965 (let ((buffer-read-only nil))
782 ;; Set score. 966 ;; Set score.
783 (gnus-summary-update-mark 967 (gnus-summary-update-mark
784 (if (= n (or gnus-summary-default-score 0)) ? 968 (if (= n (or gnus-summary-default-score 0)) ?
785 (if (< n (or gnus-summary-default-score 0)) 969 (if (< n (or gnus-summary-default-score 0))
786 gnus-score-below-mark gnus-score-over-mark)) 'score)) 970 gnus-score-below-mark gnus-score-over-mark))
971 'score))
787 (let* ((article (gnus-summary-article-number)) 972 (let* ((article (gnus-summary-article-number))
788 (score (assq article gnus-newsgroup-scored))) 973 (score (assq article gnus-newsgroup-scored)))
789 (if score (setcdr score n) 974 (if score (setcdr score n)
790 (setq gnus-newsgroup-scored 975 (push (cons article n) gnus-newsgroup-scored)))
791 (cons (cons article n) gnus-newsgroup-scored))))
792 (gnus-summary-update-line))) 976 (gnus-summary-update-line)))
793 977
794 (defun gnus-summary-current-score () 978 (defun gnus-summary-current-score ()
795 "Return the score of the current article." 979 "Return the score of the current article."
796 (interactive) 980 (interactive)
806 990
807 (defvar gnus-score-edit-exit-function) 991 (defvar gnus-score-edit-exit-function)
808 (defun gnus-score-edit-current-scores (file) 992 (defun gnus-score-edit-current-scores (file)
809 "Edit the current score alist." 993 "Edit the current score alist."
810 (interactive (list gnus-current-score-file)) 994 (interactive (list gnus-current-score-file))
995 (gnus-set-global-variables)
811 (let ((winconf (current-window-configuration))) 996 (let ((winconf (current-window-configuration)))
812 (and (buffer-name gnus-summary-buffer) (gnus-score-save)) 997 (when (buffer-name gnus-summary-buffer)
998 (gnus-score-save))
813 (gnus-make-directory (file-name-directory file)) 999 (gnus-make-directory (file-name-directory file))
814 (setq gnus-score-edit-buffer (find-file-noselect file)) 1000 (setq gnus-score-edit-buffer (find-file-noselect file))
815 (gnus-configure-windows 'edit-score) 1001 (gnus-configure-windows 'edit-score)
816 (gnus-score-mode) 1002 (gnus-score-mode)
817 (setq gnus-score-edit-exit-function 'gnus-score-edit-done) 1003 (setq gnus-score-edit-exit-function 'gnus-score-edit-done)
824 (defun gnus-score-edit-file (file) 1010 (defun gnus-score-edit-file (file)
825 "Edit a score file." 1011 "Edit a score file."
826 (interactive 1012 (interactive
827 (list (read-file-name "Edit score file: " gnus-kill-files-directory))) 1013 (list (read-file-name "Edit score file: " gnus-kill-files-directory)))
828 (gnus-make-directory (file-name-directory file)) 1014 (gnus-make-directory (file-name-directory file))
829 (and (buffer-name gnus-summary-buffer) (gnus-score-save)) 1015 (when (buffer-name gnus-summary-buffer)
1016 (gnus-score-save))
830 (let ((winconf (current-window-configuration))) 1017 (let ((winconf (current-window-configuration)))
831 (setq gnus-score-edit-buffer (find-file-noselect file)) 1018 (setq gnus-score-edit-buffer (find-file-noselect file))
832 (gnus-configure-windows 'edit-score) 1019 (gnus-configure-windows 'edit-score)
833 (gnus-score-mode) 1020 (gnus-score-mode)
834 (setq gnus-score-edit-exit-function 'gnus-score-edit-done) 1021 (setq gnus-score-edit-exit-function 'gnus-score-edit-done)
841 (defun gnus-score-load-file (file) 1028 (defun gnus-score-load-file (file)
842 ;; Load score file FILE. Returns a list a retrieved score-alists. 1029 ;; Load score file FILE. Returns a list a retrieved score-alists.
843 (let* ((file (expand-file-name 1030 (let* ((file (expand-file-name
844 (or (and (string-match 1031 (or (and (string-match
845 (concat "^" (expand-file-name 1032 (concat "^" (expand-file-name
846 gnus-kill-files-directory)) 1033 gnus-kill-files-directory))
847 (expand-file-name file)) 1034 (expand-file-name file))
848 file) 1035 file)
849 (concat (file-name-as-directory gnus-kill-files-directory) 1036 (concat (file-name-as-directory gnus-kill-files-directory)
850 file)))) 1037 file))))
851 (cached (assoc file gnus-score-cache)) 1038 (cached (assoc file gnus-score-cache))
857 ;; We load the score file. 1044 ;; We load the score file.
858 (setq gnus-score-alist nil) 1045 (setq gnus-score-alist nil)
859 (setq alist (gnus-score-load-score-alist file)) 1046 (setq alist (gnus-score-load-score-alist file))
860 ;; We add '(touched) to the alist to signify that it hasn't been 1047 ;; We add '(touched) to the alist to signify that it hasn't been
861 ;; touched (yet). 1048 ;; touched (yet).
862 (or (assq 'touched alist) (setq alist (cons (list 'touched nil) alist))) 1049 (unless (assq 'touched alist)
1050 (push (list 'touched nil) alist))
863 ;; If it is a global score file, we make it read-only. 1051 ;; If it is a global score file, we make it read-only.
864 (and global 1052 (and global
865 (not (assq 'read-only alist)) 1053 (not (assq 'read-only alist))
866 (setq alist (cons (list 'read-only t) alist))) 1054 (push (list 'read-only t) alist))
867 (setq gnus-score-cache 1055 (push (cons file alist) gnus-score-cache))
868 (cons (cons file alist) gnus-score-cache)))
869 (let ((a alist) 1056 (let ((a alist)
870 found) 1057 found)
871 (while a 1058 (while a
872 ;; Downcase all header names. 1059 ;; Downcase all header names.
873 (when (stringp (caar a)) 1060 (when (stringp (caar a))
888 (adapt (gnus-score-get 'adapt alist)) 1075 (adapt (gnus-score-get 'adapt alist))
889 (thread-mark-and-expunge 1076 (thread-mark-and-expunge
890 (car (gnus-score-get 'thread-mark-and-expunge alist))) 1077 (car (gnus-score-get 'thread-mark-and-expunge alist)))
891 (adapt-file (car (gnus-score-get 'adapt-file alist))) 1078 (adapt-file (car (gnus-score-get 'adapt-file alist)))
892 (local (gnus-score-get 'local alist)) 1079 (local (gnus-score-get 'local alist))
1080 (decay (car (gnus-score-get 'decay alist)))
893 (eval (car (gnus-score-get 'eval alist)))) 1081 (eval (car (gnus-score-get 'eval alist))))
1082 ;; Perform possible decays.
1083 (when (and gnus-decay-scores
1084 (gnus-decay-scores
1085 alist (or decay (gnus-time-to-day (current-time)))))
1086 (gnus-score-set 'touched '(t) alist)
1087 (gnus-score-set 'decay (list (gnus-time-to-day (current-time)))))
894 ;; We do not respect eval and files atoms from global score 1088 ;; We do not respect eval and files atoms from global score
895 ;; files. 1089 ;; files.
896 (and files (not global) 1090 (and files (not global)
897 (setq lists (apply 'append lists 1091 (setq lists (apply 'append lists
898 (mapcar (lambda (file) 1092 (mapcar (lambda (file)
899 (gnus-score-load-file file)) 1093 (gnus-score-load-file file))
900 (if adapt-file (cons adapt-file files) 1094 (if adapt-file (cons adapt-file files)
901 files))))) 1095 files)))))
902 (and eval (not global) (eval eval)) 1096 (and eval (not global) (eval eval))
903 ;; We then expand any exclude-file directives. 1097 ;; We then expand any exclude-file directives.
904 (setq gnus-scores-exclude-files 1098 (setq gnus-scores-exclude-files
905 (nconc 1099 (nconc
906 (mapcar 1100 (mapcar
907 (lambda (sfile) 1101 (lambda (sfile)
908 (expand-file-name sfile (file-name-directory file))) 1102 (expand-file-name sfile (file-name-directory file)))
909 exclude-files) gnus-scores-exclude-files)) 1103 exclude-files)
1104 gnus-scores-exclude-files))
910 (if (not local) 1105 (if (not local)
911 () 1106 ()
912 (save-excursion 1107 (save-excursion
913 (set-buffer gnus-summary-buffer) 1108 (set-buffer gnus-summary-buffer)
914 (while local 1109 (while local
916 (symbolp (caar local)) 1111 (symbolp (caar local))
917 (progn 1112 (progn
918 (make-local-variable (caar local)) 1113 (make-local-variable (caar local))
919 (set (caar local) (nth 1 (car local))))) 1114 (set (caar local) (nth 1 (car local)))))
920 (setq local (cdr local))))) 1115 (setq local (cdr local)))))
921 (if orphan (setq gnus-orphan-score orphan)) 1116 (when orphan
1117 (setq gnus-orphan-score orphan))
922 (setq gnus-adaptive-score-alist 1118 (setq gnus-adaptive-score-alist
923 (cond ((equal adapt '(t)) 1119 (cond ((equal adapt '(t))
924 (setq gnus-newsgroup-adaptive t) 1120 (setq gnus-newsgroup-adaptive t)
925 gnus-default-adaptive-score-alist) 1121 gnus-default-adaptive-score-alist)
926 ((equal adapt '(ignore)) 1122 ((equal adapt '(ignore))
948 (let ((cache (assoc file gnus-score-cache))) 1144 (let ((cache (assoc file gnus-score-cache)))
949 (if cache 1145 (if cache
950 (setq gnus-score-alist (cdr cache)) 1146 (setq gnus-score-alist (cdr cache))
951 (setq gnus-score-alist nil) 1147 (setq gnus-score-alist nil)
952 (gnus-score-load-score-alist file) 1148 (gnus-score-load-score-alist file)
953 (or gnus-score-alist 1149 (unless gnus-score-alist
954 (setq gnus-score-alist (copy-alist '((touched nil))))) 1150 (setq gnus-score-alist (copy-alist '((touched nil)))))
955 (setq gnus-score-cache 1151 (push (cons file gnus-score-alist) gnus-score-cache))))
956 (cons (cons file gnus-score-alist) gnus-score-cache)))))
957 1152
958 (defun gnus-score-remove-from-cache (file) 1153 (defun gnus-score-remove-from-cache (file)
959 (setq gnus-score-cache 1154 (setq gnus-score-cache
960 (delq (assoc file gnus-score-cache) gnus-score-cache))) 1155 (delq (assoc file gnus-score-cache) gnus-score-cache)))
961 1156
962 (defun gnus-score-load-score-alist (file) 1157 (defun gnus-score-load-score-alist (file)
1158 "Read score FILE."
963 (let (alist) 1159 (let (alist)
964 (if (not (file-readable-p file)) 1160 (if (not (file-readable-p file))
1161 ;; Couldn't read file.
965 (setq gnus-score-alist nil) 1162 (setq gnus-score-alist nil)
1163 ;; Read file.
966 (save-excursion 1164 (save-excursion
967 (gnus-set-work-buffer) 1165 (gnus-set-work-buffer)
968 (insert-file-contents file) 1166 (insert-file-contents file)
969 (goto-char (point-min)) 1167 (goto-char (point-min))
970 ;; Only do the loading if the score file isn't empty. 1168 ;; Only do the loading if the score file isn't empty.
971 (when (save-excursion (re-search-forward "[()0-9a-zA-Z]" nil t)) 1169 (when (save-excursion (re-search-forward "[()0-9a-zA-Z]" nil t))
972 (setq alist 1170 (setq alist
973 (condition-case () 1171 (condition-case ()
974 (read (current-buffer)) 1172 (read (current-buffer))
975 (error 1173 (error
976 (progn 1174 (gnus-error 3.2 "Problem with score file %s" file))))))
977 (gnus-message 3 "Problem with score file %s" file)
978 (ding)
979 (sit-for 2)
980 nil))))))
981 (if (eq (car alist) 'setq) 1175 (if (eq (car alist) 'setq)
982 ;; This is an old-style score file. 1176 ;; This is an old-style score file.
983 (setq gnus-score-alist (gnus-score-transform-old-to-new alist)) 1177 (setq gnus-score-alist (gnus-score-transform-old-to-new alist))
984 (setq gnus-score-alist alist)) 1178 (setq gnus-score-alist alist))
985 ;; Check the syntax of the score file. 1179 ;; Check the syntax of the score file.
1031 (progn 1225 (progn
1032 (ding) 1226 (ding)
1033 (gnus-message 3 err) 1227 (gnus-message 3 err)
1034 (sit-for 2) 1228 (sit-for 2)
1035 nil) 1229 nil)
1036 alist))))) 1230 alist)))))
1037 1231
1038 (defun gnus-score-transform-old-to-new (alist) 1232 (defun gnus-score-transform-old-to-new (alist)
1039 (let* ((alist (nth 2 alist)) 1233 (let* ((alist (nth 2 alist))
1040 out entry) 1234 out entry)
1041 (if (eq (car alist) 'quote) 1235 (when (eq (car alist) 'quote)
1042 (setq alist (nth 1 alist))) 1236 (setq alist (nth 1 alist)))
1043 (while alist 1237 (while alist
1044 (setq entry (car alist)) 1238 (setq entry (car alist))
1045 (if (stringp (car entry)) 1239 (if (stringp (car entry))
1046 (let ((scor (cdr entry))) 1240 (let ((scor (cdr entry)))
1047 (setq out (cons entry out)) 1241 (push entry out)
1048 (while scor 1242 (while scor
1049 (setcar scor 1243 (setcar scor
1050 (list (caar scor) (nth 2 (car scor)) 1244 (list (caar scor) (nth 2 (car scor))
1051 (and (nth 3 (car scor)) 1245 (and (nth 3 (car scor))
1052 (gnus-day-number (nth 3 (car scor)))) 1246 (gnus-day-number (nth 3 (car scor))))
1053 (if (nth 1 (car scor)) 'r 's))) 1247 (if (nth 1 (car scor)) 'r 's)))
1054 (setq scor (cdr scor)))) 1248 (setq scor (cdr scor))))
1055 (setq out (cons (if (not (listp (cdr entry))) 1249 (push (if (not (listp (cdr entry)))
1056 (list (car entry) (cdr entry)) 1250 (list (car entry) (cdr entry))
1057 entry) 1251 entry)
1058 out))) 1252 out))
1059 (setq alist (cdr alist))) 1253 (setq alist (cdr alist)))
1060 (cons (list 'touched t) (nreverse out)))) 1254 (cons (list 'touched t) (nreverse out))))
1061 1255
1062 (defun gnus-score-save () 1256 (defun gnus-score-save ()
1063 ;; Save all score information. 1257 ;; Save all score information.
1064 (let ((cache gnus-score-cache)) 1258 (let ((cache gnus-score-cache)
1259 entry score file)
1065 (save-excursion 1260 (save-excursion
1066 (setq gnus-score-alist nil) 1261 (setq gnus-score-alist nil)
1067 (set-buffer (get-buffer-create "*Score*")) 1262 (nnheader-set-temp-buffer " *Gnus Scores*")
1068 (buffer-disable-undo (current-buffer)) 1263 (while cache
1069 (let (entry score file) 1264 (current-buffer)
1070 (while cache 1265 (setq entry (pop cache)
1071 (setq entry (car cache) 1266 file (car entry)
1072 cache (cdr cache) 1267 score (cdr entry))
1073 file (car entry) 1268 (if (or (not (equal (gnus-score-get 'touched score) '(t)))
1074 score (cdr entry)) 1269 (gnus-score-get 'read-only score)
1075 (if (or (not (equal (gnus-score-get 'touched score) '(t))) 1270 (and (file-exists-p file)
1076 (gnus-score-get 'read-only score) 1271 (not (file-writable-p file))))
1077 (and (file-exists-p file) 1272 ()
1078 (not (file-writable-p file)))) 1273 (setq score (setcdr entry (delq (assq 'touched score) score)))
1079 () 1274 (erase-buffer)
1080 (setq score (setcdr entry (delq (assq 'touched score) score))) 1275 (let (emacs-lisp-mode-hook)
1081 (erase-buffer) 1276 (if (string-match
1082 (let (emacs-lisp-mode-hook) 1277 (concat (regexp-quote gnus-adaptive-file-suffix)
1083 (if (string-match 1278 "$")
1084 (concat (regexp-quote gnus-adaptive-file-suffix) 1279 file)
1085 "$") file) 1280 ;; This is an adaptive score file, so we do not run
1086 ;; This is an adaptive score file, so we do not run 1281 ;; it through `pp'. These files can get huge, and
1087 ;; it through `pp'. These files can get huge, and 1282 ;; are not meant to be edited by human hands.
1088 ;; are not meant to be edited by human hands. 1283 (gnus-prin1 score)
1089 (prin1 score (current-buffer)) 1284 ;; This is a normal score file, so we print it very
1090 ;; This is a normal score file, so we print it very 1285 ;; prettily.
1091 ;; prettily. 1286 (pp score (current-buffer))))
1092 (pp score (current-buffer)))) 1287 (gnus-make-directory (file-name-directory file))
1093 (if (not (gnus-make-directory (file-name-directory file))) 1288 ;; If the score file is empty, we delete it.
1094 () 1289 (if (zerop (buffer-size))
1095 ;; If the score file is empty, we delete it. 1290 (delete-file file)
1096 (if (zerop (buffer-size)) 1291 ;; There are scores, so we write the file.
1097 (delete-file file) 1292 (when (file-writable-p file)
1098 ;; There are scores, so we write the file. 1293 (gnus-write-buffer file)
1099 (when (file-writable-p file) 1294 (when gnus-score-after-write-file-function
1100 (write-region (point-min) (point-max) file nil 'silent) 1295 (funcall gnus-score-after-write-file-function file)))))
1101 (and gnus-score-after-write-file-function 1296 (and gnus-score-uncacheable-files
1102 (funcall gnus-score-after-write-file-function file))))) 1297 (string-match gnus-score-uncacheable-files file)
1103 (and gnus-score-uncacheable-files 1298 (gnus-score-remove-from-cache file)))
1104 (string-match gnus-score-uncacheable-files file)
1105 (gnus-score-remove-from-cache file)))))
1106 (kill-buffer (current-buffer))))) 1299 (kill-buffer (current-buffer)))))
1107 1300
1108 (defun gnus-score-headers (score-files &optional trace) 1301 (defun gnus-score-load-files (score-files)
1109 ;; Score `gnus-newsgroup-headers'. 1302 "Load all score files in SCORE-FILES."
1110 (let (scores news) 1303 ;; Load the score files.
1111 ;; PLM: probably this is not the best place to clear orphan-score 1304 (let (scores)
1112 (setq gnus-orphan-score nil)
1113 (setq gnus-scores-articles nil)
1114 (setq gnus-scores-exclude-files nil)
1115 ;; Load the score files.
1116 (while score-files 1305 (while score-files
1117 (if (stringp (car score-files)) 1306 (if (stringp (car score-files))
1118 ;; It is a string, which means that it's a score file name, 1307 ;; It is a string, which means that it's a score file name,
1119 ;; so we load the score file and add the score alist to 1308 ;; so we load the score file and add the score alist to
1120 ;; the list of alists. 1309 ;; the list of alists.
1129 (while s 1318 (while s
1130 (and (setq c (rassq (car s) gnus-score-cache)) 1319 (and (setq c (rassq (car s) gnus-score-cache))
1131 (member (car c) gnus-scores-exclude-files) 1320 (member (car c) gnus-scores-exclude-files)
1132 (setq scores (delq (car s) scores))) 1321 (setq scores (delq (car s) scores)))
1133 (setq s (cdr s))))) 1322 (setq s (cdr s)))))
1323 scores))
1324
1325 (defun gnus-score-headers (score-files &optional trace)
1326 ;; Score `gnus-newsgroup-headers'.
1327 (let (scores news)
1328 ;; PLM: probably this is not the best place to clear orphan-score
1329 (setq gnus-orphan-score nil
1330 gnus-scores-articles nil
1331 gnus-scores-exclude-files nil
1332 scores (gnus-score-load-files score-files))
1134 (setq news scores) 1333 (setq news scores)
1135 ;; Do the scoring. 1334 ;; Do the scoring.
1136 (while news 1335 (while news
1137 (setq scores news 1336 (setq scores news
1138 news nil) 1337 news nil)
1149 ;; Create articles, an alist of the form `(HEADER . SCORE)'. 1348 ;; Create articles, an alist of the form `(HEADER . SCORE)'.
1150 (while (setq header (pop headers)) 1349 (while (setq header (pop headers))
1151 ;; WARNING: The assq makes the function O(N*S) while it could 1350 ;; WARNING: The assq makes the function O(N*S) while it could
1152 ;; be written as O(N+S), where N is (length gnus-newsgroup-headers) 1351 ;; be written as O(N+S), where N is (length gnus-newsgroup-headers)
1153 ;; and S is (length gnus-newsgroup-scored). 1352 ;; and S is (length gnus-newsgroup-scored).
1154 (or (assq (mail-header-number header) gnus-newsgroup-scored) 1353 (unless (assq (mail-header-number header) gnus-newsgroup-scored)
1155 (setq gnus-scores-articles ;Total of 2 * N cons-cells used. 1354 (setq gnus-scores-articles ;Total of 2 * N cons-cells used.
1156 (cons (cons header (or gnus-summary-default-score 0)) 1355 (cons (cons header (or gnus-summary-default-score 0))
1157 gnus-scores-articles)))) 1356 gnus-scores-articles))))
1158 1357
1159 (save-excursion 1358 (save-excursion
1160 (set-buffer (get-buffer-create "*Headers*")) 1359 (set-buffer (get-buffer-create "*Headers*"))
1161 (buffer-disable-undo (current-buffer)) 1360 (buffer-disable-undo (current-buffer))
1162 1361
1183 ;; Remove the buffer. 1382 ;; Remove the buffer.
1184 (kill-buffer (current-buffer))) 1383 (kill-buffer (current-buffer)))
1185 1384
1186 ;; Add articles to `gnus-newsgroup-scored'. 1385 ;; Add articles to `gnus-newsgroup-scored'.
1187 (while gnus-scores-articles 1386 (while gnus-scores-articles
1188 (or (= gnus-summary-default-score (cdar gnus-scores-articles)) 1387 (when (or (/= gnus-summary-default-score
1189 (setq gnus-newsgroup-scored 1388 (cdar gnus-scores-articles))
1190 (cons (cons (mail-header-number 1389 gnus-save-score)
1191 (caar gnus-scores-articles)) 1390 (push (cons (mail-header-number (caar gnus-scores-articles))
1192 (cdar gnus-scores-articles)) 1391 (cdar gnus-scores-articles))
1193 gnus-newsgroup-scored))) 1392 gnus-newsgroup-scored))
1194 (setq gnus-scores-articles (cdr gnus-scores-articles))) 1393 (setq gnus-scores-articles (cdr gnus-scores-articles)))
1195 1394
1395 (let (score)
1396 (while (setq score (pop scores))
1397 (while score
1398 (when (listp (caar score))
1399 (gnus-score-advanced (car score) trace))
1400 (pop score))))
1401
1196 (gnus-message 5 "Scoring...done")))))) 1402 (gnus-message 5 "Scoring...done"))))))
1197 1403
1198 1404
1199 (defun gnus-get-new-thread-ids (articles) 1405 (defun gnus-get-new-thread-ids (articles)
1200 (let ((index (nth 1 (assoc "message-id" gnus-header-index))) 1406 (let ((index (nth 1 (assoc "message-id" gnus-header-index)))
1203 (while articles 1409 (while articles
1204 (setq art (car articles) 1410 (setq art (car articles)
1205 this (aref (car art) index) 1411 this (aref (car art) index)
1206 tref (aref (car art) refind) 1412 tref (aref (car art) refind)
1207 articles (cdr articles)) 1413 articles (cdr articles))
1208 (if (string-equal tref "") ;no references line 1414 (when (string-equal tref "") ;no references line
1209 (setq id-list (cons this id-list)))) 1415 (push this id-list)))
1210 id-list)) 1416 id-list))
1211 1417
1212 ;; Orphan functions written by plm@atcmp.nl (Peter Mutsaers). 1418 ;; Orphan functions written by plm@atcmp.nl (Peter Mutsaers).
1213 (defun gnus-score-orphans (score) 1419 (defun gnus-score-orphans (score)
1214 (let ((new-thread-ids (gnus-get-new-thread-ids gnus-scores-articles)) 1420 (let ((new-thread-ids (gnus-get-new-thread-ids gnus-scores-articles))
1222 (while articles 1428 (while articles
1223 (setq art (car articles) 1429 (setq art (car articles)
1224 this (aref (car art) gnus-score-index) 1430 this (aref (car art) gnus-score-index)
1225 articles (cdr articles)) 1431 articles (cdr articles))
1226 ;;completely skip if this is empty (not a child, so not an orphan) 1432 ;;completely skip if this is empty (not a child, so not an orphan)
1227 (if (not (string= this "")) 1433 (when (not (string= this ""))
1228 (if (equal last this) 1434 (if (equal last this)
1229 ;; O(N*H) cons-cells used here, where H is the number of 1435 ;; O(N*H) cons-cells used here, where H is the number of
1230 ;; headers. 1436 ;; headers.
1231 (setq alike (cons art alike)) 1437 (push art alike)
1232 (if last 1438 (when last
1233 (progn 1439 ;; Insert the line, with a text property on the
1234 ;; Insert the line, with a text property on the 1440 ;; terminating newline referring to the articles with
1235 ;; terminating newline referring to the articles with 1441 ;; this line.
1236 ;; this line. 1442 (insert last ?\n)
1237 (insert last ?\n) 1443 (put-text-property (1- (point)) (point) 'articles alike))
1238 (put-text-property (1- (point)) (point) 'articles alike))) 1444 (setq alike (list art)
1239 (setq alike (list art) 1445 last this))))
1240 last this)))) 1446 (when last ; Bwadr, duplicate code.
1241 (and last ; Bwadr, duplicate code. 1447 (insert last ?\n)
1242 (progn 1448 (put-text-property (1- (point)) (point) 'articles alike))
1243 (insert last ?\n)
1244 (put-text-property (1- (point)) (point) 'articles alike)))
1245 1449
1246 ;; PLM: now delete those lines that contain an entry from new-thread-ids 1450 ;; PLM: now delete those lines that contain an entry from new-thread-ids
1247 (while new-thread-ids 1451 (while new-thread-ids
1248 (setq this-id (car new-thread-ids) 1452 (setq this-id (car new-thread-ids)
1249 new-thread-ids (cdr new-thread-ids)) 1453 new-thread-ids (cdr new-thread-ids))
1250 (goto-char (point-min)) 1454 (goto-char (point-min))
1251 (while (search-forward this-id nil t) 1455 (while (search-forward this-id nil t)
1252 ;; found a match. remove this line 1456 ;; found a match. remove this line
1253 (beginning-of-line) 1457 (beginning-of-line)
1254 (kill-line 1))) 1458 (kill-line 1)))
1255 1459
1256 ;; now for each line: update its articles with score by moving to 1460 ;; now for each line: update its articles with score by moving to
1257 ;; every end-of-line in the buffer and read the articles property 1461 ;; every end-of-line in the buffer and read the articles property
1274 (while scores 1478 (while scores
1275 (setq alist (car scores) 1479 (setq alist (car scores)
1276 scores (cdr scores) 1480 scores (cdr scores)
1277 entries (assoc header alist)) 1481 entries (assoc header alist))
1278 (while (cdr entries) ;First entry is the header index. 1482 (while (cdr entries) ;First entry is the header index.
1279 (let* ((rest (cdr entries)) 1483 (let* ((rest (cdr entries))
1280 (kill (car rest)) 1484 (kill (car rest))
1281 (match (nth 0 kill)) 1485 (match (nth 0 kill))
1282 (type (or (nth 3 kill) '>)) 1486 (type (or (nth 3 kill) '>))
1283 (score (or (nth 1 kill) gnus-score-interactive-default-score)) 1487 (score (or (nth 1 kill) gnus-score-interactive-default-score))
1284 (date (nth 2 kill)) 1488 (date (nth 2 kill))
1292 ;; `gnus-score-string' does to minimize searches and stuff, 1496 ;; `gnus-score-string' does to minimize searches and stuff,
1293 ;; I will assume that people generally will put so few 1497 ;; I will assume that people generally will put so few
1294 ;; matches on numbers that any cleverness will take more 1498 ;; matches on numbers that any cleverness will take more
1295 ;; time than one would gain. 1499 ;; time than one would gain.
1296 (while articles 1500 (while articles
1297 (and (funcall match-func 1501 (when (funcall match-func
1298 (or (aref (caar articles) gnus-score-index) 0) 1502 (or (aref (caar articles) gnus-score-index) 0)
1299 match) 1503 match)
1300 (progn 1504 (when trace
1301 (and trace (setq gnus-score-trace 1505 (push (cons (car-safe (rassq alist gnus-score-cache)) kill)
1302 (cons 1506 gnus-score-trace))
1303 (cons 1507 (setq found t)
1304 (car-safe (rassq alist gnus-score-cache)) 1508 (setcdr (car articles) (+ score (cdar articles))))
1305 kill)
1306 gnus-score-trace)))
1307 (setq found t)
1308 (setcdr (car articles) (+ score (cdar articles)))))
1309 (setq articles (cdr articles))) 1509 (setq articles (cdr articles)))
1310 ;; Update expire date 1510 ;; Update expire date
1311 (cond ((null date)) ;Permanent entry. 1511 (cond ((null date)) ;Permanent entry.
1312 ((and found gnus-update-score-entry-dates) ;Match, update date. 1512 ((and found gnus-update-score-entry-dates) ;Match, update date.
1313 (gnus-score-set 'touched '(t) alist) 1513 (gnus-score-set 'touched '(t) alist)
1319 (setq entries rest))))) 1519 (setq entries rest)))))
1320 nil) 1520 nil)
1321 1521
1322 (defun gnus-score-date (scores header now expire &optional trace) 1522 (defun gnus-score-date (scores header now expire &optional trace)
1323 (let ((gnus-score-index (nth 1 (assoc header gnus-header-index))) 1523 (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
1324 entries alist) 1524 entries alist match match-func article)
1325 1525
1326 ;; Find matches. 1526 ;; Find matches.
1327 (while scores 1527 (while scores
1328 (setq alist (car scores) 1528 (setq alist (car scores)
1329 scores (cdr scores) 1529 scores (cdr scores)
1330 entries (assoc header alist)) 1530 entries (assoc header alist))
1331 (while (cdr entries) ;First entry is the header index. 1531 (while (cdr entries) ;First entry is the header index.
1332 (let* ((rest (cdr entries)) 1532 (let* ((rest (cdr entries))
1333 (kill (car rest)) 1533 (kill (car rest))
1334 (match (timezone-make-date-sortable (nth 0 kill)))
1335 (type (or (nth 3 kill) 'before)) 1534 (type (or (nth 3 kill) 'before))
1336 (score (or (nth 1 kill) gnus-score-interactive-default-score)) 1535 (score (or (nth 1 kill) gnus-score-interactive-default-score))
1337 (date (nth 2 kill)) 1536 (date (nth 2 kill))
1338 (found nil) 1537 (found nil)
1339 (match-func
1340 (cond ((eq type 'after) 'string<)
1341 ((eq type 'before) 'gnus-string>)
1342 ((eq type 'at) 'string=)
1343 (t (error "Illegal match type: %s" type))))
1344 (articles gnus-scores-articles) 1538 (articles gnus-scores-articles)
1345 l) 1539 l)
1540 (cond
1541 ((eq type 'after)
1542 (setq match-func 'string<
1543 match (gnus-date-iso8601 (nth 0 kill))))
1544 ((eq type 'before)
1545 (setq match-func 'gnus-string>
1546 match (gnus-date-iso8601 (nth 0 kill))))
1547 ((eq type 'at)
1548 (setq match-func 'string=
1549 match (gnus-date-iso8601 (nth 0 kill))))
1550 ((eq type 'regexp)
1551 (setq match-func 'string-match
1552 match (nth 0 kill)))
1553 (t (error "Illegal match type: %s" type)))
1346 ;; Instead of doing all the clever stuff that 1554 ;; Instead of doing all the clever stuff that
1347 ;; `gnus-score-string' does to minimize searches and stuff, 1555 ;; `gnus-score-string' does to minimize searches and stuff,
1348 ;; I will assume that people generally will put so few 1556 ;; I will assume that people generally will put so few
1349 ;; matches on numbers that any cleverness will take more 1557 ;; matches on numbers that any cleverness will take more
1350 ;; time than one would gain. 1558 ;; time than one would gain.
1351 (while articles 1559 (while (setq article (pop articles))
1352 (and 1560 (when (and
1353 (setq l (aref (caar articles) gnus-score-index)) 1561 (setq l (aref (car article) gnus-score-index))
1354 (funcall match-func match (timezone-make-date-sortable l)) 1562 (funcall match-func match (gnus-date-iso8601 l)))
1355 (progn 1563 (when trace
1356 (and trace (setq gnus-score-trace 1564 (push (cons (car-safe (rassq alist gnus-score-cache)) kill)
1357 (cons 1565 gnus-score-trace))
1358 (cons 1566 (setq found t)
1359 (car-safe (rassq alist gnus-score-cache)) 1567 (setcdr article (+ score (cdr article)))))
1360 kill)
1361 gnus-score-trace)))
1362 (setq found t)
1363 (setcdr (car articles) (+ score (cdar articles)))))
1364 (setq articles (cdr articles)))
1365 ;; Update expire date 1568 ;; Update expire date
1366 (cond ((null date)) ;Permanent entry. 1569 (cond ((null date)) ;Permanent entry.
1367 ((and found gnus-update-score-entry-dates) ;Match, update date. 1570 ((and found gnus-update-score-entry-dates) ;Match, update date.
1368 (gnus-score-set 'touched '(t) alist) 1571 (gnus-score-set 'touched '(t) alist)
1369 (setcar (nthcdr 2 kill) now)) 1572 (setcar (nthcdr 2 kill) now))
1370 ((and expire (< date expire)) ;Old entry, remove. 1573 ((and expire (< date expire)) ;Old entry, remove.
1371 (gnus-score-set 'touched '(t) alist) 1574 (gnus-score-set 'touched '(t) alist)
1372 (setcdr entries (cdr rest)) 1575 (setcdr entries (cdr rest))
1373 (setq rest entries))) 1576 (setq rest entries)))
1374 (setq entries rest))))) 1577 (setq entries rest)))))
1375 nil) 1578 nil)
1376 1579
1377 (defun gnus-score-body (scores header now expire &optional trace) 1580 (defun gnus-score-body (scores header now expire &optional trace)
1378 (save-excursion 1581 (save-excursion
1379 (set-buffer nntp-server-buffer)
1380 (setq gnus-scores-articles 1582 (setq gnus-scores-articles
1381 (sort gnus-scores-articles 1583 (sort gnus-scores-articles
1382 (lambda (a1 a2) 1584 (lambda (a1 a2)
1383 (< (mail-header-number (car a1)) 1585 (< (mail-header-number (car a1))
1384 (mail-header-number (car a2)))))) 1586 (mail-header-number (car a2))))))
1587 (set-buffer nntp-server-buffer)
1385 (save-restriction 1588 (save-restriction
1386 (let* ((buffer-read-only nil) 1589 (let* ((buffer-read-only nil)
1387 (articles gnus-scores-articles) 1590 (articles gnus-scores-articles)
1388 (all-scores scores) 1591 (all-scores scores)
1389 (request-func (cond ((string= "head" header) 1592 (request-func (cond ((string= "head" header)
1391 ((string= "body" header) 1594 ((string= "body" header)
1392 'gnus-request-body) 1595 'gnus-request-body)
1393 (t 'gnus-request-article))) 1596 (t 'gnus-request-article)))
1394 entries alist ofunc article last) 1597 entries alist ofunc article last)
1395 (when articles 1598 (when articles
1396 (while (cdr articles) 1599 (setq last (mail-header-number (caar (last articles))))
1397 (setq articles (cdr articles)))
1398 (setq last (mail-header-number (caar articles)))
1399 (setq articles gnus-scores-articles)
1400 ;; Not all backends support partial fetching. In that case, 1600 ;; Not all backends support partial fetching. In that case,
1401 ;; we just fetch the entire article. 1601 ;; we just fetch the entire article.
1402 (or (gnus-check-backend-function 1602 (unless (gnus-check-backend-function
1403 (and (string-match "^gnus-" (symbol-name request-func)) 1603 (and (string-match "^gnus-" (symbol-name request-func))
1404 (intern (substring (symbol-name request-func) 1604 (intern (substring (symbol-name request-func)
1405 (match-end 0)))) 1605 (match-end 0))))
1406 gnus-newsgroup-name) 1606 gnus-newsgroup-name)
1407 (progn 1607 (setq ofunc request-func)
1408 (setq ofunc request-func) 1608 (setq request-func 'gnus-request-article))
1409 (setq request-func 'gnus-request-article)))
1410 (while articles 1609 (while articles
1411 (setq article (mail-header-number (caar articles))) 1610 (setq article (mail-header-number (caar articles)))
1412 (gnus-message 7 "Scoring on article %s of %s..." article last) 1611 (gnus-message 7 "Scoring on article %s of %s..." article last)
1413 (when (funcall request-func article gnus-newsgroup-name) 1612 (when (funcall request-func article gnus-newsgroup-name)
1414 (widen) 1613 (widen)
1415 (goto-char (point-min)) 1614 (goto-char (point-min))
1416 ;; If just parts of the article is to be searched, but the 1615 ;; If just parts of the article is to be searched, but the
1417 ;; backend didn't support partial fetching, we just narrow 1616 ;; backend didn't support partial fetching, we just narrow
1418 ;; to the relevant parts. 1617 ;; to the relevant parts.
1419 (if ofunc 1618 (when ofunc
1420 (if (eq ofunc 'gnus-request-head) 1619 (if (eq ofunc 'gnus-request-head)
1421 (narrow-to-region
1422 (point)
1423 (or (search-forward "\n\n" nil t) (point-max)))
1424 (narrow-to-region 1620 (narrow-to-region
1425 (or (search-forward "\n\n" nil t) (point)) 1621 (point)
1426 (point-max)))) 1622 (or (search-forward "\n\n" nil t) (point-max)))
1623 (narrow-to-region
1624 (or (search-forward "\n\n" nil t) (point))
1625 (point-max))))
1427 (setq scores all-scores) 1626 (setq scores all-scores)
1428 ;; Find matches. 1627 ;; Find matches.
1429 (while scores 1628 (while scores
1430 (setq alist (car scores) 1629 (setq alist (pop scores)
1431 scores (cdr scores)
1432 entries (assoc header alist)) 1630 entries (assoc header alist))
1433 (while (cdr entries) ;First entry is the header index. 1631 (while (cdr entries) ;First entry is the header index.
1434 (let* ((rest (cdr entries)) 1632 (let* ((rest (cdr entries))
1435 (kill (car rest)) 1633 (kill (car rest))
1436 (match (nth 0 kill)) 1634 (match (nth 0 kill))
1437 (type (or (nth 3 kill) 's)) 1635 (type (or (nth 3 kill) 's))
1438 (score (or (nth 1 kill) 1636 (score (or (nth 1 kill)
1439 gnus-score-interactive-default-score)) 1637 gnus-score-interactive-default-score))
1440 (date (nth 2 kill)) 1638 (date (nth 2 kill))
1441 (found nil) 1639 (found nil)
1442 (case-fold-search 1640 (case-fold-search
1443 (not (or (eq type 'R) (eq type 'S) 1641 (not (or (eq type 'R) (eq type 'S)
1450 (eq type 'string) (eq type 'String)) 1648 (eq type 'string) (eq type 'String))
1451 'search-forward) 1649 'search-forward)
1452 (t 1650 (t
1453 (error "Illegal match type: %s" type))))) 1651 (error "Illegal match type: %s" type)))))
1454 (goto-char (point-min)) 1652 (goto-char (point-min))
1455 (if (funcall search-func match nil t) 1653 (when (funcall search-func match nil t)
1456 ;; Found a match, update scores. 1654 ;; Found a match, update scores.
1457 (progn 1655 (setcdr (car articles) (+ score (cdar articles)))
1458 (setcdr (car articles) (+ score (cdar articles))) 1656 (setq found t)
1459 (setq found t) 1657 (when trace
1460 (and trace (setq gnus-score-trace 1658 (push
1461 (cons 1659 (cons (car-safe (rassq alist gnus-score-cache)) kill)
1462 (cons 1660 gnus-score-trace)))
1463 (car-safe
1464 (rassq alist gnus-score-cache))
1465 kill)
1466 gnus-score-trace)))))
1467 ;; Update expire date 1661 ;; Update expire date
1468 (cond 1662 (unless trace
1469 ((null date)) ;Permanent entry. 1663 (cond
1470 ((and found gnus-update-score-entry-dates) ;Match, update date. 1664 ((null date)) ;Permanent entry.
1471 (gnus-score-set 'touched '(t) alist) 1665 ((and found gnus-update-score-entry-dates)
1472 (setcar (nthcdr 2 kill) now)) 1666 ;; Match, update date.
1473 ((and expire (< date expire)) ;Old entry, remove. 1667 (gnus-score-set 'touched '(t) alist)
1474 (gnus-score-set 'touched '(t) alist) 1668 (setcar (nthcdr 2 kill) now))
1475 (setcdr entries (cdr rest)) 1669 ((and expire (< date expire)) ;Old entry, remove.
1476 (setq rest entries))) 1670 (gnus-score-set 'touched '(t) alist)
1671 (setcdr entries (cdr rest))
1672 (setq rest entries))))
1477 (setq entries rest))))) 1673 (setq entries rest)))))
1478 (setq articles (cdr articles))))))) 1674 (setq articles (cdr articles)))))))
1479 nil) 1675 nil)
1676
1677 (defun gnus-score-thread (scores header now expire &optional trace)
1678 (gnus-score-followup scores header now expire trace t))
1480 1679
1481 (defun gnus-score-followup (scores header now expire &optional trace thread) 1680 (defun gnus-score-followup (scores header now expire &optional trace thread)
1482 ;; Insert the unique article headers in the buffer. 1681 ;; Insert the unique article headers in the buffer.
1483 (let ((gnus-score-index (nth 1 (assoc header gnus-header-index))) 1682 (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
1484 (current-score-file gnus-current-score-file) 1683 (current-score-file gnus-current-score-file)
1503 (while articles 1702 (while articles
1504 (setq art (car articles) 1703 (setq art (car articles)
1505 this (aref (car art) gnus-score-index) 1704 this (aref (car art) gnus-score-index)
1506 articles (cdr articles)) 1705 articles (cdr articles))
1507 (if (equal last this) 1706 (if (equal last this)
1508 (setq alike (cons art alike)) 1707 (push art alike)
1509 (if last 1708 (when last
1510 (progn 1709 (insert last ?\n)
1511 (insert last ?\n) 1710 (put-text-property (1- (point)) (point) 'articles alike))
1512 (put-text-property (1- (point)) (point) 'articles alike)))
1513 (setq alike (list art) 1711 (setq alike (list art)
1514 last this))) 1712 last this)))
1515 (and last ; Bwadr, duplicate code. 1713 (when last ; Bwadr, duplicate code.
1516 (progn 1714 (insert last ?\n)
1517 (insert last ?\n) 1715 (put-text-property (1- (point)) (point) 'articles alike))
1518 (put-text-property (1- (point)) (point) 'articles alike)))
1519 1716
1520 ;; Find matches. 1717 ;; Find matches.
1521 (while scores 1718 (while scores
1522 (setq alist (car scores) 1719 (setq alist (car scores)
1523 scores (cdr scores) 1720 scores (cdr scores)
1524 entries (assoc header alist)) 1721 entries (assoc header alist))
1525 (while (cdr entries) ;First entry is the header index. 1722 (while (cdr entries) ;First entry is the header index.
1526 (let* ((rest (cdr entries)) 1723 (let* ((rest (cdr entries))
1527 (kill (car rest)) 1724 (kill (car rest))
1528 (match (nth 0 kill)) 1725 (match (nth 0 kill))
1529 (type (or (nth 3 kill) 's)) 1726 (type (or (nth 3 kill) 's))
1530 (score (or (nth 1 kill) gnus-score-interactive-default-score)) 1727 (score (or (nth 1 kill) gnus-score-interactive-default-score))
1531 (date (nth 2 kill)) 1728 (date (nth 2 kill))
1567 ;; Update expire date 1764 ;; Update expire date
1568 (cond ((null date)) ;Permanent entry. 1765 (cond ((null date)) ;Permanent entry.
1569 ((and found gnus-update-score-entry-dates) ;Match, update date. 1766 ((and found gnus-update-score-entry-dates) ;Match, update date.
1570 (gnus-score-set 'touched '(t) alist) 1767 (gnus-score-set 'touched '(t) alist)
1571 (setcar (nthcdr 2 kill) now)) 1768 (setcar (nthcdr 2 kill) now))
1572 ((and expire (< date expire)) ;Old entry, remove. 1769 ((and expire (< date expire)) ;Old entry, remove.
1573 (gnus-score-set 'touched '(t) alist) 1770 (gnus-score-set 'touched '(t) alist)
1574 (setcdr entries (cdr rest)) 1771 (setcdr entries (cdr rest))
1575 (setq rest entries))) 1772 (setq rest entries)))
1576 (setq entries rest)))) 1773 (setq entries rest))))
1577 ;; We change the score file back to the previous one. 1774 ;; We change the score file back to the previous one.
1605 ;; than EXPIRE. 1802 ;; than EXPIRE.
1606 1803
1607 ;; Insert the unique article headers in the buffer. 1804 ;; Insert the unique article headers in the buffer.
1608 (let ((gnus-score-index (nth 1 (assoc header gnus-header-index))) 1805 (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
1609 ;; gnus-score-index is used as a free variable. 1806 ;; gnus-score-index is used as a free variable.
1610 alike last this art entries alist articles scores fuzzy) 1807 alike last this art entries alist articles
1808 fuzzies arts words kill)
1611 1809
1612 ;; Sorting the articles costs os O(N*log N) but will allow us to 1810 ;; Sorting the articles costs os O(N*log N) but will allow us to
1613 ;; only match with each unique header. Thus the actual matching 1811 ;; only match with each unique header. Thus the actual matching
1614 ;; will be O(M*U) where M is the number of strings to match with, 1812 ;; will be O(M*U) where M is the number of strings to match with,
1615 ;; and U is the number of unique headers. It is assumed (but 1813 ;; and U is the number of unique headers. It is assumed (but
1617 ;; factor involved with string matching. 1815 ;; factor involved with string matching.
1618 (setq gnus-scores-articles (sort gnus-scores-articles 'gnus-score-string<) 1816 (setq gnus-scores-articles (sort gnus-scores-articles 'gnus-score-string<)
1619 articles gnus-scores-articles) 1817 articles gnus-scores-articles)
1620 1818
1621 (erase-buffer) 1819 (erase-buffer)
1622 (while articles 1820 (while (setq art (pop articles))
1623 (setq art (car articles) 1821 (setq this (aref (car art) gnus-score-index))
1624 this (aref (car art) gnus-score-index)
1625 articles (cdr articles))
1626 (if (equal last this) 1822 (if (equal last this)
1627 ;; O(N*H) cons-cells used here, where H is the number of 1823 ;; O(N*H) cons-cells used here, where H is the number of
1628 ;; headers. 1824 ;; headers.
1629 (setq alike (cons art alike)) 1825 (push art alike)
1630 (if last 1826 (when last
1631 (progn 1827 ;; Insert the line, with a text property on the
1632 ;; Insert the line, with a text property on the 1828 ;; terminating newline referring to the articles with
1633 ;; terminating newline referring to the articles with 1829 ;; this line.
1634 ;; this line. 1830 (insert last ?\n)
1635 (insert last ?\n) 1831 (put-text-property (1- (point)) (point) 'articles alike))
1636 (put-text-property (1- (point)) (point) 'articles alike)))
1637 (setq alike (list art) 1832 (setq alike (list art)
1638 last this))) 1833 last this)))
1639 (and last ; Bwadr, duplicate code. 1834 (when last ; Bwadr, duplicate code.
1640 (progn 1835 (insert last ?\n)
1641 (insert last ?\n) 1836 (put-text-property (1- (point)) (point) 'articles alike))
1642 (put-text-property (1- (point)) (point) 'articles alike))) 1837
1643 1838 ;; Go through all the score alists and pick out the entries
1644 ;; Find ordinary matches. 1839 ;; for this header.
1645 (setq scores score-list) 1840 (while score-list
1646 (while scores 1841 (setq alist (pop score-list)
1647 (setq alist (car scores) 1842 ;; There's only one instance of this header for
1648 scores (cdr scores) 1843 ;; each score alist.
1649 entries (assoc header alist)) 1844 entries (assoc header alist))
1650 (while (cdr entries) ;First entry is the header index. 1845 (while (cdr entries) ;First entry is the header index.
1651 (let* ((rest (cdr entries)) 1846 (let* ((kill (cadr entries))
1652 (kill (car rest))
1653 (match (nth 0 kill)) 1847 (match (nth 0 kill))
1654 (type (or (nth 3 kill) 's)) 1848 (type (or (nth 3 kill) 's))
1655 (score (or (nth 1 kill) gnus-score-interactive-default-score)) 1849 (score (or (nth 1 kill) gnus-score-interactive-default-score))
1656 (date (nth 2 kill)) 1850 (date (nth 2 kill))
1657 (found nil) 1851 (found nil)
1658 (mt (aref (symbol-name type) 0)) 1852 (mt (aref (symbol-name type) 0))
1659 (case-fold-search 1853 (case-fold-search (not (memq mt '(?R ?S ?E ?F))))
1660 (not (or (= mt ?R) (= mt ?S) (= mt ?E) (= mt ?F))))
1661 (dmt (downcase mt)) 1854 (dmt (downcase mt))
1662 (search-func 1855 (search-func
1663 (cond ((= dmt ?r) 're-search-forward) 1856 (cond ((= dmt ?r) 're-search-forward)
1664 ((or (= dmt ?e) (= dmt ?s) (= dmt ?f)) 'search-forward) 1857 ((or (= dmt ?e) (= dmt ?s) (= dmt ?f)) 'search-forward)
1665 (t (error "Illegal match type: %s" type)))) 1858 ((= dmt ?w) nil)
1666 arts art) 1859 (t (error "Illegal match type: %s" type)))))
1667 (if (= dmt ?f) 1860 (cond
1668 (setq fuzzy t) 1861 ;; Fuzzy matches. We save these for later.
1669 ;; Do non-fuzzy matching. 1862 ((= dmt ?f)
1863 (push (cons entries alist) fuzzies))
1864 ;; Word matches. Save these for even later.
1865 ((= dmt ?w)
1866 (push (cons entries alist) words))
1867 ;; Exact matches.
1868 ((= dmt ?e)
1869 ;; Do exact matching.
1670 (goto-char (point-min)) 1870 (goto-char (point-min))
1671 (if (= dmt ?e) 1871 (while (and (not (eobp))
1672 ;; Do exact matching. 1872 (funcall search-func match nil t))
1673 (while (and (not (eobp)) 1873 ;; Is it really exact?
1674 (funcall search-func match nil t)) 1874 (and (eolp)
1675 (and (= (progn (beginning-of-line) (point)) 1875 (= (gnus-point-at-bol) (match-beginning 0))
1676 (match-beginning 0)) 1876 ;; Yup.
1677 (= (progn (end-of-line) (point)) 1877 (progn
1678 (match-end 0)) 1878 (setq found (setq arts (get-text-property
1679 (progn 1879 (point) 'articles)))
1680 (setq found (setq arts (get-text-property 1880 ;; Found a match, update scores.
1681 (point) 'articles))) 1881 (if trace
1682 ;; Found a match, update scores. 1882 (while (setq art (pop arts))
1683 (if trace 1883 (setcdr art (+ score (cdr art)))
1684 (while arts 1884 (push
1685 (setq art (car arts) 1885 (cons
1686 arts (cdr arts)) 1886 (car-safe (rassq alist gnus-score-cache))
1687 (setcdr art (+ score (cdr art))) 1887 kill)
1688 (setq gnus-score-trace 1888 gnus-score-trace))
1689 (cons 1889 (while (setq art (pop arts))
1690 (cons 1890 (setcdr art (+ score (cdr art)))))))
1691 (car-safe 1891 (forward-line 1)))
1692 (rassq alist gnus-score-cache)) 1892 ;; Regexp and substring matching.
1693 kill) 1893 (t
1694 gnus-score-trace))) 1894 (goto-char (point-min))
1695 (while arts 1895 (when (string= match "")
1696 (setq art (car arts) 1896 (setq match "\n"))
1697 arts (cdr arts)) 1897 (while (and (not (eobp))
1698 (setcdr art (+ score (cdr art))))))) 1898 (funcall search-func match nil t))
1699 (forward-line 1)) 1899 (goto-char (match-beginning 0))
1700 ;; Do regexp and substring matching. 1900 (end-of-line)
1701 (and (string= match "") (setq match "\n")) 1901 (setq found (setq arts (get-text-property (point) 'articles)))
1702 (while (and (not (eobp)) 1902 ;; Found a match, update scores.
1703 (funcall search-func match nil t)) 1903 (if trace
1704 (goto-char (match-beginning 0)) 1904 (while (setq art (pop arts))
1705 (end-of-line) 1905 (setcdr art (+ score (cdr art)))
1706 (setq found (setq arts (get-text-property (point) 'articles))) 1906 (push (cons (car-safe (rassq alist gnus-score-cache)) kill)
1907 gnus-score-trace))
1908 (while (setq art (pop arts))
1909 (setcdr art (+ score (cdr art)))))
1910 (forward-line 1))))
1911 ;; Update expiry date
1912 (if trace
1913 (setq entries (cdr entries))
1914 (cond
1915 ;; Permanent entry.
1916 ((null date)
1917 (setq entries (cdr entries)))
1918 ;; We have a match, so we update the date.
1919 ((and found gnus-update-score-entry-dates)
1920 (gnus-score-set 'touched '(t) alist)
1921 (setcar (nthcdr 2 kill) now)
1922 (setq entries (cdr entries)))
1923 ;; This entry has expired, so we remove it.
1924 ((and expire (< date expire))
1925 (gnus-score-set 'touched '(t) alist)
1926 (setcdr entries (cddr entries)))
1927 ;; No match; go to next entry.
1928 (t
1929 (setq entries (cdr entries))))))))
1930
1931 ;; Find fuzzy matches.
1932 (when fuzzies
1933 ;; Simplify the entire buffer for easy matching.
1934 (gnus-simplify-buffer-fuzzy)
1935 (while (setq kill (cadaar fuzzies))
1936 (let* ((match (nth 0 kill))
1937 (type (nth 3 kill))
1938 (score (or (nth 1 kill) gnus-score-interactive-default-score))
1939 (date (nth 2 kill))
1940 (mt (aref (symbol-name type) 0))
1941 (case-fold-search (not (= mt ?F)))
1942 found)
1943 (goto-char (point-min))
1944 (while (and (not (eobp))
1945 (search-forward match nil t))
1946 (when (and (= (gnus-point-at-bol) (match-beginning 0))
1947 (eolp))
1948 (setq found (setq arts (get-text-property (point) 'articles)))
1949 (if trace
1950 (while (setq art (pop arts))
1951 (setcdr art (+ score (cdr art)))
1952 (push (cons
1953 (car-safe (rassq (cdar fuzzies) gnus-score-cache))
1954 kill)
1955 gnus-score-trace))
1707 ;; Found a match, update scores. 1956 ;; Found a match, update scores.
1708 (if trace 1957 (while (setq art (pop arts))
1709 (while arts 1958 (setcdr art (+ score (cdr art))))))
1710 (setq art (pop arts)) 1959 (forward-line 1))
1711 (setcdr art (+ score (cdr art))) 1960 ;; Update expiry date
1712 (push (cons 1961 (cond
1713 (car-safe (rassq alist gnus-score-cache)) 1962 ;; Permanent.
1714 kill) 1963 ((null date)
1715 gnus-score-trace)) 1964 )
1716 (while arts 1965 ;; Match, update date.
1717 (setq art (pop arts)) 1966 ((and found gnus-update-score-entry-dates)
1718 (setcdr art (+ score (cdr art))))) 1967 (gnus-score-set 'touched '(t) (cdar fuzzies))
1719 (forward-line 1))) 1968 (setcar (nthcdr 2 kill) now))
1720 ;; Update expire date 1969 ;; Old entry, remove.
1721 (cond 1970 ((and expire (< date expire))
1722 ((null date)) ;Permanent entry. 1971 (gnus-score-set 'touched '(t) (cdar fuzzies))
1723 ((and found gnus-update-score-entry-dates) ;Match, update date. 1972 (setcdr (caar fuzzies) (cddaar fuzzies))))
1724 (gnus-score-set 'touched '(t) alist) 1973 (setq fuzzies (cdr fuzzies)))))
1974
1975 (when words
1976 ;; Enter all words into the hashtb.
1977 (let ((hashtb (gnus-make-hashtable
1978 (* 10 (count-lines (point-min) (point-max))))))
1979 (gnus-enter-score-words-into-hashtb hashtb)
1980 (while (setq kill (cadaar words))
1981 (let* ((score (or (nth 1 kill) gnus-score-interactive-default-score))
1982 (date (nth 2 kill))
1983 found)
1984 (when (setq arts (intern-soft (nth 0 kill) hashtb))
1985 (setq arts (symbol-value arts))
1986 (setq found t)
1987 (if trace
1988 (while (setq art (pop arts))
1989 (setcdr art (+ score (cdr art)))
1990 (push (cons
1991 (car-safe (rassq (cdar words) gnus-score-cache))
1992 kill)
1993 gnus-score-trace))
1994 ;; Found a match, update scores.
1995 (while (setq art (pop arts))
1996 (setcdr art (+ score (cdr art))))))
1997 ;; Update expiry date
1998 (cond
1999 ;; Permanent.
2000 ((null date)
2001 )
2002 ;; Match, update date.
2003 ((and found gnus-update-score-entry-dates)
2004 (gnus-score-set 'touched '(t) (cdar words))
1725 (setcar (nthcdr 2 kill) now)) 2005 (setcar (nthcdr 2 kill) now))
1726 ((and expire (< date expire)) ;Old entry, remove. 2006 ;; Old entry, remove.
1727 (gnus-score-set 'touched '(t) alist) 2007 ((and expire (< date expire))
1728 (setcdr entries (cdr rest)) 2008 (gnus-score-set 'touched '(t) (cdar words))
1729 (setq rest entries)))) 2009 (setcdr (caar words) (cddaar words))))
1730 (setq entries rest)))) 2010 (setq words (cdr words))))))
1731 2011 nil))
1732 ;; Find fuzzy matches. 2012
1733 (when fuzzy 2013 (defun gnus-enter-score-words-into-hashtb (hashtb)
1734 (setq scores score-list) 2014 ;; Find all the words in the buffer and enter them into
1735 (gnus-simplify-buffer-fuzzy) 2015 ;; the hashtable.
1736 (while scores 2016 (let ((syntab (syntax-table))
1737 (setq alist (car scores) 2017 word val)
1738 scores (cdr scores) 2018 (goto-char (point-min))
1739 entries (assoc header alist)) 2019 (unwind-protect
1740 (while (cdr entries) ;First entry is the header index. 2020 (progn
1741 (let* ((rest (cdr entries)) 2021 (set-syntax-table gnus-adaptive-word-syntax-table)
1742 (kill (car rest)) 2022 (while (re-search-forward "\\b\\w+\\b" nil t)
1743 (match (nth 0 kill)) 2023 (setq val
1744 (type (or (nth 3 kill) 's)) 2024 (gnus-gethash
1745 (score (or (nth 1 kill) gnus-score-interactive-default-score)) 2025 (setq word (downcase (buffer-substring
1746 (date (nth 2 kill)) 2026 (match-beginning 0) (match-end 0))))
1747 (found nil) 2027 hashtb))
1748 (mt (aref (symbol-name type) 0)) 2028 (gnus-sethash
1749 (case-fold-search (not (= mt ?F))) 2029 word
1750 (dmt (downcase mt)) 2030 (append (get-text-property (gnus-point-at-eol) 'articles) val)
1751 arts art) 2031 hashtb)))
1752 (when (= dmt ?f) 2032 (set-syntax-table syntab))
1753 (goto-char (point-min)) 2033 ;; Make all the ignorable words ignored.
1754 (while (and (not (eobp)) 2034 (let ((ignored (append gnus-ignored-adaptive-words
1755 (search-forward match nil t)) 2035 gnus-default-ignored-adaptive-words)))
1756 (when (and (= (progn (beginning-of-line) (point)) 2036 (while ignored
1757 (match-beginning 0)) 2037 (gnus-sethash (pop ignored) nil hashtb)))))
1758 (= (progn (end-of-line) (point))
1759 (match-end 0)))
1760 (setq found (setq arts (get-text-property
1761 (point) 'articles)))
1762 ;; Found a match, update scores.
1763 (if trace
1764 (while arts
1765 (setq art (pop arts))
1766 (setcdr art (+ score (cdr art)))
1767 (push (cons
1768 (car-safe (rassq alist gnus-score-cache))
1769 kill)
1770 gnus-score-trace))
1771 (while arts
1772 (setq art (pop arts))
1773 (setcdr art (+ score (cdr art))))))
1774 (forward-line 1))
1775 ;; Update expire date
1776 (unless trace
1777 (cond
1778 ((null date)) ;Permanent entry.
1779 ((and found gnus-update-score-entry-dates) ;Match, update date.
1780 (gnus-score-set 'touched '(t) alist)
1781 (setcar (nthcdr 2 kill) now))
1782 ((and expire (< date expire)) ;Old entry, remove.
1783 (gnus-score-set 'touched '(t) alist)
1784 (setcdr entries (cdr rest))
1785 (setq rest entries)))))
1786 (setq entries rest))))))
1787 nil)
1788 2038
1789 (defun gnus-score-string< (a1 a2) 2039 (defun gnus-score-string< (a1 a2)
1790 ;; Compare headers in articles A2 and A2. 2040 ;; Compare headers in articles A2 and A2.
1791 ;; The header index used is the free variable `gnus-score-index'. 2041 ;; The header index used is the free variable `gnus-score-index'.
1792 (string-lessp (aref (car a1) gnus-score-index) 2042 (string-lessp (aref (car a1) gnus-score-index)
1793 (aref (car a2) gnus-score-index))) 2043 (aref (car a2) gnus-score-index)))
1794 2044
1795 (defun gnus-score-build-cons (article)
1796 ;; Build a `gnus-newsgroup-scored' type cons from ARTICLE.
1797 (cons (mail-header-number (car article)) (cdr article)))
1798
1799 (defun gnus-current-score-file-nondirectory (&optional score-file) 2045 (defun gnus-current-score-file-nondirectory (&optional score-file)
1800 (let ((score-file (or score-file gnus-current-score-file))) 2046 (let ((score-file (or score-file gnus-current-score-file)))
1801 (if score-file 2047 (if score-file
1802 (gnus-short-group-name (file-name-nondirectory score-file)) 2048 (gnus-short-group-name (file-name-nondirectory score-file))
1803 "none"))) 2049 "none")))
1804 2050
1805 (defun gnus-score-adaptive () 2051 (defun gnus-score-adaptive ()
1806 (save-excursion 2052 "Create adaptive score rules for this newsgroup."
1807 (let* ((malist (gnus-copy-sequence gnus-adaptive-score-alist)) 2053 (when gnus-use-adaptive-scoring
1808 (alist malist) 2054 ;; We change the score file to the adaptive score file.
1809 (date (current-time-string)) 2055 (save-excursion
1810 (data gnus-newsgroup-data) 2056 (set-buffer gnus-summary-buffer)
1811 elem headers match) 2057 (gnus-score-load-file
1812 ;; First we transform the adaptive rule alist into something 2058 (or gnus-newsgroup-adaptive-score-file
1813 ;; that's faster to process. 2059 (gnus-score-file-name
1814 (while malist 2060 gnus-newsgroup-name gnus-adaptive-file-suffix))))
1815 (setq elem (car malist)) 2061 ;; Perform ordinary line scoring.
1816 (if (symbolp (car elem)) 2062 (when (or (not (listp gnus-use-adaptive-scoring))
1817 (setcar elem (symbol-value (car elem)))) 2063 (memq 'line gnus-use-adaptive-scoring))
1818 (setq elem (cdr elem))
1819 (while elem
1820 (setcdr (car elem)
1821 (cons (if (eq (caar elem) 'followup)
1822 "references"
1823 (symbol-name (caar elem)))
1824 (cdar elem)))
1825 (setcar (car elem)
1826 `(lambda (h)
1827 (,(intern
1828 (concat "mail-header-"
1829 (if (eq (caar elem) 'followup)
1830 "message-id"
1831 (downcase (symbol-name (caar elem))))))
1832 h)))
1833 (setq elem (cdr elem)))
1834 (setq malist (cdr malist)))
1835 ;; We change the score file to the adaptive score file.
1836 (save-excursion 2064 (save-excursion
1837 (set-buffer gnus-summary-buffer) 2065 (let* ((malist (gnus-copy-sequence gnus-adaptive-score-alist))
1838 (gnus-score-load-file 2066 (alist malist)
1839 (or gnus-newsgroup-adaptive-score-file 2067 (date (current-time-string))
1840 (gnus-score-file-name 2068 (data gnus-newsgroup-data)
1841 gnus-newsgroup-name gnus-adaptive-file-suffix)))) 2069 elem headers match)
1842 ;; The we score away. 2070 ;; First we transform the adaptive rule alist into something
1843 (while data 2071 ;; that's faster to process.
1844 (setq elem (cdr (assq (gnus-data-mark (car data)) alist))) 2072 (while malist
1845 (if (or (not elem) 2073 (setq elem (car malist))
1846 (gnus-data-pseudo-p (car data))) 2074 (when (symbolp (car elem))
1847 () 2075 (setcar elem (symbol-value (car elem))))
1848 (when (setq headers (gnus-data-header (car data))) 2076 (setq elem (cdr elem))
1849 (while elem 2077 (while elem
1850 (setq match (funcall (caar elem) headers)) 2078 (setcdr (car elem)
1851 (gnus-summary-score-entry 2079 (cons (if (eq (caar elem) 'followup)
1852 (nth 1 (car elem)) match 2080 "references"
1853 (cond 2081 (symbol-name (caar elem)))
1854 ((numberp match) 2082 (cdar elem)))
1855 '=) 2083 (setcar (car elem)
1856 ((equal (nth 1 (car elem)) "date") 2084 `(lambda (h)
1857 'a) 2085 (,(intern
1858 (t 2086 (concat "mail-header-"
1859 ;; Whether we use substring or exact matches are controlled 2087 (if (eq (caar elem) 'followup)
1860 ;; here. 2088 "message-id"
1861 (if (or (not gnus-score-exact-adapt-limit) 2089 (downcase (symbol-name (caar elem))))))
1862 (< (length match) gnus-score-exact-adapt-limit)) 2090 h)))
1863 'e 2091 (setq elem (cdr elem)))
1864 (if (equal (nth 1 (car elem)) "subject") 2092 (setq malist (cdr malist)))
1865 'f 's)))) 2093 ;; Then we score away.
1866 (nth 2 (car elem)) date nil t) 2094 (while data
1867 (setq elem (cdr elem))))) 2095 (setq elem (cdr (assq (gnus-data-mark (car data)) alist)))
1868 (setq data (cdr data)))))) 2096 (if (or (not elem)
2097 (gnus-data-pseudo-p (car data)))
2098 ()
2099 (when (setq headers (gnus-data-header (car data)))
2100 (while elem
2101 (setq match (funcall (caar elem) headers))
2102 (gnus-summary-score-entry
2103 (nth 1 (car elem)) match
2104 (cond
2105 ((numberp match)
2106 '=)
2107 ((equal (nth 1 (car elem)) "date")
2108 'a)
2109 (t
2110 ;; Whether we use substring or exact matches is
2111 ;; controlled here.
2112 (if (or (not gnus-score-exact-adapt-limit)
2113 (< (length match) gnus-score-exact-adapt-limit))
2114 'e
2115 (if (equal (nth 1 (car elem)) "subject")
2116 'f 's))))
2117 (nth 2 (car elem)) date nil t)
2118 (setq elem (cdr elem)))))
2119 (setq data (cdr data))))))
2120
2121 ;; Perform adaptive word scoring.
2122 (when (and (listp gnus-use-adaptive-scoring)
2123 (memq 'word gnus-use-adaptive-scoring))
2124 (nnheader-temp-write nil
2125 (let* ((hashtb (gnus-make-hashtable 1000))
2126 (date (gnus-day-number (current-time-string)))
2127 (data gnus-newsgroup-data)
2128 (syntab (syntax-table))
2129 word d score val)
2130 (unwind-protect
2131 (progn
2132 (set-syntax-table gnus-adaptive-word-syntax-table)
2133 ;; Go through all articles.
2134 (while (setq d (pop data))
2135 (when (and
2136 (not (gnus-data-pseudo-p d))
2137 (setq score
2138 (cdr (assq
2139 (gnus-data-mark d)
2140 gnus-adaptive-word-score-alist))))
2141 ;; This article has a mark that should lead to
2142 ;; adaptive word rules, so we insert the subject
2143 ;; and find all words in that string.
2144 (insert (mail-header-subject (gnus-data-header d)))
2145 (downcase-region (point-min) (point-max))
2146 (goto-char (point-min))
2147 (while (re-search-forward "\\b\\w+\\b" nil t)
2148 ;; Put the word and score into the hashtb.
2149 (setq val (gnus-gethash (setq word (match-string 0))
2150 hashtb))
2151 (gnus-sethash word (+ (or val 0) score) hashtb))
2152 (erase-buffer))))
2153 (set-syntax-table syntab))
2154 ;; Make all the ignorable words ignored.
2155 (let ((ignored (append gnus-ignored-adaptive-words
2156 gnus-default-ignored-adaptive-words)))
2157 (while ignored
2158 (gnus-sethash (pop ignored) nil hashtb)))
2159 ;; Now we have all the words and scores, so we
2160 ;; add these rules to the ADAPT file.
2161 (set-buffer gnus-summary-buffer)
2162 (mapatoms
2163 (lambda (word)
2164 (when (symbol-value word)
2165 (gnus-summary-score-entry
2166 "subject" (symbol-name word) 'w (symbol-value word)
2167 date nil t)))
2168 hashtb))))))
1869 2169
1870 (defun gnus-score-edit-done () 2170 (defun gnus-score-edit-done ()
1871 (let ((bufnam (buffer-file-name (current-buffer))) 2171 (let ((bufnam (buffer-file-name (current-buffer)))
1872 (winconf gnus-prev-winconf)) 2172 (winconf gnus-prev-winconf))
1873 (and winconf (set-window-configuration winconf)) 2173 (when winconf
2174 (set-window-configuration winconf))
1874 (gnus-score-remove-from-cache bufnam) 2175 (gnus-score-remove-from-cache bufnam)
1875 (gnus-score-load-file bufnam))) 2176 (gnus-score-load-file bufnam)))
1876 2177
1877 (defun gnus-score-find-trace () 2178 (defun gnus-score-find-trace ()
1878 "Find all score rules that applies to the current article." 2179 "Find all score rules that applies to the current article."
1879 (interactive) 2180 (interactive)
1880 (let ((gnus-newsgroup-headers 2181 (let ((gnus-newsgroup-headers
1881 (list (gnus-summary-article-header))) 2182 (list (gnus-summary-article-header)))
1882 (gnus-newsgroup-scored nil) 2183 (gnus-newsgroup-scored nil)
1883 (buf (current-buffer))
1884 trace) 2184 trace)
1885 (when (get-buffer "*Gnus Scores*") 2185 (save-excursion
1886 (save-excursion 2186 (nnheader-set-temp-buffer "*Score Trace*"))
1887 (set-buffer "*Gnus Scores*")
1888 (erase-buffer)))
1889 (setq gnus-score-trace nil) 2187 (setq gnus-score-trace nil)
1890 (gnus-possibly-score-headers 'trace) 2188 (gnus-possibly-score-headers 'trace)
1891 (if (not (setq trace gnus-score-trace)) 2189 (if (not (setq trace gnus-score-trace))
1892 (gnus-error 1 "No score rules apply to the current article.") 2190 (gnus-error 1 "No score rules apply to the current article.")
1893 (pop-to-buffer "*Gnus Scores*") 2191 (set-buffer "*Score Trace*")
1894 (gnus-add-current-to-buffer-list) 2192 (gnus-add-current-to-buffer-list)
1895 (erase-buffer)
1896 (while trace 2193 (while trace
1897 (insert (format "%S -> %s\n" (cdar trace) 2194 (insert (format "%S -> %s\n" (cdar trace)
1898 (file-name-nondirectory (caar trace)))) 2195 (file-name-nondirectory (caar trace))))
1899 (setq trace (cdr trace))) 2196 (setq trace (cdr trace)))
1900 (goto-char (point-min)) 2197 (goto-char (point-min))
1901 (pop-to-buffer buf)))) 2198 (gnus-configure-windows 'score-trace))))
2199
2200 (defun gnus-score-find-favourite-words ()
2201 "List words used in scoring."
2202 (interactive)
2203 (let ((alists (gnus-score-load-files (gnus-all-score-files)))
2204 alist rule rules kill)
2205 ;; Go through all the score alists for this group
2206 ;; and find all `w' rules.
2207 (while (setq alist (pop alists))
2208 (while (setq rule (pop alist))
2209 (when (and (stringp (car rule))
2210 (equal "subject" (downcase (pop rule))))
2211 (while (setq kill (pop rule))
2212 (when (memq (nth 3 kill) '(w W word Word))
2213 (push (cons (or (nth 1 kill)
2214 gnus-score-interactive-default-score)
2215 (car kill))
2216 rules))))))
2217 (setq rules (sort rules (lambda (r1 r2)
2218 (string-lessp (cdr r1) (cdr r2)))))
2219 ;; Add up words that have appeared several times.
2220 (let ((r rules))
2221 (while (cdr r)
2222 (if (equal (cdar r) (cdadr r))
2223 (progn
2224 (setcar (car r) (+ (caar r) (caadr r)))
2225 (setcdr r (cddr r)))
2226 (pop r))))
2227 ;; Insert the words.
2228 (nnheader-set-temp-buffer "*Score Words*")
2229 (if (not (setq rules (sort rules (lambda (r1 r2) (> (car r1) (car r2))))))
2230 (gnus-error 3 "No word score rules")
2231 (while rules
2232 (insert (format "%-5d: %s\n" (caar rules) (cdar rules)))
2233 (pop rules))
2234 (gnus-add-current-to-buffer-list)
2235 (goto-char (point-min))
2236 (gnus-configure-windows 'score-words))))
1902 2237
1903 (defun gnus-summary-rescore () 2238 (defun gnus-summary-rescore ()
1904 "Redo the entire scoring process in the current summary." 2239 "Redo the entire scoring process in the current summary."
1905 (interactive) 2240 (interactive)
1906 (gnus-score-save) 2241 (gnus-score-save)
1948 (while (gnus-summary-find-subject subject) 2283 (while (gnus-summary-find-subject subject)
1949 (gnus-summary-raise-score score)) 2284 (gnus-summary-raise-score score))
1950 (gnus-summary-next-subject 1 t))) 2285 (gnus-summary-next-subject 1 t)))
1951 2286
1952 (defun gnus-score-default (level) 2287 (defun gnus-score-default (level)
1953 (if level (prefix-numeric-value level) 2288 (if level (prefix-numeric-value level)
1954 gnus-score-interactive-default-score)) 2289 gnus-score-interactive-default-score))
1955 2290
1956 (defun gnus-summary-raise-thread (&optional score) 2291 (defun gnus-summary-raise-thread (&optional score)
1957 "Raise the score of the articles in the current thread with SCORE." 2292 "Raise the score of the articles in the current thread with SCORE."
1958 (interactive "P") 2293 (interactive "P")
1964 (gnus-summary-goto-subject (car articles)) 2299 (gnus-summary-goto-subject (car articles))
1965 (gnus-summary-raise-score score) 2300 (gnus-summary-raise-score score)
1966 (setq articles (cdr articles)))) 2301 (setq articles (cdr articles))))
1967 (setq e (point))) 2302 (setq e (point)))
1968 (let ((gnus-summary-check-current t)) 2303 (let ((gnus-summary-check-current t))
1969 (or (zerop (gnus-summary-next-subject 1 t)) 2304 (unless (zerop (gnus-summary-next-subject 1 t))
1970 (goto-char e)))) 2305 (goto-char e))))
1971 (gnus-summary-recenter) 2306 (gnus-summary-recenter)
1972 (gnus-summary-position-point) 2307 (gnus-summary-position-point)
1973 (gnus-set-mode-line 'summary)) 2308 (gnus-set-mode-line 'summary))
1974 2309
1975 (defun gnus-summary-lower-same-subject-and-select (score) 2310 (defun gnus-summary-lower-same-subject-and-select (score)
1990 ;;; Finding score files. 2325 ;;; Finding score files.
1991 2326
1992 (defun gnus-score-score-files (group) 2327 (defun gnus-score-score-files (group)
1993 "Return a list of all possible score files." 2328 "Return a list of all possible score files."
1994 ;; Search and set any global score files. 2329 ;; Search and set any global score files.
1995 (and gnus-global-score-files 2330 (when gnus-global-score-files
1996 (or gnus-internal-global-score-files 2331 (unless gnus-internal-global-score-files
1997 (gnus-score-search-global-directories gnus-global-score-files))) 2332 (gnus-score-search-global-directories gnus-global-score-files)))
1998 ;; Fix the kill-file dir variable. 2333 ;; Fix the kill-file dir variable.
1999 (setq gnus-kill-files-directory 2334 (setq gnus-kill-files-directory
2000 (file-name-as-directory gnus-kill-files-directory)) 2335 (file-name-as-directory gnus-kill-files-directory))
2001 ;; If we can't read it, there are no score files. 2336 ;; If we can't read it, there are no score files.
2002 (if (not (file-exists-p (expand-file-name gnus-kill-files-directory))) 2337 (if (not (file-exists-p (expand-file-name gnus-kill-files-directory)))
2026 (gnus-score-file-regexp))))))) 2361 (gnus-score-file-regexp)))))))
2027 (cdr gnus-score-file-list))) 2362 (cdr gnus-score-file-list)))
2028 2363
2029 (defun gnus-score-score-files-1 (dir) 2364 (defun gnus-score-score-files-1 (dir)
2030 "Return all possible score files under DIR." 2365 "Return all possible score files under DIR."
2031 (let ((files (directory-files (expand-file-name dir) t nil t)) 2366 (let ((files (list (expand-file-name dir)))
2032 (regexp (gnus-score-file-regexp)) 2367 (regexp (gnus-score-file-regexp))
2033 out file) 2368 (case-fold-search nil)
2369 seen out file)
2034 (while (setq file (pop files)) 2370 (while (setq file (pop files))
2035 (cond 2371 (cond
2036 ;; Ignore "." and "..". 2372 ;; Ignore "." and "..".
2037 ((member (file-name-nondirectory file) '("." "..")) 2373 ((member (file-name-nondirectory file) '("." ".."))
2038 nil) 2374 nil)
2039 ;; Recurse down directories. 2375 ;; Add subtrees of directory to also be searched.
2040 ((file-directory-p file) 2376 ((and (file-directory-p file)
2041 (setq out (nconc (gnus-score-score-files-1 file) out))) 2377 (not (member (file-truename file) seen)))
2378 (push (file-truename file) seen)
2379 (setq files (nconc (directory-files file t nil t) files)))
2042 ;; Add files to the list of score files. 2380 ;; Add files to the list of score files.
2043 ((string-match regexp file) 2381 ((string-match regexp file)
2044 (push file out)))) 2382 (push file out))))
2045 (or out 2383 (or out
2046 ;; Return a dummy value. 2384 ;; Return a dummy value.
2072 (erase-buffer) 2410 (erase-buffer)
2073 (insert (car sfiles)) 2411 (insert (car sfiles))
2074 (goto-char (point-min)) 2412 (goto-char (point-min))
2075 ;; First remove the suffix itself. 2413 ;; First remove the suffix itself.
2076 (when (re-search-forward (concat "." score-regexp) nil t) 2414 (when (re-search-forward (concat "." score-regexp) nil t)
2077 (replace-match "" t t) 2415 (replace-match "" t t)
2078 (goto-char (point-min)) 2416 (goto-char (point-min))
2079 (if (looking-at (regexp-quote kill-dir)) 2417 (if (looking-at (regexp-quote kill-dir))
2080 ;; If the file name was just "SCORE", `klen' is one character 2418 ;; If the file name was just "SCORE", `klen' is one character
2081 ;; too much. 2419 ;; too much.
2082 (delete-char (min (1- (point-max)) klen)) 2420 (delete-char (min (1- (point-max)) klen))
2087 (goto-char (point-min)) 2425 (goto-char (point-min))
2088 (let ((regexp (concat 2426 (let ((regexp (concat
2089 "[/:" (if trans (char-to-string trans) "") "]"))) 2427 "[/:" (if trans (char-to-string trans) "") "]")))
2090 (while (re-search-forward regexp nil t) 2428 (while (re-search-forward regexp nil t)
2091 (replace-match "." t t))) 2429 (replace-match "." t t)))
2092 ;; Cludge to get rid of "nntp+" problems. 2430 ;; Kludge to get rid of "nntp+" problems.
2093 (goto-char (point-min)) 2431 (goto-char (point-min))
2094 (and (looking-at "nn[a-z]+\\+") 2432 (when (looking-at "nn[a-z]+\\+")
2095 (progn 2433 (search-forward "+")
2096 (search-forward "+") 2434 (forward-char -1)
2097 (forward-char -1) 2435 (insert "\\")
2098 (insert "\\"))) 2436 (forward-char 1))
2099 ;; Kludge to deal with "++". 2437 ;; Kludge to deal with "++".
2100 (goto-char (point-min)) 2438 (while (search-forward "+" nil t)
2101 (while (search-forward "++" nil t) 2439 (replace-match "\\+" t t))
2102 (replace-match "\\+\\+" t t))
2103 ;; Translate "all" to ".*". 2440 ;; Translate "all" to ".*".
2104 (goto-char (point-min)) 2441 (goto-char (point-min))
2105 (while (search-forward "all" nil t) 2442 (while (search-forward "all" nil t)
2106 (replace-match ".*" t t)) 2443 (replace-match ".*" t t))
2107 (goto-char (point-min)) 2444 (goto-char (point-min))
2108 ;; Deal with "not."s. 2445 ;; Deal with "not."s.
2109 (if (looking-at "not.") 2446 (if (looking-at "not.")
2110 (progn 2447 (progn
2111 (setq not-match t) 2448 (setq not-match t)
2112 (setq regexp (buffer-substring 5 (point-max)))) 2449 (setq regexp (concat "^" (buffer-substring 5 (point-max)))))
2113 (setq regexp (buffer-substring 1 (point-max))) 2450 (setq regexp (concat "^" (buffer-substring 1 (point-max))))
2114 (setq not-match nil)) 2451 (setq not-match nil))
2115 ;; Finally - if this resulting regexp matches the group name, 2452 ;; Finally - if this resulting regexp matches the group name,
2116 ;; we add this score file to the list of score files 2453 ;; we add this score file to the list of score files
2117 ;; applicable to this group. 2454 ;; applicable to this group.
2118 (if (or (and not-match 2455 (when (or (and not-match
2119 (not (string-match regexp group))) 2456 (not (string-match regexp group)))
2120 (and (not not-match) 2457 (and (not not-match)
2121 (string-match regexp group))) 2458 (string-match regexp group)))
2122 (setq ofiles (cons (car sfiles) ofiles)))) 2459 (push (car sfiles) ofiles)))
2123 (setq sfiles (cdr sfiles))) 2460 (setq sfiles (cdr sfiles)))
2124 (kill-buffer (current-buffer)) 2461 (kill-buffer (current-buffer))
2125 ;; Slight kludge here - the last score file returned should be 2462 ;; Slight kludge here - the last score file returned should be
2126 ;; the local score file, whether it exists or not. This is so 2463 ;; the local score file, whether it exists or not. This is so
2127 ;; that any score commands the user enters will go to the right 2464 ;; that any score commands the user enters will go to the right
2128 ;; file, and not end up in some global score file. 2465 ;; file, and not end up in some global score file.
2129 (let ((localscore (gnus-score-file-name group))) 2466 (let ((localscore (gnus-score-file-name group)))
2130 (setq ofiles (cons localscore (delete localscore ofiles)))) 2467 (setq ofiles (cons localscore (delete localscore ofiles))))
2131 (nreverse ofiles)))) 2468 (gnus-sort-score-files (nreverse ofiles)))))
2132 2469
2133 (defun gnus-score-find-single (group) 2470 (defun gnus-score-find-single (group)
2134 "Return list containing the score file for GROUP." 2471 "Return list containing the score file for GROUP."
2135 (list (or gnus-newsgroup-adaptive-score-file 2472 (list (or gnus-newsgroup-adaptive-score-file
2136 (gnus-score-file-name group gnus-adaptive-file-suffix)) 2473 (gnus-score-file-name group gnus-adaptive-file-suffix))
2137 (gnus-score-file-name group))) 2474 (gnus-score-file-name group)))
2138 2475
2139 (defun gnus-score-find-hierarchical (group) 2476 (defun gnus-score-find-hierarchical (group)
2140 "Return list of score files for GROUP. 2477 "Return list of score files for GROUP.
2141 This includes the score file for the group and all its parents." 2478 This includes the score file for the group and all its parents."
2142 (let ((all (copy-sequence '(nil))) 2479 (let* ((prefix (gnus-group-real-prefix group))
2143 (start 0)) 2480 (all (list nil))
2481 (group (gnus-group-real-name group))
2482 (start 0))
2144 (while (string-match "\\." group (1+ start)) 2483 (while (string-match "\\." group (1+ start))
2145 (setq start (match-beginning 0)) 2484 (setq start (match-beginning 0))
2146 (setq all (cons (substring group 0 start) all))) 2485 (push (substring group 0 start) all))
2147 (setq all (cons group all)) 2486 (push group all)
2148 (nconc 2487 (setq all
2149 (mapcar (lambda (newsgroup) 2488 (nconc
2150 (gnus-score-file-name newsgroup gnus-adaptive-file-suffix)) 2489 (mapcar (lambda (group)
2151 (setq all (nreverse all))) 2490 (gnus-score-file-name group gnus-adaptive-file-suffix))
2152 (mapcar 'gnus-score-file-name all)))) 2491 (setq all (nreverse all)))
2492 (mapcar 'gnus-score-file-name all)))
2493 (if (equal prefix "")
2494 all
2495 (mapcar
2496 (lambda (file)
2497 (concat (file-name-directory file) prefix
2498 (file-name-nondirectory file)))
2499 all))))
2500
2501 (defun gnus-score-file-rank (file)
2502 "Return a number that says how specific score FILE is.
2503 Destroys the current buffer."
2504 (if (member file gnus-internal-global-score-files)
2505 0
2506 (when (string-match
2507 (concat "^" (regexp-quote
2508 (expand-file-name
2509 (file-name-as-directory gnus-kill-files-directory))))
2510 file)
2511 (setq file (substring file (match-end 0))))
2512 (insert file)
2513 (goto-char (point-min))
2514 (let ((beg (point))
2515 elems)
2516 (while (re-search-forward "[./]" nil t)
2517 (push (buffer-substring beg (1- (point)))
2518 elems))
2519 (erase-buffer)
2520 (setq elems (delete "all" elems))
2521 (length elems))))
2522
2523 (defun gnus-sort-score-files (files)
2524 "Sort FILES so that the most general files come first."
2525 (nnheader-temp-write nil
2526 (let ((alist
2527 (mapcar
2528 (lambda (file)
2529 (cons (inline (gnus-score-file-rank file)) file))
2530 files)))
2531 (mapcar
2532 (lambda (f) (cdr f))
2533 (sort alist (lambda (f1 f2) (< (car f1) (car f2))))))))
2153 2534
2154 (defun gnus-score-find-alist (group) 2535 (defun gnus-score-find-alist (group)
2155 "Return list of score files for GROUP. 2536 "Return list of score files for GROUP.
2156 The list is determined from the variable gnus-score-file-alist." 2537 The list is determined from the variable gnus-score-file-alist."
2157 (let ((alist gnus-score-file-multiple-match-alist) 2538 (let ((alist gnus-score-file-multiple-match-alist)
2159 ;; if this group has been seen before, return the cached entry 2540 ;; if this group has been seen before, return the cached entry
2160 (if (setq score-files (assoc group gnus-score-file-alist-cache)) 2541 (if (setq score-files (assoc group gnus-score-file-alist-cache))
2161 (cdr score-files) ;ensures caching groups with no matches 2542 (cdr score-files) ;ensures caching groups with no matches
2162 ;; handle the multiple match alist 2543 ;; handle the multiple match alist
2163 (while alist 2544 (while alist
2164 (and (string-match (caar alist) group) 2545 (when (string-match (caar alist) group)
2165 (setq score-files 2546 (setq score-files
2166 (nconc score-files (copy-sequence (cdar alist))))) 2547 (nconc score-files (copy-sequence (cdar alist)))))
2167 (setq alist (cdr alist))) 2548 (setq alist (cdr alist)))
2168 (setq alist gnus-score-file-single-match-alist) 2549 (setq alist gnus-score-file-single-match-alist)
2169 ;; handle the single match alist 2550 ;; handle the single match alist
2170 (while alist 2551 (while alist
2171 (and (string-match (caar alist) group) 2552 (when (string-match (caar alist) group)
2172 ;; progn used just in case ("regexp") has no files 2553 ;; progn used just in case ("regexp") has no files
2173 ;; and score-files is still nil. -sj 2554 ;; and score-files is still nil. -sj
2174 ;; this can be construed as a "stop searching here" feature :> 2555 ;; this can be construed as a "stop searching here" feature :>
2175 ;; and used to simplify regexps in the single-alist 2556 ;; and used to simplify regexps in the single-alist
2176 (progn 2557 (setq score-files
2177 (setq score-files 2558 (nconc score-files (copy-sequence (cdar alist))))
2178 (nconc score-files (copy-sequence (cdar alist)))) 2559 (setq alist nil))
2179 (setq alist nil)))
2180 (setq alist (cdr alist))) 2560 (setq alist (cdr alist)))
2181 ;; cache the score files 2561 ;; cache the score files
2182 (setq gnus-score-file-alist-cache 2562 (push (cons group score-files) gnus-score-file-alist-cache)
2183 (cons (cons group score-files) gnus-score-file-alist-cache))
2184 score-files))) 2563 score-files)))
2185 2564
2186 (defun gnus-possibly-score-headers (&optional trace) 2565 (defun gnus-all-score-files (&optional group)
2566 "Return a list of all score files for the current group."
2187 (let ((funcs gnus-score-find-score-files-function) 2567 (let ((funcs gnus-score-find-score-files-function)
2568 (group (or group gnus-newsgroup-name))
2188 score-files) 2569 score-files)
2189 ;; Make sure funcs is a list. 2570 ;; Make sure funcs is a list.
2190 (and funcs 2571 (and funcs
2191 (not (listp funcs)) 2572 (not (listp funcs))
2192 (setq funcs (list funcs))) 2573 (setq funcs (list funcs)))
2193 ;; Get the initial score files for this group. 2574 ;; Get the initial score files for this group.
2194 (when funcs 2575 (when funcs
2195 (setq score-files (gnus-score-find-alist gnus-newsgroup-name))) 2576 (setq score-files (nreverse (gnus-score-find-alist group))))
2577 ;; Add any home adapt files.
2578 (let ((home (gnus-home-score-file group t)))
2579 (when home
2580 (push home score-files)
2581 (setq gnus-newsgroup-adaptive-score-file home)))
2582 ;; Check whether there is a `adapt-file' group parameter.
2583 (let ((param-file (gnus-group-find-parameter group 'adapt-file)))
2584 (when param-file
2585 (push param-file score-files)
2586 (setq gnus-newsgroup-adaptive-score-file param-file)))
2196 ;; Go through all the functions for finding score files (or actual 2587 ;; Go through all the functions for finding score files (or actual
2197 ;; scores) and add them to a list. 2588 ;; scores) and add them to a list.
2198 (while funcs 2589 (while funcs
2199 (when (gnus-functionp (car funcs)) 2590 (when (gnus-functionp (car funcs))
2200 (setq score-files 2591 (setq score-files
2201 (nconc score-files (funcall (car funcs) gnus-newsgroup-name)))) 2592 (nconc score-files (nreverse (funcall (car funcs) group)))))
2202 (setq funcs (cdr funcs))) 2593 (setq funcs (cdr funcs)))
2594 ;; Add any home score files.
2595 (let ((home (gnus-home-score-file group)))
2596 (when home
2597 (push home score-files)))
2203 ;; Check whether there is a `score-file' group parameter. 2598 ;; Check whether there is a `score-file' group parameter.
2204 (let ((param-file (gnus-group-get-parameter 2599 (let ((param-file (gnus-group-find-parameter group 'score-file)))
2205 gnus-newsgroup-name 'score-file)))
2206 (when param-file 2600 (when param-file
2207 (push param-file score-files))) 2601 (push param-file score-files)))
2602 ;; Expand all files names.
2603 (let ((files score-files))
2604 (while files
2605 (when (stringp (car files))
2606 (setcar files (expand-file-name
2607 (car files) gnus-kill-files-directory)))
2608 (pop files)))
2609 (setq score-files (nreverse score-files))
2610 ;; Remove any duplicate score files.
2611 (while (and score-files
2612 (member (car score-files) (cdr score-files)))
2613 (pop score-files))
2614 (let ((files score-files))
2615 (while (cdr files)
2616 (when (member (cadr files) (cddr files))
2617 (setcdr files (cddr files)))
2618 (pop files)))
2208 ;; Do the scoring if there are any score files for this group. 2619 ;; Do the scoring if there are any score files for this group.
2620 score-files))
2621
2622 (defun gnus-possibly-score-headers (&optional trace)
2623 "Do scoring if scoring is required."
2624 (let ((score-files (gnus-all-score-files)))
2209 (when score-files 2625 (when score-files
2210 (gnus-score-headers score-files trace)))) 2626 (gnus-score-headers score-files trace))))
2211 2627
2212 (defun gnus-score-file-name (newsgroup &optional suffix) 2628 (defun gnus-score-file-name (newsgroup &optional suffix)
2213 "Return the name of a score file for NEWSGROUP." 2629 "Return the name of a score file for NEWSGROUP."
2239 (while files 2655 (while files
2240 (if (string-match "/$" (car files)) 2656 (if (string-match "/$" (car files))
2241 (setq out (nconc (directory-files 2657 (setq out (nconc (directory-files
2242 (car files) t 2658 (car files) t
2243 (concat (gnus-score-file-regexp) "$")))) 2659 (concat (gnus-score-file-regexp) "$"))))
2244 (setq out (cons (car files) out))) 2660 (push (car files) out))
2245 (setq files (cdr files))) 2661 (setq files (cdr files)))
2246 (setq gnus-internal-global-score-files out))) 2662 (setq gnus-internal-global-score-files out)))
2247 2663
2248 (defun gnus-score-default-fold-toggle () 2664 (defun gnus-score-default-fold-toggle ()
2249 "Toggle folding for new score file entries." 2665 "Toggle folding for new score file entries."
2251 (setq gnus-score-default-fold (not gnus-score-default-fold)) 2667 (setq gnus-score-default-fold (not gnus-score-default-fold))
2252 (if gnus-score-default-fold 2668 (if gnus-score-default-fold
2253 (gnus-message 1 "New score file entries will be case insensitive.") 2669 (gnus-message 1 "New score file entries will be case insensitive.")
2254 (gnus-message 1 "New score file entries will be case sensitive."))) 2670 (gnus-message 1 "New score file entries will be case sensitive.")))
2255 2671
2672 ;;; Home score file.
2673
2674 (defun gnus-home-score-file (group &optional adapt)
2675 "Return the home score file for GROUP.
2676 If ADAPT, return the home adaptive file instead."
2677 (let ((list (if adapt gnus-home-adapt-file gnus-home-score-file))
2678 elem found)
2679 ;; Make sure we have a list.
2680 (unless (listp list)
2681 (setq list (list list)))
2682 ;; Go through the list and look for matches.
2683 (while (and (not found)
2684 (setq elem (pop list)))
2685 (setq found
2686 (cond
2687 ;; Simple string.
2688 ((stringp elem)
2689 elem)
2690 ;; Function.
2691 ((gnus-functionp elem)
2692 (funcall elem group))
2693 ;; Regexp-file cons
2694 ((consp elem)
2695 (when (string-match (car elem) group)
2696 (cadr elem))))))
2697 (when found
2698 (nnheader-concat gnus-kill-files-directory found))))
2699
2700 (defun gnus-hierarchial-home-score-file (group)
2701 "Return the score file of the top-level hierarchy of GROUP."
2702 (if (string-match "^[^.]+\\." group)
2703 (concat (match-string 0 group) gnus-score-file-suffix)
2704 ;; Group name without any dots.
2705 (concat group "." gnus-score-file-suffix)))
2706
2707 (defun gnus-hierarchial-home-adapt-file (group)
2708 "Return the adapt file of the top-level hierarchy of GROUP."
2709 (if (string-match "^[^.]+\\." group)
2710 (concat (match-string 0 group) gnus-adaptive-file-suffix)
2711 ;; Group name without any dots.
2712 (concat group "." gnus-adaptive-file-suffix)))
2713
2714 ;;;
2715 ;;; Score decays
2716 ;;;
2717
2718 (defun gnus-decay-score (score)
2719 "Decay SCORE."
2720 (floor
2721 (- score
2722 (* (if (< score 0) 1 -1)
2723 (min score
2724 (max gnus-score-decay-constant
2725 (* (abs score)
2726 gnus-score-decay-scale)))))))
2727
2728 (defun gnus-decay-scores (alist day)
2729 "Decay non-permanent scores in ALIST."
2730 (let ((times (- (gnus-time-to-day (current-time)) day))
2731 kill entry updated score n)
2732 (unless (zerop times) ;Done decays today already?
2733 (while (setq entry (pop alist))
2734 (when (stringp (car entry))
2735 (setq entry (cdr entry))
2736 (while (setq kill (pop entry))
2737 (when (nth 2 kill)
2738 (setq updated t)
2739 (setq score (or (car kill) gnus-score-interactive-default-score)
2740 n times)
2741 (while (natnump (decf n))
2742 (setq score (funcall gnus-decay-score-function score)))
2743 (setcar kill score))))))
2744 ;; Return whether this score file needs to be saved. By Je-haysuss!
2745 updated))
2746
2256 (provide 'gnus-score) 2747 (provide 'gnus-score)
2257 2748
2258 ;;; gnus-score.el ends here 2749 ;;; gnus-score.el ends here