-;;; 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-2011 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,
: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 ======================================================
(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)
(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)
(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
+ ;; Returns an aproximative 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))
;; #### 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))))
(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))))
(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
'nndiary)]
"Sort by number")))
+
+
;; Group parameters autosetting =============================================
(defun gnus-diary-update-group-parameters (group)
;; - 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.
+;; because of mail splitting are *not* created with the back end function.
;; Thus, `nndiary-request-create-group-hooks' is inoperative.
(defun gnus-diary-maybe-update-group-parameters (group)
(when (eq (car (gnus-find-method-for-group group)) 'nndiary)
(add-hook 'gnus-subscribe-newsgroup-hooks
'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-hooks
+ (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))