* gnus-art.el (gnus-button-alist): Fixed regexp for manual links.
[gnus] / lisp / nndiary.el
1 ;;; nndiary.el --- A diary backend for Gnus
2
3 ;; Copyright (C) 1999, 2000, 2001, 2003
4 ;;        Free Software Foundation, Inc.
5
6 ;; Author:        Didier Verna <didier@xemacs.org>
7 ;; Maintainer:    Didier Verna <didier@xemacs.org>
8 ;; Created:       Fri Jul 16 18:55:42 1999
9 ;; Keywords:      calendar mail news
10
11 ;; This file is part of GNU Emacs.
12
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 by
15 ;; the Free Software Foundation; either version 2 of the License, or
16 ;; (at your option) any later version.
17
18 ;; GNU Emacs is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 ;; GNU General Public License for more details.
22
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., 675 Mass Ave, Cambridge, MA 02139, USA.
26
27
28 ;;; Commentary:
29
30 ;; Contents management by FCM version 0.1.
31
32 ;; Description:
33 ;; ===========
34
35 ;; This package implements NNDiary, a diary backend for Gnus.  NNDiary is a
36 ;; mail backend, pretty similar to nnml in its functionnning (it has all the
37 ;; features of nnml, actually), but in which messages are treated as event
38 ;; reminders.
39
40 ;; Here is a typical scenario:
41 ;; - You've got a date with Andy Mc Dowell or Bruce Willis (select according
42 ;;   to your sexual preference) in one month.  You don't want to forget it.
43 ;; - Send a (special) diary message to yourself (see below).
44 ;; - Forget all about it and keep on getting and reading new mail, as usual.
45 ;; - From time to time, as you type `g' in the group buffer and as the date
46 ;;   is getting closer, the message will pop up again, just like if it were
47 ;;   new and unread.
48 ;; - Read your "new" messages, this one included, and start dreaming of the
49 ;;   night you're gonna have.
50 ;; - Once the date is over (you actually fell asleep just after dinner), the
51 ;;   message will be automatically deleted if it is marked as expirable.
52
53 ;; Some more notes on the diary backend:
54 ;; - NNDiary is a *real* mail backend.  You *really* send real diary
55 ;;   messsages.  This means for instance that you can give appointements to
56 ;;   anybody (provided they use Gnus and NNDiary) by sending the diary message
57 ;;   to them as well.
58 ;; - However, since NNDiary also has a 'request-post method, you can also
59 ;;  `C-u a' instead of `C-u m' on a diary group and the message won't actually
60 ;;   be sent; just stored in the group.
61 ;; - The events you want to remember need not be punctual.  You can set up
62 ;;   reminders for regular dates (like once each week, each monday at 13:30
63 ;;   and so on).  Diary messages of this kind will never be deleted (unless
64 ;;   you do it explicitely).  But that, you guessed.
65
66
67 ;; Usage:
68 ;; =====
69
70 ;;  1/ NNDiary has two modes of operation: traditional (the default) and
71 ;;     autonomous.
72 ;;     a/ In traditional mode, NNDiary does not get new mail by itself.  You
73 ;;        have to move mails from your primary mail backend to nndiary
74 ;;        groups.
75 ;;     b/ In autonomous mode, NNDiary retrieves its own mail and handles it
76 ;;        independantly of your primary mail backend.  To use NNDiary in
77 ;;        autonomous mode, you have several things to do:
78 ;;           i/ Put (setq nndiary-get-new-mail t) in your gnusrc file.
79 ;;          ii/ Diary messages contain several `X-Diary-*' special headers.
80 ;;              You *must* arrange that these messages be split in a private
81 ;;              folder *before* Gnus treat them.  You need this because Gnus
82 ;;              is not able yet to manage multiple backends for mail
83 ;;              retrieval.  Getting them from a separate source will
84 ;;              compensate this misfeature to some extent, as we will see.
85 ;;              As an example, here's my procmailrc entry to store diary files
86 ;;              in ~/.nndiary (the default nndiary mail source file):
87 ;;
88 ;;              :0 HD :
89 ;;              * ^X-Diary
90 ;;              .nndiary
91 ;;         iii/ Customize the variables `nndiary-mail-sources' and
92 ;;              `nndiary-split-methods'.  These are replacements for the usual
93 ;;              mail sources and split methods which, and will be used in
94 ;;              autonomous mode.  `nndiary-mail-sources' defaults to
95 ;;              '(file :path "~/.nndiary").
96 ;;  2/ Install nndiary somewhere Emacs / Gnus can find it.  Normally, you
97 ;;     *don't* have to '(require 'nndiary) anywhere.  Gnus will do so when
98 ;;     appropriate as long as nndiary is somewhere in the load path.
99 ;;  3/ Now, customize the rest of nndiary.  In particular, you should
100 ;;     customize `nndiary-reminders', the list of times when you want to be
101 ;;     reminded of your appointements (e.g. 3 weeks before, then 2 days
102 ;;     before, then 1 hour before and that's it).
103 ;;  4/ You *must* use the group timestamp feature of Gnus.  This adds a
104 ;;     timestamp to each groups' parameters (please refer to the Gnus
105 ;;     documentation ("Group Timestamp" info node) to see how it's done.
106 ;;  5/ Once you have done this, you may add a permanent nndiary virtual server
107 ;;     (something like '(nndiary "")) to your `gnus-secondary-select-methods'.
108 ;;     Yes, this server will be able to retrieve mails and split them when you
109 ;;     type `g' in the group buffer, just as if it were your only mail backend.
110 ;;     This is the benefit of using a private folder.
111 ;;  6/ Hopefully, almost everything (see the TODO section below) will work as
112 ;;     expected when you restart Gnus: in the group buffer, `g' and `M-g' will
113 ;;     also get your new diary mails, `F' will find your new diary groups etc.
114
115
116 ;; How to send diary messages:
117 ;; ==========================
118
119 ;; There are 7 special headers in diary messages. These headers are of the
120 ;; form `X-Diary-<something>', the <something> being one of `Minute', `Hour',
121 ;; `Dom', `Month', `Year', `Time-Zone' and `Dow'. `Dom' means "Day of Month",
122 ;; and `dow' means "Day of Week".  These headers actually behave like crontab
123 ;; specifications and define the event date(s).
124
125 ;; For all headers but the `Time-Zone' one, a header value is either a
126 ;; star (meaning all possible values), or a list of fields (separated by a
127 ;; comma).  A field is either an integer, or a range.  A range is two integers
128 ;; separated by a dash.  Possible integer values are 0-59 for `Minute', 0-23
129 ;; for `Hour', 1-31 for `Dom', `1-12' for Month, above 1971 for `Year' and 0-6
130 ;; for `Dow' (0 = sunday).  As a special case, a star in either `Dom' or `Dow'
131 ;; doesn't mean "all possible values", but "use only the other field".  Note
132 ;; that if both are star'ed, the use of either one gives the same result :-),
133
134 ;; The `Time-Zone' header is special in that it can have only one value (you
135 ;; bet ;-).
136 ;; A star doesn't mean "all possible values" (because it has no sense), but
137 ;; "the current local time zone".
138
139 ;; As an example, here's how you would say "Each Monday and each 1st of month,
140 ;; at 12:00, 20:00, 21:00, 22:00, 23:00 and 24:00, from 1999 to 2010" (I let
141 ;; you find what to do then):
142 ;;
143 ;;   X-Diary-Minute: 0
144 ;;   X-Diary-Hour: 12, 20-24
145 ;;   X-Diary-Dom: 1
146 ;;   X-Diary-Month: *
147 ;;   X-Diary-Year: 1999-2010
148 ;;   X-Diary-Dow: 1
149 ;;   X-Diary-Time-Zone: *
150 ;;
151 ;;
152 ;; Sending a diary message is not different from sending any other kind of
153 ;; mail, except that such messages are identified by the presence of these
154 ;; special headers.
155
156
157
158 ;; Bugs / Todo:
159 ;; ===========
160
161 ;; * Respooling doesn't work because contrary to the request-scan function,
162 ;;   Gnus won't allow me to override the split methods when calling the
163 ;;   respooling backend functions.
164 ;; * There's a bug in the time zone mechanism with variable TZ locations.
165 ;; * We could allow a keyword like `ask' in X-Diary-* headers, that would mean
166 ;;   "ask for value upon reception of the message".
167 ;; * We could add an optional header X-Diary-Reminders to specify a special
168 ;;   reminders value for this message. Suggested by Jody Klymak.
169 ;; * We should check messages validity in other circumstances than just
170 ;;   moving an article from sonwhere else (request-accept). For instance, when
171 ;;   editing / saving and so on.
172
173
174 ;; Remarks:
175 ;; =======
176
177 ;; * nnoo.
178 ;;   NNDiary is very similar to nnml.  This makes the idea of using nnoo (to
179 ;;   derive nndiary from nnml) natural.  However, my experience with nnoo is
180 ;;   that for reasonably complex backends like this one, noo is a burden
181 ;;   rather than an help.  It's tricky to use, not everything can be
182 ;;   inherited, what can be inherited and when is not very clear, and you've
183 ;;   got to be very careful because a little mistake can fuck up your your
184 ;;   other backends, especially because their variables will be use instead of
185 ;;   your real ones.  Finally, I found it easier to just clone the needed
186 ;;   parts of nnml, and tracking nnml updates is not a big deal.
187
188 ;;   IMHO, nnoo is actually badly designed.  A much simpler, and yet more
189 ;;   powerful one would be to make *real* functions and variables for a new
190 ;;   backend based on another. Lisp is a reflexive language so that's a very
191 ;;   easy thing to do: inspect the function's form, replace occurences of
192 ;;   <nnfrom> (even in strings) with <nnto>, and you're done.
193
194 ;; * nndiary-get-new-mail, nndiary-mail-source and nndiary-split-methods:
195 ;;   NNDiary has some experimental parts, in the sense Gnus normally uses only
196 ;;   one mail backends for mail retreival and splitting.  This backend is also
197 ;;   an attempt to make it behave differently.  For Gnus developpers: as you
198 ;;   can see if you snarf into the code, that was not a very difficult thing
199 ;;   to do.  Something should be done about the respooling breakage though.
200
201
202 ;;; Code:
203
204 (require 'nnoo)
205 (require 'nnheader)
206 (require 'nnmail)
207 (eval-when-compile (require 'cl))
208
209 (require 'gnus-start)
210 (require 'gnus-sum)
211
212 ;; Compatibility Functions  =================================================
213
214 (eval-and-compile
215   (if (fboundp 'signal-error)
216       (defun nndiary-error (&rest args)
217         (apply #'signal-error 'nndiary args))
218     (defun nndiary-error (&rest args)
219       (apply #'error args))))
220
221
222 ;; Backend behavior customization ===========================================
223
224 (defgroup nndiary nil
225   "The Gnus Diary backend."
226   :group 'gnus-diary)
227
228 (defcustom nndiary-mail-sources
229   `((file :path ,(expand-file-name "~/.nndiary")))
230   "*NNDiary specific mail sources.
231 This variable is used by nndiary in place of the standard `mail-sources'
232 variable when `nndiary-get-new-mail' is set to non-nil.  These sources
233 must contain diary messages ONLY."
234   :group 'nndiary
235   :group 'mail-source
236   :type 'sexp)
237
238 (defcustom nndiary-split-methods '(("diary" ""))
239   "*NNDiary specific split methods.
240 This variable is used by nndiary in place of the standard
241 `nnmail-split-methods' variable when `nndiary-get-new-mail' is set to
242 non-nil."
243   :group 'nndiary
244   :group 'nnmail-split
245   :type '(choice (repeat :tag "Alist" (group (string :tag "Name") regexp))
246                  (function-item nnmail-split-fancy)
247                  (function :tag "Other")))
248
249
250 (defcustom nndiary-reminders '((0 . day))
251   "*Different times when you want to be reminded of your appointements.
252 Diary articles will appear again, as if they'd been just received.
253
254 Entries look like (3 . day) which means something like \"Please
255 Hortense, would you be so kind as to remind me of my appointments 3 days
256 before the date, thank you very much. Anda, hmmm... by the way, are you
257 doing anything special tonight ?\".
258
259 The units of measure are 'minute 'hour 'day 'week 'month and 'year (no,
260 not 'century, sorry).
261
262 NOTE: the units of measure actually express dates, not durations: if you
263 use 'week, messages will pop up on Sundays at 00:00 (or Mondays if
264 `nndiary-week-starts-on-monday' is non nil) and *not* 7 days before the
265 appointement, if you use 'month, messages will pop up on the first day of
266 each months, at 00:00 and so on.
267
268 If you really want to specify a duration (like 24 hours exactly), you can
269 use the equivalent in minutes (the smallest unit).  A fuzz of 60 seconds
270 maximum in the reminder is not that painful, I think.  Although this
271 scheme might appear somewhat weird at a first glance, it is very powerful.
272 In order to make this clear, here are some examples:
273
274 - '(0 . day): this is the default value of `nndiary-reminders'.  It means
275   pop up the appointements of the day each morning at 00:00.
276
277 - '(1 . day): this means pop up the appointements the day before, at 00:00.
278
279 - '(6 . hour): for an appointement at 18:30, this would pop up the
280   appointement message at 12:00.
281
282 - '(360 . minute): for an appointement at 18:30 and 15 seconds, this would
283   pop up the appointement message at 12:30."
284   :group 'nndiary
285   :type '(repeat (cons :format "%v\n"
286                        (integer :format "%v")
287                        (choice :format "%[%v(s)%] before...\n"
288                                :value day
289                                (const :format "%v" minute)
290                                (const :format "%v" hour)
291                                (const :format "%v" day)
292                                (const :format "%v" week)
293                                (const :format "%v" month)
294                                (const :format "%v" year)))))
295
296 (defcustom nndiary-week-starts-on-monday nil
297   "*Whether a week starts on monday (otherwise, sunday)."
298   :type 'boolean
299   :group 'nndiary)
300
301
302 (defcustom nndiary-request-create-group-hooks nil
303   "*Hooks to run after `nndiary-request-create-group' is executed.
304 The hooks will be called with the full group name as argument."
305   :group 'nndiary
306   :type 'hook)
307
308 (defcustom nndiary-request-update-info-hooks nil
309   "*Hooks to run after `nndiary-request-update-info-group' is executed.
310 The hooks will be called with the full group name as argument."
311   :group 'nndiary
312   :type 'hook)