Remove nnml-retrieve-groups that is unnecessary and somewhat problematic
[gnus] / lisp / gnus-diary.el
index c329d0d..0f5c613 100644 (file)
@@ -1,30 +1,26 @@
-;;; gnus-diary.el --- Wrapper around the NNDiary Gnus backend
+;;; gnus-diary.el --- Wrapper around the NNDiary Gnus back end
 
-;; Copyright (C) 1999 Didier Verna.
-
-;; PRCS: $Id: gnus-diary.el 1.8 Tue, 04 Sep 2001 11:32:13 +0200 didier $
+;; Copyright (C) 1999-2015 Free Software Foundation, Inc.
 
 ;; Author:        Didier Verna <didier@xemacs.org>
 ;; Maintainer:    Didier Verna <didier@xemacs.org>
-;; Created:       Tue Jul 20 10:42:55 1999 under XEmacs 21.2 (beta 18)
-;; Last Revision: Wed Aug  8 14:38:14 2001
+;; Created:       Tue Jul 20 10:42:55 1999
 ;; 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
+;; the Free Software Foundation, either version 3 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.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with this program; if not, write to the Free Software
-;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 
 ;;; Commentary:
 ;; Description:
 ;; ===========
 
-;; Gnus-Diary is a wrapper around the NNDiary Gnus backend.  It is here to
-;; make your nndiary-user life easier in differnet ways.  So, you don't have
-;; to use it if you don't want to.  But, really, you should.
-
-;; Gnus-Diary offers the following improvements on top of the NNDiary backend:
-
-;; - A nice summary line format:
-;;   Displaying diary messages in standard summary line format (usually
-;;   something like "<From Joe>: <Subject>") is pretty useless.  Most of the
-;;   time, you're the one who wrote the message, and you mostly want to see
-;;   the event's date.  Gnus-Diary offers you a nice summary line format which
-;;   will do this.  By default, a summary line will appear like this:
-;;
-;;     <Event Date>: <Subject> <Remaining time>
-;;
-;;   for example, here's how Joe's birthday is displayed in my
-;;   "nndiary:birhdays" summary buffer (the message is expirable, but will
-;;   never be deleted, as it specifies a regular event):
-;;
-;;   E  Sat, Sep 22 01, 12:00: Joe's birthday (in 6 months, 1 week)
-
-;;  - More article sorting functions:
-;;    Gnus-Diary adds a new sorting function called
-;;    `gnus-summary-sort-by-schedule'.  This function lets you organize your
-;;    diary summary buffers from the closest event to the farthest one.
-
-;;  - Automatic generation of diary group parameters:
-;;    When you create a new diary group, or visit one, Gnus-Diary checks your
-;;    group parameters, and if needed, sets the summary line format to the
-;;    diary-specific value, adds the diary-specific sorting functions, and
-;;    also adds the different `X-Diary-*' headers to the group's
-;;    posting-style.  It is then easier to send a diary message, because if
-;;    you use `C-u a' or `C-u m' on a diary group to prepare a message, these
-;;    headers will be inserted automatically (but not filled with proper
-;;    values yet).
-
-
-;; Usage:
-;; =====
-
-;; 0/ Don't use any `gnus-user-format-function-[d|D]'.  Gnus-Diary provides
-;;    both of these (sorry if you used them before).
-;; 1/ Add '(require 'gnus-diary) to your gnusrc file.
-;; 2/ Customize your gnus-diary options to suit your needs.
-
+;; gnus-diary is a utility toolkit used on top of the nndiary back end. It is
+;; now fully documented in the Gnus manual.
 
 
 ;; Bugs / Todo:
 ;; ===========
 
