* gnus.el (gnus-group-short-name, gnus-group-prefixed-p): new functions
[gnus] / lisp / nndiary.el
index ecaf5ea..cec8ee4 100644 (file)
@@ -1,23 +1,21 @@
 ;;; nndiary.el --- A diary backend for Gnus
 
-;; Copyright (C) 1999-2001 Didier Verna.
-
-;; PRCS: $Id: nndiary.el 1.22 Tue, 04 Sep 2001 11:32:13 +0200 didier $
+;; Copyright (C) 1999, 2000, 2001
+;;        Free Software Foundation, Inc.
 
 ;; Author:        Didier Verna <didier@xemacs.org>
 ;; Maintainer:    Didier Verna <didier@xemacs.org>
 ;; Created:       Fri Jul 16 18:55:42 1999
-;; Last Revision: Wed Aug  8 17:36:21 2001
 ;; Keywords:      calendar mail news
 
-;; This file is part of NNDiary.
+;; This file is part of GNU Emacs.
 
-;; NNDiary 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 2 of the License, or
 ;; (at your option) any later version.
 
-;; NNDiary is distributed in the hope that it will be useful,
+;; 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
 ;; GNU General Public License for more details.
 ;; Usage:
 ;; =====
 
-;;  1/ Diary messages contain several `X-Diary-*' special headers.  You *must*
-;;     arrange that these messages be split in a private folder *before* Gnus
-;;     treat them.  You need this because Gnus is not able yet to manage
-;;     multiple backends for mail retrieval.  Getting them from a separate
-;;     source will compensate this misfeature to some extent, as we will see.
-;;     As an example, here's my procmailrc entry to store diary files in
-;;     ~/.nndiary (the default nndiary mail source file):
-;;
-;;      :0 HD :
-;;      * ^X-Diary
-;;      .nndiary
+;;  1/ NNDiary has two modes of operation: traditional (the default) and
+;;     autonomous.
+;;     a/ In traditional mode, NNDiary does not get new mail by itself.  You
+;;        have to move mails from your primary mail backend to nndiary
+;;        groups.
+;;     b/ In autonomous mode, NNDiary retrieves its own mail and handles it
+;;        independantly of your primary mail backend.  To use NNDiary in
+;;        autonomous mode, you have several things to do:
+;;           i/ Put (setq nndiary-get-new-mail t) in your gnusrc file.
+;;          ii/ Diary messages contain several `X-Diary-*' special headers.
+;;              You *must* arrange that these messages be split in a private
+;;              folder *before* Gnus treat them.  You need this because Gnus
+;;              is not able yet to manage multiple backends for mail
+;;              retrieval.  Getting them from a separate source will
+;;              compensate this misfeature to some extent, as we will see.
+;;              As an example, here's my procmailrc entry to store diary files
+;;              in ~/.nndiary (the default nndiary mail source file):
 ;;
