1 ;;; gnus-diary.el --- Wrapper around the NNDiary Gnus backend
3 ;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005,
4 ;; 2006, 2007 Free Software Foundation, Inc.
6 ;; Author: Didier Verna <didier@xemacs.org>
7 ;; Maintainer: Didier Verna <didier@xemacs.org>
8 ;; Created: Tue Jul 20 10:42:55 1999
9 ;; Keywords: calendar mail news
11 ;; This file is part of GNU Emacs.
13 ;; GNU Emacs is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published
15 ;; by the Free Software Foundation; either version 2 of the License,
16 ;; or (at your option) any later version.
18 ;; GNU Emacs is distributed in the hope that it will be useful, but
19 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 ;; General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with this program; if not, write to the Free Software
25 ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 ;; MA 02110-1301, USA.
31 ;; Contents management by FCM version 0.1.
36 ;; Gnus-Diary is a wrapper around the NNDiary Gnus backend. It is here to
37 ;; make your nndiary-user life easier in different ways. So, you don't have
38 ;; to use it if you don't want to. But, really, you should.
40 ;; Gnus-Diary offers the following features on top of the NNDiary backend:
42 ;; - A nice summary line format:
43 ;; Displaying diary messages in standard summary line format (usually
44 ;; something like "<From Joe>: <Subject>") is pretty useless. Most of the
45 ;; time, you're the one who wrote the message, and you mostly want to see
46 ;; the event's date. Gnus-Diary offers you a nice summary line format
47 ;; which will do this. By default, a summary line will appear like this:
49 ;; <Event Date>: <Subject> <Remaining time>
51 ;; for example, here's how Joe's birthday is displayed in my
52 ;; "nndiary:birhdays" summary buffer (the message is expirable, but will
53 ;; never be deleted, as it specifies a regular event):
55 ;; E Sat, Sep 22 01, 12:00: Joe's birthday (in 6 months, 1 week)
57 ;; - More article sorting functions:
58 ;; Gnus-Diary adds a new sorting function called
59 ;; `gnus-summary-sort-by-schedule'. This function lets you organize your
60 ;; diary summary buffers from the closest event to the farthest one.
62 ;; - Automatic generation of diary group parameters:
63 ;; When you create a new diary group, or visit one, Gnus-Diary checks your
64 ;; group parameters, and if needed, sets the summary line format to the
65 ;; diary-specific value, adds the diary-specific sorting functions, and
66 ;; also adds the different `X-Diary-*' headers to the group's
67 ;; posting-style. It is then easier to send a diary message, because if
68 ;; you use `C-u a' or `C-u m' on a diary group to prepare a message, these
69 ;; headers will be inserted automatically (but not filled with proper
72 ;; - An interactive mail-to-diary convertion function:
73 ;; The function `gnus-diary-check-message' ensures that the current message
74 ;; contains all the required diary headers, and prompts you for values /
75 ;; correction if needed. This function is hooked in the nndiary backend so
76 ;; that moving an article to an nndiary group will trigger it
77 ;; automatically. It is also bound to `C-c D c' in message-mode and
78 ;; article-edit-mode in order to ease the process of converting a usual
79 ;; mail to a diary one. This function takes a prefix argument which will
80 ;; force prompting of all diary headers, regardless of their
81 ;; presence/validity. That way, you can very easily reschedule a diary
82 ;; message for instance.
88 ;; 0/ Don't use any `gnus-user-format-function-[d|D]'. Gnus-Diary provides
89 ;; both of these (sorry if you used them before).
90 ;; 1/ Add '(require 'gnus-diary) to your gnusrc file.
91 ;; 2/ Customize your gnus-diary options to suit your needs.
105 (defgroup gnus-diary nil
106 "Utilities on top of the nndiary backend for Gnus."
110 (defcustom gnus-diary-summary-line-format
"%U%R%z %uD: %(%s%) (%ud)\n"
111 "*Summary line format for nndiary groups."
114 :group
'gnus-summary-format
)
116 (defcustom gnus-diary-time-format
"%a, %b %e %y, %H:%M"
117 "*Time format to display appointements in nndiary summary buffers.
118 Please refer to `format-time-string' for information on possible values."
122 (defcustom gnus-diary-delay-format-function
'gnus-diary-delay-format-english
123 "*Function called to format a diary delay string.
124 It is passed two arguments. The first one is non-nil if the delay is in
125 the past. The second one is of the form ((NUM . UNIT) ...) where NUM is
126 an integer and UNIT is one of 'year 'month 'week 'day 'hour or 'minute.
127 It should return strings like \"In 2 months, 3 weeks\", \"3 hours,
128 1 minute ago\" and so on.
130 There are currently two built-in format functions:
131 `gnus-diary-delay-format-english' (the default)
132 `gnus-diary-delay-format-french'"
133 :type
'(choice (const :tag
"english" gnus-diary-delay-format-english
)
134 (const :tag
"french" gnus-diary-delay-format-french
)
135 (symbol :tag
"other"))
138 (defconst gnus-diary-version nndiary-version
139 "Current Diary backend version.")
142 ;; Compatibility functions ==================================================
145 (if (fboundp 'kill-entire-line
)
146 (defalias 'gnus-diary-kill-entire-line
'kill-entire-line
)
147 (defun gnus-diary-kill-entire-line ()
149 (let ((kill-whole-line t
))
153 ;; Summary line format ======================================================
155 (defun gnus-diary-delay-format-french (past delay
)
158 ;; Keep only a precision of two degrees
159 (and (> (length delay
) 1) (setcdr (cdr delay
) nil
))
160 (concat (if past
"il y a " "dans ")
163 (while (setq del
(pop delay
))
164 (setq str
(concat str
165 (int-to-string (car del
)) " "
166 (cond ((eq (cdr del
) 'year
)
168 ((eq (cdr del
) 'month
)
170 ((eq (cdr del
) 'week
)
174 ((eq (cdr del
) 'hour
)
176 ((eq (cdr del
) 'minute
)
178 (unless (or (eq (cdr del
) 'month
)
185 (defun gnus-diary-delay-format-english (past delay
)
188 ;; Keep only a precision of two degrees
189 (and (> (length delay
) 1) (setcdr (cdr delay
) nil
))
190 (concat (unless past
"in ")
193 (while (setq del
(pop delay
))
194 (setq str
(concat str
195 (int-to-string (car del
)) " "
196 (symbol-name (cdr del
))
197 (and (> (car del
) 1) "s")
203 (defun gnus-diary-header-schedule (headers)
204 ;; Same as `nndiary-schedule', but given a set of headers HEADERS
207 (let ((head (cdr (assoc (intern (format "X-Diary-%s" (car elt
)))
210 (nndiary-parse-schedule-value head
(cadr elt
) (car (cddr elt
))))))
213 ;; #### NOTE: Gnus sometimes gives me a HEADER not corresponding to any
214 ;; message, with all fields set to nil here. I don't know what it is for, and
217 (defun gnus-user-format-function-d (header)
218 ;; Returns an aproximative delay string for the next occurence of this
219 ;; message. The delay is given only in the first non zero unit.
220 ;; Code partly stolen from article-make-date-line
221 (let* ((extras (mail-header-extra header
))
222 (sched (gnus-diary-header-schedule extras
))
223 (occur (nndiary-next-occurence sched
(current-time)))
225 (real-time (subtract-time occur now
)))
228 (let* ((sec (+ (* (float (car real-time
)) 65536) (cadr real-time
)))
231 (and past
(setq sec
(- sec
)))
233 ;; This is a bit convoluted, but basically we go through the time
234 ;; units for years, weeks, etc, and divide things to see whether
235 ;; that results in positive answers.
236 (let ((units `((year .
,(* 365.25 24 3600))
237 (month .
,(* 31 24 3600))
238 (week .
,(* 7 24 3600))
243 (while (setq unit
(pop units
))
244 (unless (zerop (setq num
(ffloor (/ sec
(cdr unit
)))))
245 (setq delay
(append delay
`((,(floor num
) .
,(car unit
))))))
246 (setq sec
(- sec
(* num
(cdr unit
)))))))
247 (funcall gnus-diary-delay-format-function past delay
)))
250 ;; #### NOTE: Gnus sometimes gives me a HEADER not corresponding to any
251 ;; message, with all fields set to nil here. I don't know what it is for, and
254 (defun gnus-user-format-function-D (header)
255 ;; Returns a formatted time string for the next occurence of this message.
256 (let* ((extras (mail-header-extra header
))
257 (sched (gnus-diary-header-schedule extras
))
258 (occur (nndiary-next-occurence sched
(current-time))))
259 (format-time-string gnus-diary-time-format occur
)))
262 ;; Article sorting functions ================================================
264 (defun gnus-article-sort-by-schedule (h1 h2
)
265 (let* ((now (current-time))
266 (e1 (mail-header-extra h1
))
267 (e2 (mail-header-extra h2
))
268 (s1 (gnus-diary-header-schedule e1
))
269 (s2 (gnus-diary-header-schedule e2
))
270 (o1 (nndiary-next-occurence s1 now
))
271 (o2 (nndiary-next-occurence s2 now
)))
272 (if (and (= (car o1
) (car o2
)) (= (cadr o1
) (cadr o2
)))
273 (< (mail-header-number h1
) (mail-header-number h2
))
274 (time-less-p o1 o2
))))
277 (defun gnus-thread-sort-by-schedule (h1 h2
)
278 (gnus-article-sort-by-schedule (gnus-thread-header h1
)
279 (gnus-thread-header h2
)))
281 (defun gnus-summary-sort-by-schedule (&optional reverse
)
282 "Sort nndiary summary buffers by schedule of appointements.
283 Optional prefix (or REVERSE argument) means sort in reverse order."
285 (gnus-summary-sort 'schedule reverse
))
287 (defvar gnus-summary-misc-menu
) ;; Avoid byte compiler warning.
288 (add-hook 'gnus-summary-menu-hook
290 (easy-menu-add-item gnus-summary-misc-menu
293 gnus-summary-sort-by-schedule
294 (eq (car (gnus-find-method-for-group
295 gnus-newsgroup-name
))
301 ;; Group parameters autosetting =============================================
303 (defun gnus-diary-update-group-parameters (group)
304 ;; Ensure that nndiary groups have convenient group parameters:
305 ;; - a posting style containing X-Diary headers
306 ;; - a nice summary line format
307 ;; - NNDiary specific sorting by schedule functions
308 ;; In general, try not to mess with what the user might have modified.
309 (let ((posting-style (gnus-group-get-parameter group
'posting-style t
)))
311 (mapcar (lambda (elt)
312 (let ((header (format "X-Diary-%s" (car elt
))))
313 (unless (assoc header posting-style
)
314 (setq posting-style
(append posting-style
318 (gnus-group-set-parameter group
'posting-style posting-style
)
319 ;; Summary line format:
320 (unless (gnus-group-get-parameter group
'gnus-summary-line-format t
)
321 (gnus-group-set-parameter group
'gnus-summary-line-format
322 `(,gnus-diary-summary-line-format
)))
323 ;; Sorting by schedule:
324 (unless (gnus-group-get-parameter group
'gnus-article-sort-functions
)
325 (gnus-group-set-parameter group
'gnus-article-sort-functions
326 '((append gnus-article-sort-functions
328 'gnus-article-sort-by-schedule
)))))
329 (unless (gnus-group-get-parameter group
'gnus-thread-sort-functions
)
330 (gnus-group-set-parameter group
'gnus-thread-sort-functions
331 '((append gnus-thread-sort-functions
333 'gnus-thread-sort-by-schedule
)))))
336 ;; Called when a group is subscribed. This is needed because groups created
337 ;; because of mail splitting are *not* created with the backend function.
338 ;; Thus, `nndiary-request-create-group-hooks' is inoperative.
339 (defun gnus-diary-maybe-update-group-parameters (group)
340 (when (eq (car (gnus-find-method-for-group group
)) 'nndiary
)
341 (gnus-diary-update-group-parameters group
)))
343 (add-hook 'nndiary-request-create-group-hooks
344 'gnus-diary-update-group-parameters
)
345 ;; Now that we have `gnus-subscribe-newsgroup-hooks', this is not needed
346 ;; anymore. Maybe I should remove this completely.
347 (add-hook 'nndiary-request-update-info-hooks
348 'gnus-diary-update-group-parameters
)
349 (add-hook 'gnus-subscribe-newsgroup-hooks
350 'gnus-diary-maybe-update-group-parameters
)
353 ;; Diary Message Checking ===================================================
355 (defvar gnus-diary-header-value-history nil
356 ;; History variable for header value prompting
359 (defun gnus-diary-narrow-to-headers ()
360 "Narrow the current buffer to the header part.
361 Point is left at the beginning of the region.
362 The buffer is assumed to contain a message, but the format is unknown."
363 (cond ((eq major-mode
'message-mode
)
364 (message-narrow-to-headers))
366 (goto-char (point-min))
367 (when (search-forward "\n\n" nil t
)
368 (narrow-to-region (point-min) (- (point) 1))
369 (goto-char (point-min))))
372 (defun gnus-diary-add-header (str)
373 "Add a header to the current buffer.
374 The buffer is assumed to contain a message, but the format is unknown."
375 (cond ((eq major-mode
'message-mode
)
376 (message-add-header str
))
379 (gnus-diary-narrow-to-headers)
380 (goto-char (point-max))
381 (if (string-match "\n$" str
)
386 (defun gnus-diary-check-message (arg)
387 "Ensure that the current message is a valid for NNDiary.
388 This function checks that all NNDiary required headers are present and
389 valid, and prompts for values / correction otherwise.
391 If ARG (or prefix) is non-nil, force prompting for all fields."
396 (let ((header (concat "X-Diary-" (car head
)))
399 ;; First, try to find the header, and checks for validity:
401 (gnus-diary-narrow-to-headers)
402 (when (re-search-forward (concat "^" header
":") nil t
)
403 (unless (eq (char-after) ?
)
405 (setq value
(buffer-substring (point) (gnus-point-at-eol)))
406 (and (string-match "[ \t]*\\([^ \t]+\\)[ \t]*" value
)
407 (setq value
(match-string 1 value
)))
409 (nndiary-parse-schedule-value value
410 (nth 1 head
) (nth 2 head
))
413 ;; #### NOTE: this (along with the `gnus-diary-add-header'
414 ;; function) could be rewritten in a better way, in particular
415 ;; not to blindly remove an already present header and reinsert
416 ;; it somewhere else afterwards.
417 (when (or ask invalid
)
418 (gnus-diary-kill-entire-line))
420 ;; Now, loop until a valid value is provided:
421 (while (or ask
(not value
) invalid
)
422 (let ((prompt (concat (and invalid
423 (prog1 "(current value invalid) "
427 (if (listp (nth 1 head
))
428 (completing-read prompt
(cons '("*" nil
) (nth 1 head
))
430 gnus-diary-header-value-history
)
431 (read-string prompt value
432 gnus-diary-header-value-history
))))
436 (nndiary-parse-schedule-value value
437 (nth 1 head
) (nth 2 head
))
440 (gnus-diary-add-header (concat header
": " value
))
445 (add-hook 'nndiary-request-accept-article-hooks
446 (lambda () (gnus-diary-check-message nil
)))
448 (define-key message-mode-map
"\C-cDc" 'gnus-diary-check-message
)
449 (define-key gnus-article-edit-mode-map
"\C-cDc" 'gnus-diary-check-message
)
452 ;; The end ==================================================================
454 (defun gnus-diary-version ()
455 "Current Diary backend version."
457 (message "NNDiary version %s" nndiary-version
))
459 (define-key message-mode-map
"\C-cDv" 'gnus-diary-version
)
460 (define-key gnus-article-edit-mode-map
"\C-cDv" 'gnus-diary-version
)
463 (provide 'gnus-diary
)
465 ;;; arch-tag: 98467e70-337e-4ddc-b92d-45d403ff1b4b
466 ;;; gnus-diary.el ends here