;;; gnus-registry.el --- article registry for Gnus
-;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
-;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+;;; Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+;;; Free Software Foundation, Inc.
;; Author: Ted Zlatanov <tzz@lifelogs.com>
-;; Keywords: news
+;; Keywords: news registry
;; This file is part of GNU Emacs.
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 3, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING. If not, write to the
-;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
-;; Boston, MA 02110-1301, USA.
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
(require 'gnus-sum)
(require 'gnus-util)
(require 'nnmail)
+(require 'easymenu)
(defvar gnus-adaptive-word-syntax-table)
(const :tag "Always Install" t)
(const :tag "Ask Me" ask)))
+(defvar gnus-summary-misc-menu) ;; Avoid byte compiler warning.
+
+(defvar gnus-registry-misc-menus nil) ; ugly way to keep the menus
+
(defcustom gnus-registry-clean-empty t
"Whether the empty registry entries should be deleted.
Registry entries are considered empty when they have no groups
:group 'gnus-registry
:type 'boolean)
-(defcustom gnus-registry-use-long-group-names nil
- "Whether the registry should use long group names (BUGGY)."
+(defcustom gnus-registry-use-long-group-names t
+ "Whether the registry should use long group names."
:group 'gnus-registry
:type 'boolean)
+(defcustom gnus-registry-max-track-groups 20
+ "The maximum number of non-unique group matches to check for a message ID."
+ :group 'gnus-registry
+ :type '(radio (const :format "Unlimited " nil)
+ (integer :format "Maximum non-unique matches: %v")))
+
(defcustom gnus-registry-track-extra nil
"Whether the registry should track extra data about a message.
The Subject and Sender (From:) headers are currently tracked this
(const :tag "Track by subject (Subject: header)" subject)
(const :tag "Track by sender (From: header)" sender)))
+(defcustom gnus-registry-split-strategy nil
+ "Whether the registry should track extra data about a message.
+The Subject and Sender (From:) headers are currently tracked this
+way."
+ :group 'gnus-registry
+ :type
+ '(choice :tag "Tracking choices"
+ (const :tag "Only use single choices, discard multiple matches" nil)
+ (const :tag "Majority of matches wins" majority)
+ (const :tag "First found wins" first)))
+
(defcustom gnus-registry-entry-caching t
"Whether the registry should cache extra information."
:group 'gnus-registry
(if (and (eq system-type 'ms-dos)
(not (gnus-long-file-names)))
"%s#%d.tm#" ; MSDOS limits files to 8+3
- (if (memq system-type '(vax-vms axp-vms))
- "%s$tmp$%d"
- "%s#tmp#%d"))
+ "%s#tmp#%d")
working-dir (setq i (1+ i))))
(file-exists-p working-file)))
nnmail-split-fancy-with-parent-ignore-groups
(list nnmail-split-fancy-with-parent-ignore-groups)))
(log-agent "gnus-registry-split-fancy-with-parent")
- found)
+ found found-full)
;; this is a big if-else statement. it uses
;; gnus-registry-post-process-groups to filter the results after
9
"%s is looking for matches for reference %s from [%s]"
log-agent reference refstr)
- (dolist (group (gnus-registry-fetch-groups reference))
+ (dolist (group (gnus-registry-fetch-groups
+ reference
+ gnus-registry-max-track-groups))
(when (and group (gnus-registry-follow-group-p group))
(gnus-message
7
log-agent reference refstr group)
(push group found))))
;; filter the found groups and return them
+ ;; the found groups are the full groups
(setq found (gnus-registry-post-process-groups
- "references" refstr found)))
-
+ "references" refstr found found)))
+
;; else: there were no matches, now try the extra tracking by sender
- ((and (gnus-registry-track-sender-p)
- sender)
+ ((and (gnus-registry-track-sender-p)
+ sender
+ (not (equal (gnus-extract-address-component-email sender)
+ user-mail-address)))
(maphash
(lambda (key value)
(let ((this-sender (cdr
matches)
(when (and this-sender
(equal sender this-sender))
- (setq found (append (gnus-registry-fetch-groups key) found))
+ (let ((groups (gnus-registry-fetch-groups
+ key
+ gnus-registry-max-track-groups)))
+ (dolist (group groups)
+ (push group found-full)
+ (setq found (append (list group) (delete group found)))))
(push key matches)
(gnus-message
;; raise level of messaging if gnus-registry-track-extra
log-agent sender found matches))))
gnus-registry-hashtb)
;; filter the found groups and return them
- (setq found (gnus-registry-post-process-groups "sender" sender found)))
+ ;; the found groups are NOT the full groups
+ (setq found (gnus-registry-post-process-groups
+ "sender" sender found found-full)))
;; else: there were no matches, now try the extra tracking by subject
((and (gnus-registry-track-subject-p)
matches)
(when (and this-subject
(equal subject this-subject))
- (setq found (append (gnus-registry-fetch-groups key) found))
+ (let ((groups (gnus-registry-fetch-groups
+ key
+ gnus-registry-max-track-groups)))
+ (dolist (group groups)
+ (push group found-full)
+ (setq found (append (list group) (delete group found)))))
(push key matches)
(gnus-message
;; raise level of messaging if gnus-registry-track-extra
log-agent subject found matches))))
gnus-registry-hashtb)
;; filter the found groups and return them
+ ;; the found groups are NOT the full groups
(setq found (gnus-registry-post-process-groups
- "subject" subject found))))))
+ "subject" subject found found-full))))
+ ;; after the (cond) we extract the actual value safely
+ (car-safe found)))
-(defun gnus-registry-post-process-groups (mode key groups)
+(defun gnus-registry-post-process-groups (mode key groups groups-full)
"Modifies GROUPS found by MODE for KEY to determine which ones to follow.
MODE can be 'subject' or 'sender' for example. The KEY is the
false. Foreign methods are not supported so they are rejected.
Reduces the list to a single group, or complains if that's not
-possible."
+possible. Uses `gnus-registry-split-strategy' and GROUPS-FULL if
+necessary."
(let ((log-agent "gnus-registry-post-process-group")
out)
+
+ ;; the strategy can be 'first, 'majority, or nil
+ (when (eq gnus-registry-split-strategy 'first)
+ (when groups
+ (setq groups (list (car-safe groups)))))
+
+ (when (eq gnus-registry-split-strategy 'majority)
+ (let ((freq (make-hash-table
+ :size 256
+ :test 'equal)))
+ (mapc (lambda(x) (puthash x (1+ (gethash x freq 0)) freq)) groups-full)
+ (setq groups (list (car-safe
+ (sort
+ groups
+ (lambda (a b)
+ (> (gethash a freq 0)
+ (gethash b freq 0)))))))))
+
(if gnus-registry-use-long-group-names
(dolist (group groups)
(let ((m1 (gnus-find-method-for-group group))
(let (articles)
(maphash
(lambda (key value)
- (when (gnus-registry-grep-in-list
- keyword
- (cdr (gnus-registry-fetch-extra key 'keywords)))
+ (when (member keyword
+ (cdr-safe (gnus-registry-fetch-extra key 'keywords)))
(push key articles)))
gnus-registry-hashtb)
articles))
(assoc article (gnus-data-list nil)))))
nil))
-;;; this should be redone with catch/throw
(defun gnus-registry-grep-in-list (word list)
- (when word
- (memq nil
- (mapcar 'not
- (mapcar
- (lambda (x)
- (string-match word x))
- list)))))
+"Find if a WORD matches any regular expression in the given LIST."
+ (when (and word list)
+ (catch 'found
+ (dolist (r list)
+ (when (string-match r word)
+ (throw 'found r))))))
(defun gnus-registry-do-marks (type function)
"For each known mark, call FUNCTION for each cell of type TYPE.
"Install the keyboard shortcuts and menus for the registry.
Uses `gnus-registry-marks' to find what shortcuts to install."
(let (keys-plist)
- (gnus-registry-do-marks
+ (setq gnus-registry-misc-menus nil)
+ (gnus-registry-do-marks
:char
(lambda (mark data)
(let ((function-format
;; all this just to get the mark, I must be doing it wrong
(intern ,(symbol-name mark))
articles ,remove t)
+ (gnus-message
+ 9
+ "Applying mark %s to %d articles"
+ ,(symbol-name mark) (length articles))
(dolist (article articles)
(gnus-summary-update-article
- article
+ article
(assoc article (gnus-data-list nil)))))))
(push (intern function-name) keys-plist)
- (push shortcut keys-plist)
- (gnus-message
+ (push shortcut keys-plist)
+ (push (vector (format "%s %s"
+ (upcase-initials variant-name)
+ (symbol-name mark))
+ (intern function-name) t)
+ gnus-registry-misc-menus)
+ (gnus-message
9
"Defined mark handling function %s"
function-name))))))
(gnus-define-keys-1
- '(gnus-registry-mark-map "M" gnus-summary-mark-map)
- keys-plist)))
+ '(gnus-registry-mark-map "M" gnus-summary-mark-map)
+ keys-plist)
+ (add-hook 'gnus-summary-menu-hook
+ (lambda ()
+ (easy-menu-add-item
+ gnus-summary-misc-menu
+ nil
+ (cons "Registry Marks" gnus-registry-misc-menus))))))
;;; use like this:
;;; (defalias 'gnus-user-format-function-M
;;; 'gnus-registry-user-format-function-M)
- (defun gnus-registry-user-format-function-M (headers)
+(defun gnus-registry-user-format-function-M (headers)
(let* ((id (mail-header-message-id headers))
(marks (when id (gnus-registry-fetch-extra-marks id))))
- (concat (mapcar (lambda(mark)
- (list (plist-get
- (cdr-safe (assoc mark gnus-registry-marks))
- :char)))
- marks))))
+ (apply 'concat (mapcar (lambda(mark)
+ (let ((c
+ (plist-get
+ (cdr-safe
+ (assoc mark gnus-registry-marks))
+ :char)))
+ (if c
+ (list c)
+ nil)))
+ marks))))
(defun gnus-registry-read-mark ()
"Read a mark name from the user with completion."
crumb
(gnus-group-short-name crumb))))))))
-(defun gnus-registry-fetch-groups (id)
- "Get the groups of a message, based on the message ID."
+(defun gnus-registry-fetch-groups (id &optional max)
+ "Get the groups (up to MAX, if given) of a message, based on the message ID."
(let ((trail (gethash id gnus-registry-hashtb))
groups)
(dolist (crumb trail)
(if (or (not (stringp crumb)) gnus-registry-use-long-group-names)
crumb
(gnus-group-short-name crumb))
- groups))))
+ groups))
+ (when (and max (> (length groups) max))
+ (return))))
;; return the list of groups
groups))
;;; we could call it here: (customize-variable 'gnus-registry-install)
gnus-registry-install)
-(when (gnus-registry-install-p)
+(when (or (eq gnus-registry-install t)
+ (gnus-registry-install-p))
(gnus-registry-initialize))
;; TODO: a few things
(provide 'gnus-registry)
-;;; arch-tag: 5cba0a32-718a-4a97-8c91-0a15af21da94
+;; arch-tag: 5cba0a32-718a-4a97-8c91-0a15af21da94
;;; gnus-registry.el ends here