+;;              :0 HD :
+;;              * ^X-Diary
+;;              .nndiary
+;;         iii/ Customize the variables `nndiary-mail-sources' and
+;;              `nndiary-split-methods'.  These are replacements for the usual
+;;              mail sources and split methods which, and will be used in
+;;              autonomous mode.  `nndiary-mail-sources' defaults to
+;;              '(file :path "~/.nndiary").
 ;;  2/ Install nndiary somewhere Emacs / Gnus can find it.  Normally, you
 ;;     *don't* have to '(require 'nndiary) anywhere.  Gnus will do so when
 ;;     appropriate as long as nndiary is somewhere in the load path.
-;;  3/ Now, customize nndiary: type `M-x customize-group', and then `nndiary'
-;;     at the prompt (note that if you have not restarted Emacs yet, you'll
-;;     have to the load the library by hand before being able to customize it).
-;;     In particular, you should customize the following options:
-;;     - `nndiary-mail-sources', which overrides the normal `mail-sources'
-;;       value for diary messages retrieving.  It defaults to
-;;       '(file :path "~/.nndiary").
-;;     - `nndiary-split-methods', which overrides the normal
-;;       `nnmail-split-methods' value for diary messages splitting.  You can
-;;       have all the diary groups you want (for example, I have a birthdays
-;;       group, and stuff like that).
-;;     - `nndiary-reminders', the list of times when you want to be reminded
-;;       of your appointements (e.g. 3 weeks before, then 2 days before, then
-;;       1 hour before and that's it).
+;;  3/ Now, customize the rest of nndiary.  In particular, you should
+;;     customize `nndiary-reminders', the list of times when you want to be
+;;     reminded of your appointements (e.g. 3 weeks before, then 2 days
+;;     before, then 1 hour before and that's it).
 ;;  4/ You *must* use the group timestamp feature of Gnus.  This adds a
 ;;     timestamp to each groups' parameters (please refer to the Gnus
 ;;     documentation ("Group Timestamp" info node) to see how it's done.
 ;; * Respooling doesn't work because contrary to the request-scan function,
 ;;   Gnus won't allow me to override the split methods when calling the
 ;;   respooling backend functions.
-;; * The time zone mechanism is subject to change.
+;; * There's a bug in the time zone mechanism with variable TZ locations.
 ;; * We could allow a keyword like `ask' in X-Diary-* headers, that would mean
-;;   "ask for value upon reception of the message". Suggested by Jody Klymak.
+;;   "ask for value upon reception of the message".
 ;; * We could add an optional header X-Diary-Reminders to specify a special
 ;;   reminders value for this message. Suggested by Jody Klymak.
-;; * Modify the request-accept-article function to make it prompt for diary
-;;   headers if they're missing.
+;; * We should check messages validity in other circumstances than just
+;;   moving an article from sonwhere else (request-accept). For instance, when
+;;   editing / saving and so on.
+
 
 ;; Remarks:
 ;; =======
 
 ;; Compatibility Functions  =================================================
 
-(if (fboundp 'signal-error)
+(eval-and-compile
+  (if (fboundp 'signal-error)
+      (defun nndiary-error (&rest args)
+       (apply #'signal-error 'nndiary args))
     (defun nndiary-error (&rest args)
-      (apply #'signal-error 'nndiary args))
-  (defun nndiary-error (&rest args)
-    (apply #'error args)))
+      (apply #'error args))))
 
 
 ;; Backend behavior customization ===========================================
   `((file :path ,(expand-file-name "~/.nndiary")))
   "*NNDiary specific mail sources.
 This variable is used by nndiary in place of the standard `mail-sources'
-variable.  These sources must contain diary messages ONLY."
+variable when `nndiary-get-new-mail' is set to non-nil.  These sources
+must contain diary messages ONLY."
   :group 'nndiary
   :group 'mail-source
   :type 'sexp)
@@ -232,7 +238,8 @@ variable.  These sources must contain diary messages ONLY."
 (defcustom nndiary-split-methods '(("diary" ""))
   "*NNDiary specific split methods.
 This variable is used by nndiary in place of the standard
-`nnmail-split-methods' variable."
+`nnmail-split-methods' variable when `nndiary-get-new-mail' is set to
+non-nil."
   :group 'nndiary
   :group 'nnmail-split
   :type '(choice (repeat :tag "Alist" (group (string :tag "Name") regexp))
@@ -304,6 +311,13 @@ The hooks will be called with the full group name as argument."
   :group 'nndiary
   :type 'hook)
 
+(defcustom nndiary-request-accept-article-hooks nil
+  "*Hooks to run before accepting an article.
+Executed near the beginning of `nndiary-request-accept-article'.
+The hooks will be called with the article in the current buffer."
+  :group 'nndiary
+  :type 'hook)
+
 (defcustom nndiary-check-directory-twice t
   "*If t, check directories twice to avoid NFS failures."
   :group 'nndiary
@@ -327,10 +341,11 @@ The hooks will be called with the full group name as argument."
     (expand-file-name "newsgroups" nndiary-directory)
   "Newsgroups description file for the nndiary backend.")
 
-(defvoo nndiary-get-new-mail t
+(defvoo nndiary-get-new-mail nil
   "Whether nndiary gets new mail and split it.
-Contrary to traditional mail backends, this variable should always be
-non-nil because nndiary uses its own mail-sources and split-methods.")
+Contrary to traditional mail backends, this variable can be set to t
+even if your primary mail backend also retreives mail. In such a case,
+NDiary uses its own mail-sources and split-methods.")
 
 (defvoo nndiary-nov-is-evil nil
   "If non-nil, Gnus will never use nov databases for nndiary groups.
@@ -349,25 +364,7 @@ all.  This may very well take some time.")
 
 \f
 
-;; $Format: "(defconst nndiary-prcs-major-version \"$ProjectMajorVersion$\")"$
-(defconst nndiary-prcs-major-version "branch-0-2")
-;; $Format: "(defconst nndiary-prcs-minor-version \"$ProjectMinorVersion$\")"$
-(defconst nndiary-prcs-minor-version "1")
-(defconst nndiary-version
-  (let ((level nndiary-prcs-minor-version)
-       major minor status)
-    (string-match "\\(branch\\|version\\)-\\([0-9]+\\)-\\([0-9]+\\)"
-                 nndiary-prcs-major-version)
-    (setq major (match-string 2 nndiary-prcs-major-version)
-         minor (match-string 3 nndiary-prcs-major-version)
-         status (match-string 1 nndiary-prcs-major-version))
-    (cond ((string= status "version")
-          (setq level (int-to-string (1- (string-to-int level))))
-          (if (eq level 0)
-              (concat major "." minor)
-            (concat major "." minor "." level)))
-         ((string= status "branch")
-          (concat major "." minor "-b" level))))
+(defconst nndiary-version "0.2-b14"
   "Current Diary backend version.")
 
 (defun nndiary-version ()
@@ -375,7 +372,6 @@ all.  This may very well take some time.")
   (interactive)
   (message "NNDiary version %s" nndiary-version))
 
-
 (defvoo nndiary-nov-file-name ".overview")
 
 (defvoo nndiary-current-directory nil)
@@ -483,6 +479,19 @@ all.  This may very well take some time.")
   ;; the (relative) number of seconds ahead GMT.
   )
 
+(defsubst nndiary-schedule ()
+  (let (head)
+    (condition-case arg
+       (mapcar
+        (lambda (elt)
+          (setq head (nth 0 elt))
+          (nndiary-parse-schedule (nth 0 elt) (nth 1 elt) (nth 2 elt)))
+        nndiary-headers)
+      (t
+       (nnheader-report 'nndiary "X-Diary-%s header parse error: %s."
+                       head (cdr arg))
+       nil))
+    ))
 
 ;;; Interface functions =====================================================
 
@@ -695,7 +704,8 @@ all.  This may very well take some time.")
              (with-temp-buffer
                (nndiary-request-article number group server (current-buffer))
                (let ((nndiary-current-directory nil))
-                 (nnmail-expiry-target-group nnmail-expiry-target group))))
+                 (nnmail-expiry-target-group nnmail-expiry-target group)))
+             (nndiary-possibly-change-directory group server))
            (nnheader-message 5 "Deleting article %s in %s" number group)
            (condition-case ()
                (funcall nnmail-delete-file-function article)
@@ -745,31 +755,34 @@ all.  This may very well take some time.")
 (deffoo nndiary-request-accept-article (group &optional server last)
   (nndiary-possibly-change-directory group server)
   (nnmail-check-syntax)
-  (let (result)
-    (when nnmail-cache-accepted-message-ids
-      (nnmail-cache-insert (nnmail-fetch-field "message-id")))
-    (if (stringp group)
+  (run-hooks 'nndiary-request-accept-article-hooks)
+  (when (nndiary-schedule)
+    (let (result)
+      (when nnmail-cache-accepted-message-ids
+       (nnmail-cache-insert (nnmail-fetch-field "message-id") group))
+      (if (stringp group)
+         (and
+          (nnmail-activate 'nndiary)
+          (setq result
+                (car (nndiary-save-mail
+                      (list (cons group (nndiary-active-number group))))))
+          (progn
+            (nnmail-save-active nndiary-group-alist nndiary-active-file)
+            (and last (nndiary-save-nov))))
        (and
         (nnmail-activate 'nndiary)
-        (setq result
-              (car (nndiary-save-mail
-                    (list (cons group (nndiary-active-number group))))))
-        (progn
+        (if (and (not (setq result
+                            (nnmail-article-group 'nndiary-active-number)))
+                 (yes-or-no-p "Moved to `junk' group; delete article? "))
+            (setq result 'junk)
+          (setq result (car (nndiary-save-mail result))))
+        (when last
           (nnmail-save-active nndiary-group-alist nndiary-active-file)
-          (and last (nndiary-save-nov))))
-      (and
-       (nnmail-activate 'nndiary)
-       (if (and (not (setq result
-                          (nnmail-article-group 'nndiary-active-number)))
-               (yes-or-no-p "Moved to `junk' group; delete article? "))
-          (setq result 'junk)
-        (setq result (car (nndiary-save-mail result))))
-       (when last
-        (nnmail-save-active nndiary-group-alist nndiary-active-file)
-        (when nnmail-cache-accepted-message-ids
-          (nnmail-cache-close))
-        (nndiary-save-nov))))
-    result))
+          (when nnmail-cache-accepted-message-ids
+            (nnmail-cache-close))
+          (nndiary-save-nov))))
+      result))
+  )
 
 (deffoo nndiary-request-post (&optional server)
   (nnmail-do-request-post 'nndiary-request-accept-article server))
@@ -1110,13 +1123,7 @@ all.  This may very well take some time.")
        (narrow-to-region
         (goto-char (point-min))
         (if (search-forward "\n\n" nil t) (1- (point)) (point-max))))
-      ;; Fold continuation lines.
-      (goto-char (point-min))
-      (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
-       (replace-match " " t t))
-      ;; Remove any tabs; they are too confusing.
-      (subst-char-in-region (point-min) (point-max) ?\t ? )
-      (let ((headers (nnheader-parse-head t)))
+      (let ((headers (nnheader-parse-naked-head)))
        (mail-header-set-chars headers chars)
        (mail-header-set-number headers number)
        headers))))
