Merge the t-gnus-6_17-quimby branch.
[elisp/gnus.git-] / lisp / gnus-logic.el
1 ;;; gnus-logic.el --- advanced scoring code for Gnus
2
3 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
4 ;;   2005, 2006 Free Software Foundation, Inc.
5
6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;; Keywords: news
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 ;; Boston, MA 02110-1301, USA.
25
26 ;;; Commentary:
27
28 ;;; Code:
29
30 (eval-when-compile (require 'cl))
31
32 (require 'gnus)
33 (require 'gnus-score)
34 (require 'gnus-util)
35
36 ;;; Internal variables.
37
38 (defvar gnus-advanced-headers nil)
39
40 ;; To avoid having 8-bit characters in the source file.
41 (defvar gnus-advanced-not (intern (format "%c" 172)))
42
43 (defconst gnus-advanced-index
44   ;; Name to index alist.
45   `(("number"
46      ,(luna-class-slot-index (luna-find-class 'mime-gnus-entity) 'location)
47      gnus-advanced-integer)
48     ("subject"
49      ,(luna-class-slot-index (luna-find-class 'mime-gnus-entity) 'subject)
50      gnus-advanced-string)
51     ("from"
52      ,(luna-class-slot-index (luna-find-class 'mime-gnus-entity) 'from)
53      gnus-advanced-string)
54     ("date"
55      ,(luna-class-slot-index (luna-find-class 'mime-gnus-entity) 'date)
56      gnus-advanced-date)
57     ("message-id"
58      ,(luna-class-slot-index (luna-find-class 'mime-gnus-entity) 'id)
59      gnus-advanced-string)
60     ("references"
61      ,(luna-class-slot-index (luna-find-class 'mime-gnus-entity) 'references)
62      gnus-advanced-string)
63     ("chars"
64      ,(luna-class-slot-index (luna-find-class 'mime-gnus-entity) 'chars)
65      gnus-advanced-integer)
66     ("lines"
67      ,(luna-class-slot-index (luna-find-class 'mime-gnus-entity) 'lines)
68      gnus-advanced-integer)
69     ("xref"
70      ,(luna-class-slot-index (luna-find-class 'mime-gnus-entity) 'xref)
71      gnus-advanced-string)
72     ("head" nil gnus-advanced-body)
73     ("body" nil gnus-advanced-body)
74     ("all" nil gnus-advanced-body)))
75
76 (eval-and-compile
77   (autoload 'parse-time-string "parse-time"))
78
79 (defun gnus-score-advanced (rule &optional trace)
80   "Apply advanced scoring RULE to all the articles in the current group."
81   (let (new-score score multiple)
82     (dolist (gnus-advanced-headers gnus-newsgroup-headers)
83       (when (setq multiple (gnus-advanced-score-rule (car rule)))
84         (setq new-score (or (nth 1 rule)
85                             gnus-score-interactive-default-score))
86         (when (numberp multiple)
87           (setq new-score (* multiple new-score)))
88         ;; This rule was successful, so we add the score to this
89         ;; article.
90         (if (setq score (assq (mail-header-number gnus-advanced-headers)
91                               gnus-newsgroup-scored))
92             (setcdr score
93                     (+ (cdr score) new-score))
94           (push (cons (mail-header-number gnus-advanced-headers)
95                       new-score)
96                 gnus-newsgroup-scored)
97           (when trace
98             (push (cons "A file" rule)
99                   ;; Must be synced with `gnus-score-edit-file-at-point'.
100                   gnus-score-trace)))))))
101
102 (defun gnus-advanced-score-rule (rule)
103   "Apply RULE to `gnus-advanced-headers'."
104   (let ((type (car rule)))
105     (cond
106      ;; "And" rule.
107      ((or (eq type '&) (eq type 'and))
108       (pop rule)
109       (if (not rule)
110           t                             ; Empty rule is true.
111         (while (and rule
112                     (gnus-advanced-score-rule (car rule)))
113           (pop rule))
114         ;; If all the rules were true, then `rule' should be nil.
115         (not rule)))
116      ;; "Or" rule.
117      ((or (eq type '|) (eq type 'or))
118       (pop rule)
119       (if (not rule)
120           nil
121         (while (and rule
122                     (not (gnus-advanced-score-rule (car rule))))
123           (pop rule))
124         ;; If one of the rules returned true, then `rule' should be non-nil.
125         rule))
126      ;; "Not" rule.
127      ((or (eq type '!) (eq type 'not) (eq type gnus-advanced-not))
128       (not (gnus-advanced-score-rule (nth 1 rule))))
129      ;; This is a `1-'-type redirection rule.
130      ((and (symbolp type)
131            (string-match "^[0-9]+-$\\|^\\^+$" (symbol-name type)))
132       (let ((gnus-advanced-headers
133              (gnus-parent-headers
134               gnus-advanced-headers
135               (if (string-match "^\\([0-9]+\\)-$" (symbol-name type))
136                   ;; 1- type redirection.
137                   (string-to-number
138                    (substring (symbol-name type)
139                               (match-beginning 1) (match-end 1)))
140                 ;; ^^^ type redirection.
141                 (length (symbol-name type))))))
142         (when gnus-advanced-headers
143           (gnus-advanced-score-rule (nth 1 rule)))))
144      ;; Plain scoring rule.
145      ((stringp type)
146       (gnus-advanced-score-article rule))
147      ;; Bug-out time!
148      (t
149       (error "Unknown advanced score type: %s" rule)))))
150
151 (defun gnus-advanced-score-article (rule)
152   ;; `rule' is a semi-normal score rule, so we find out what function
153   ;; that's supposed to do the actual processing.
154   (let* ((header (car rule))
155          (func (assoc (downcase header) gnus-advanced-index)))
156     (if (not func)
157         (error "No such header: %s" rule)
158       ;; Call the score function.
159       (funcall (caddr func) (or (cadr func) header)
160                (cadr rule) (caddr rule)))))
161
162 (defun gnus-advanced-string (index match type)
163   "See whether string MATCH of TYPE matches `gnus-advanced-headers' in INDEX."
164   (let* ((type (or type 's))
165          (case-fold-search (not (eq (downcase (symbol-name type))
166                                     (symbol-name type))))
167          (header (or (aref gnus-advanced-headers index) "")))
168     (cond
169      ((memq type '(r R regexp Regexp))
170       (string-match match header))
171      ((memq type '(s S string String))
172       (string-match (regexp-quote match) header))
173      ((memq type '(e E exact Exact))
174       (string= match header))
175      ((memq type '(f F fuzzy Fuzzy))
176       (string-match (regexp-quote (gnus-simplify-subject-fuzzy match))
177                     header))
178      (t
179       (error "No such string match type: %s" type)))))
180
181 (defun gnus-advanced-integer (index match type)
182   (if (not (memq type '(< > <= >= =)))
183       (error "No such integer score type: %s" type)
184     (funcall type (or (aref gnus-advanced-headers index) 0) match)))
185
186 (defun gnus-advanced-date (index match type)
187   (let ((date (apply 'encode-time (parse-time-string
188                                    (aref gnus-advanced-headers index))))
189         (match (apply 'encode-time (parse-time-string match))))
190     (cond
191      ((eq type 'at)
192       (equal date match))
193      ((eq type 'before)
194       (time-less-p match date))
195      ((eq type 'after)
196       (time-less-p date match))
197      (t
198       (error "No such date score type: %s" type)))))
199
200 (defun gnus-advanced-body (header match type)
201   (when (string= header "all")
202     (setq header "article"))
203   (save-excursion
204     (set-buffer nntp-server-buffer)
205     (let* ((request-func (cond ((string= "head" header)
206                                 'gnus-request-head)
207                                ((string= "body" header)
208                                 'gnus-request-body)
209                                (t 'gnus-request-article)))
210            ofunc article)
211       ;; Not all backends support partial fetching.  In that case, we
212       ;; just fetch the entire article.
213       (unless (gnus-check-backend-function
214                (intern (concat "request-" header))
215                gnus-newsgroup-name)
216         (setq ofunc request-func)
217         (setq request-func 'gnus-request-article))
218       (setq article (mail-header-number gnus-advanced-headers))
219       (gnus-message 7 "Scoring article %s..." article)
220       (when (funcall request-func article gnus-newsgroup-name)
221         (goto-char (point-min))
222         ;; If just parts of the article is to be searched and the
223         ;; backend didn't support partial fetching, we just narrow to
224         ;; the relevant parts.
225         (when ofunc
226           (if (eq ofunc 'gnus-request-head)
227               (narrow-to-region
228                (point)
229                (or (search-forward "\n\n" nil t) (point-max)))
230             (narrow-to-region
231              (or (search-forward "\n\n" nil t) (point))
232              (point-max))))
233         (let* ((case-fold-search (not (eq (downcase (symbol-name type))
234                                           (symbol-name type))))
235                (search-func
236                 (cond ((memq type '(r R regexp Regexp))
237                        're-search-forward)
238                       ((memq type '(s S string String))
239                        'search-forward)
240                       (t
241                        (error "Invalid match type: %s" type)))))
242           (goto-char (point-min))
243           (prog1
244               (funcall search-func match nil t)
245             (widen)))))))
246
247 (provide 'gnus-logic)
248
249 ;;; gnus-logic.el ends here