-;; * Provide `gnus-group-diary-mail' and `gnus-group-post-diary-news' (or
-;;   something like that), that would do just like `gnus-group-mail' and
-;;   `gnus-group-post-news', but also prompt for diary header values with
-;;   completion etc.
-;; * Maybe not actually: we could just have a function that converts *any*
-;;   message to a diary one, by prompting the schedule. You could then forward
-;;   a message and make it a diary one etc.
 
 ;;; Code:
 
 (require 'nndiary)
+(require 'message)
+(require 'gnus-art)
 
 (defgroup gnus-diary nil
-  "Utilities on top of the nndiary backend for Gnus.")
+  "Utilities on top of the nndiary back end for Gnus."
+  :version "22.1"
+  :group 'gnus)
 
-(defcustom gnus-diary-summary-line-format "%U%R%z%I %uD: %(%s%) (%ud)\n"
+(defcustom gnus-diary-summary-line-format "%U%R%z %uD: %(%s%) (%ud)\n"
   "*Summary line format for nndiary groups."
   :type 'string
   :group 'gnus-diary
   :group 'gnus-summary-format)
 
 (defcustom gnus-diary-time-format "%a, %b %e %y, %H:%M"
-  "*Time format to display appointements in nndiary summary buffers.
+  "*Time format to display appointments in nndiary summary buffers.
 Please refer to `format-time-string' for information on possible values."
   :type 'string
   :group 'gnus-diary)
 
 (defcustom gnus-diary-delay-format-function 'gnus-diary-delay-format-english
   "*Function called to format a diary delay string.
-It is passed two arguments.  The first one is non nil if the delay is in
+It is passed two arguments.  The first one is non-nil if the delay is in
 the past.  The second one is of the form ((NUM . UNIT) ...) where NUM is
 an integer and UNIT is one of 'year 'month 'week 'day 'hour or 'minute.
 It should return strings like \"In 2 months, 3 weeks\", \"3 hours,
@@ -128,7 +78,18 @@ There are currently two built-in format functions:
   :group 'gnus-diary)
 
 (defconst gnus-diary-version nndiary-version
-  "Current Diary backend version.")
+  "Current Diary back end version.")
+
+
+;; Compatibility functions ==================================================
+
+(eval-and-compile
+  (if (fboundp 'kill-entire-line)
+      (defalias 'gnus-diary-kill-entire-line 'kill-entire-line)
+    (defun gnus-diary-kill-entire-line ()
+      (beginning-of-line)
+      (let ((kill-whole-line t))
+       (kill-line)))))
 
 
 ;; Summary line format ======================================================
@@ -137,7 +98,7 @@ There are currently two built-in format functions:
   (if (null delay)
       "maintenant!"
     ;; Keep only a precision of two degrees
-    (and (> (length delay) 1) (setf (nthcdr 2 delay) nil))
+    (and (> (length delay) 1) (setcdr (cdr delay) nil))
     (concat (if past "il y a " "dans ")
            (let ((str "")
                  del)
@@ -167,7 +128,7 @@ There are currently two built-in format functions:
   (if (null delay)
       "now!"
     ;; Keep only a precision of two degrees
-    (and (> (length delay) 1) (setf (nthcdr 2 delay) nil))
+    (and (> (length delay) 1) (setcdr (cdr delay) nil))
     (concat (unless past "in ")
            (let ((str "")
                  del)
@@ -188,14 +149,15 @@ There are currently two built-in format functions:
      (let ((head (cdr (assoc (intern (format "X-Diary-%s" (car elt)))
                             headers))))
        (when head
-        (nndiary-parse-schedule-value head (cadr elt) (caddr elt)))))
+        (nndiary-parse-schedule-value head (cadr elt) (car (cddr elt))))))
    nndiary-headers))
 
 ;; #### NOTE: Gnus sometimes gives me a HEADER not corresponding to any
 ;; message, with all fields set to nil here. I don't know what it is for, and
 ;; I just ignore it.
+;;;###autoload
 (defun gnus-user-format-function-d (header)
-  ;; Returns an aproximative delay string for the next occurence of this
+  ;; Return an approximate delay string for the next occurrence of this
   ;; message. The delay is given only in the first non zero unit.
   ;; Code partly stolen from article-make-date-line
   (let* ((extras (mail-header-extra header))
@@ -230,8 +192,9 @@ There are currently two built-in format functions:
 ;; #### NOTE: Gnus sometimes gives me a HEADER not corresponding to any
 ;; message, with all fields set to nil here. I don't know what it is for, and
 ;; I just ignore it.
+;;;###autoload
 (defun gnus-user-format-function-D (header)
-  ;; Returns a formatted time string for the next occurence of this message.
+  ;; Returns a formatted time string for the next occurrence of this message.
   (let* ((extras (mail-header-extra header))
         (sched (gnus-diary-header-schedule extras))
         (occur (nndiary-next-occurence sched (current-time))))
@@ -249,7 +212,7 @@ There are currently two built-in format functions:
         (o1 (nndiary-next-occurence s1 now))
         (o2 (nndiary-next-occurence s2 now)))
     (if (and (= (car o1) (car o2)) (= (cadr o1) (cadr o2)))
-        (< (mail-header-number h1) (mail-header-number h2))
+       (< (mail-header-number h1) (mail-header-number h2))
       (time-less-p o1 o2))))
 
 
@@ -258,11 +221,12 @@ There are currently two built-in format functions:
                                 (gnus-thread-header h2)))
 
 (defun gnus-summary-sort-by-schedule (&optional reverse)
-  "Sort nndiary summary buffers by schedule of appointements.
+  "Sort nndiary summary buffers by schedule of appointments.
 Optional prefix (or REVERSE argument) means sort in reverse order."
   (interactive "P")
   (gnus-summary-sort 'schedule reverse))
 
+(defvar gnus-summary-misc-menu) ;; Avoid byte compiler warning.
 (add-hook 'gnus-summary-menu-hook
          (lambda ()
            (easy-menu-add-item gnus-summary-misc-menu
@@ -274,6 +238,8 @@ Optional prefix (or REVERSE argument) means sort in reverse order."
                                     'nndiary)]
                                "Sort by number")))
 
+
+
 ;; Group parameters autosetting =============================================
 
 (defun gnus-diary-update-group-parameters (group)
@@ -282,51 +248,153 @@ Optional prefix (or REVERSE argument) means sort in reverse order."
   ;; - a nice summary line format
   ;; - NNDiary specific sorting by schedule functions
   ;; In general, try not to mess with what the user might have modified.
-  (let ((posting-style (gnus-group-get-parameter group 'posting-style t)))
-    ;; Posting style:
-    (mapcar (lambda (elt)
-             (let ((header (format "X-Diary-%s" (car elt))))
-               (unless (assoc header posting-style)
-                 (setq posting-style (append posting-style
-                                             `((,header "*")))))
-               ))
-           nndiary-headers)
-    (gnus-group-set-parameter group 'posting-style posting-style)
-    ;; Summary line format:
-    (unless (gnus-group-get-parameter group 'gnus-summary-line-format t)
-      (gnus-group-set-parameter group 'gnus-summary-line-format
-                               `(,gnus-diary-summary-line-format)))
-    ;; Sorting by schedule:
-    (unless (gnus-group-get-parameter group 'gnus-article-sort-functions)
-      (gnus-group-set-parameter group 'gnus-article-sort-functions
-                               '((append gnus-article-sort-functions
-                                         (list
-                                          'gnus-article-sort-by-schedule)))))
-    (unless (gnus-group-get-parameter group 'gnus-thread-sort-functions)
-      (gnus-group-set-parameter group 'gnus-thread-sort-functions
-                               '((append gnus-thread-sort-functions
-                                         (list
-                                          'gnus-thread-sort-by-schedule)))))
-    ))
+
+  ;; Posting style:
+  (let ((posting-style (gnus-group-get-parameter group 'posting-style t))
+       (headers nndiary-headers)
+       header)
+    (while headers
+      (setq header (format "X-Diary-%s" (caar headers))
+           headers (cdr headers))
+      (unless (assoc header posting-style)
+       (setq posting-style (append posting-style (list (list header "*"))))))
+    (gnus-group-set-parameter group 'posting-style posting-style))
+  ;; Summary line format:
+  (unless (gnus-group-get-parameter group 'gnus-summary-line-format t)
+    (gnus-group-set-parameter group 'gnus-summary-line-format
+                             `(,gnus-diary-summary-line-format)))
+  ;; Sorting by schedule:
+  (unless (gnus-group-get-parameter group 'gnus-article-sort-functions)
+    (gnus-group-set-parameter group 'gnus-article-sort-functions
+                             '((append gnus-article-sort-functions
+                                       (list
+                                        'gnus-article-sort-by-schedule)))))
+  (unless (gnus-group-get-parameter group 'gnus-thread-sort-functions)
+    (gnus-group-set-parameter group 'gnus-thread-sort-functions
+                             '((append gnus-thread-sort-functions
+                                       (list
+                                        'gnus-thread-sort-by-schedule))))))
 
 ;; Called when a group is subscribed. This is needed because groups created
-;; because of mail splitting are *not* created with the backend function.
-;; Thus, `nndiary-request-create-group-hooks' is inoperative.
+;; because of mail splitting are *not* created with the back end function.
+;; Thus, `nndiary-request-create-group-functions' is inoperative.
 (defun gnus-diary-maybe-update-group-parameters (group)
   (when (eq (car (gnus-find-method-for-group group)) 'nndiary)
     (gnus-diary-update-group-parameters group)))
 
-(add-hook 'nndiary-request-create-group-hooks
+(add-hook 'nndiary-request-create-group-functions
          'gnus-diary-update-group-parameters)
-;; Now that we have `gnus-subscribe-newsgroup-hooks', this is not needed
+;; Now that we have `gnus-subscribe-newsgroup-functions', this is not needed
 ;; anymore. Maybe I should remove this completely.
-(add-hook 'nndiary-request-update-info-hooks
+(add-hook 'nndiary-request-update-info-functions
          'gnus-diary-update-group-parameters)
-(add-hook 'gnus-subscribe-newsgroup-hooks
+(add-hook 'gnus-subscribe-newsgroup-functions
          'gnus-diary-maybe-update-group-parameters)
 
+
+;; Diary Message Checking ===================================================
+
+(defvar gnus-diary-header-value-history nil
+  ;; History variable for header value prompting
+  )
+
+(defun gnus-diary-narrow-to-headers ()
+  "Narrow the current buffer to the header part.
+Point is left at the beginning of the region.
+The buffer is assumed to contain a message, but the format is unknown."
+  (cond ((eq major-mode 'message-mode)
+        (message-narrow-to-headers))
+       (t
+        (goto-char (point-min))
+        (when (search-forward "\n\n" nil t)
+          (narrow-to-region (point-min) (- (point) 1))
+          (goto-char (point-min))))
+       ))
+
+(defun gnus-diary-add-header (str)
+  "Add a header to the current buffer.
+The buffer is assumed to contain a message, but the format is unknown."
+  (cond ((eq major-mode 'message-mode)
+        (message-add-header str))
+       (t
+        (save-restriction
+          (gnus-diary-narrow-to-headers)
+          (goto-char (point-max))
+          (if (string-match "\n$" str)
+              (insert str)
+            (insert str ?\n))))
+       ))
+
+(defun gnus-diary-check-message (arg)
+  "Ensure that the current message is a valid for NNDiary.
+This function checks that all NNDiary required headers are present and
+valid, and prompts for values / correction otherwise.
+
+If ARG (or prefix) is non-nil, force prompting for all fields."
+  (interactive "P")
+  (save-excursion
+    (mapcar
+     (lambda (head)
+       (let ((header (concat "X-Diary-" (car head)))
+            (ask arg)
+            value invalid)
+        ;; First, try to find the header, and checks for validity:
+        (save-restriction
+          (gnus-diary-narrow-to-headers)
+          (when (re-search-forward (concat "^" header ":") nil t)
+            (unless (eq (char-after) ? )
+              (insert " "))
+            (setq value (buffer-substring (point) (point-at-eol)))
+            (and (string-match "[ \t]*\\([^ \t]+\\)[ \t]*" value)
+                 (setq value (match-string 1 value)))
+            (condition-case ()
+                (nndiary-parse-schedule-value value
+                                              (nth 1 head) (nth 2 head))
+              (error
+               (setq invalid t)))
+            ;; #### NOTE: this (along with the `gnus-diary-add-header'
+            ;; function) could be rewritten in a better way, in particular
+            ;; not to blindly remove an already present header and reinsert
+            ;; it somewhere else afterwards.
+            (when (or ask invalid)
+              (gnus-diary-kill-entire-line))
+            ))
+        ;; Now, loop until a valid value is provided:
+        (while (or ask (not value) invalid)
+          (let ((prompt (concat (and invalid
+                                     (prog1 "(current value invalid) "
+                                       (beep)))
+                                header ": ")))
+            (setq value
+                  (if (listp (nth 1 head))
+                      (gnus-completing-read prompt (cons "*" (mapcar 'car (nth 1 head)))
+                                             t value
+                                             'gnus-diary-header-value-history)
+                    (read-string prompt value
+                                 'gnus-diary-header-value-history))))
+          (setq ask nil)
+          (setq invalid nil)
+          (condition-case ()
+              (nndiary-parse-schedule-value value
+                                            (nth 1 head) (nth 2 head))
+            (error
+             (setq invalid t))))
+        (gnus-diary-add-header (concat header ": " value))
+        ))
+     nndiary-headers)
+    ))
+
+(add-hook 'nndiary-request-accept-article-functions
+         (lambda () (gnus-diary-check-message nil)))
+
+(define-key message-mode-map "\C-c\C-fd" 'gnus-diary-check-message)
+(define-key gnus-article-edit-mode-map "\C-c\C-fd" 'gnus-diary-check-message)
+
+
+;; The end ==================================================================
+
 (defun gnus-diary-version ()
-  "Current Diary backend version."
+  "Current Diary back end version."
   (interactive)
   (message "NNDiary version %s" nndiary-version))