@@ -1285,7 +1292,7 @@ all.  This may very well take some time.")
       val)))
 
 (defun nndiary-parse-schedule-value (str min-or-values max)
-  ;; Parse the schedule string STR.
+  ;; Parse the schedule string STR, or signal an error.
   ;; Signals are caught by `nndary-schedule'.
   (if (string-match "[ \t]*\\*[ \t]*" str)
       ;; unspecifyed
@@ -1338,17 +1345,6 @@ all.  This may very well take some time.")
       (nndiary-parse-schedule-value (match-string 1) min-or-values max))
     ))
 
-(defsubst nndiary-schedule ()
-  (mapcar
-   (lambda (elt)
-     (condition-case arg
-        (nndiary-parse-schedule (nth 0 elt) (nth 1 elt) (nth 2 elt))
-       (t
-       (nnheader-report 'nndiary "X-Diary-%s header parse error: %s."
-                        (car elt) (cdr arg))
-       nil)))
-   nndiary-headers))
-
 (defun nndiary-max (spec)
   ;; Returns the max of specification SPEC, or nil for permanent schedules.
   (unless (null spec)
@@ -1414,7 +1410,7 @@ all.  This may very well take some time.")
                      (nth 6 date-elts))))
         reminder res)
     ;; remove the DOW and DST entries
-    (setf (nthcdr 6 date-elts) (nthcdr 8 date-elts))
+    (setcdr (nthcdr 5 date-elts) (nthcdr 8 date-elts))
     (while (setq reminder (pop reminders))
       (push
        (cond ((eq (cdr reminder) 'minute)