commit | author | age
|
76bbd0
|
1 |
;;; org-bbdb.el --- Support for links to BBDB entries -*- lexical-binding: t; -*- |
C |
2 |
|
|
3 |
;; Copyright (C) 2004-2018 Free Software Foundation, Inc. |
|
4 |
|
|
5 |
;; Authors: Carsten Dominik <carsten at orgmode dot org> |
|
6 |
;; Thomas Baumann <thomas dot baumann at ch dot tum dot de> |
|
7 |
;; Keywords: outlines, hypermedia, calendar, wp |
|
8 |
;; Homepage: https://orgmode.org |
|
9 |
;; |
|
10 |
;; This file is part of GNU Emacs. |
|
11 |
;; |
|
12 |
;; GNU Emacs is free software: you can redistribute it and/or modify |
|
13 |
;; it under the terms of the GNU General Public License as published by |
|
14 |
;; the Free Software Foundation, either version 3 of the License, or |
|
15 |
;; (at your option) any later version. |
|
16 |
|
|
17 |
;; GNU Emacs is distributed in the hope that it will be useful, |
|
18 |
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19 |
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
20 |
;; GNU General Public License for more details. |
|
21 |
|
|
22 |
;; You should have received a copy of the GNU General Public License |
|
23 |
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. |
|
24 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
25 |
;; |
|
26 |
;;; Commentary: |
|
27 |
|
|
28 |
;; This file implements links to BBDB database entries from within Org. |
|
29 |
;; Org mode loads this module by default - if this is not what you want, |
|
30 |
;; configure the variable `org-modules'. |
|
31 |
|
|
32 |
;; It also implements an interface (based on Ivar Rummelhoff's |
|
33 |
;; bbdb-anniv.el) for those Org users, who do not use the diary |
|
34 |
;; but who do want to include the anniversaries stored in the BBDB |
|
35 |
;; into the org-agenda. If you already include the `diary' into the |
|
36 |
;; agenda, you might want to prefer to include the anniversaries in |
|
37 |
;; the diary using bbdb-anniv.el. |
|
38 |
;; |
|
39 |
;; Put the following in /somewhere/at/home/diary.org and make sure |
|
40 |
;; that this file is in `org-agenda-files'. |
|
41 |
;; |
|
42 |
;; %%(org-bbdb-anniversaries) |
|
43 |
;; |
|
44 |
;; For example my diary.org looks like: |
|
45 |
;; * Anniversaries |
|
46 |
;; #+CATEGORY: Anniv |
|
47 |
;; %%(org-bbdb-anniversaries) |
|
48 |
;; |
|
49 |
;; |
|
50 |
;; To add an anniversary to a BBDB record, press `C-o' in the record. |
|
51 |
;; You will be prompted for the field name, in this case it must be |
|
52 |
;; "anniversary". If this is the first time you are using this field, |
|
53 |
;; you need to confirm that it should be created. |
|
54 |
;; |
|
55 |
;; The format of an anniversary field stored in BBDB is the following |
|
56 |
;; (items in {} are optional): |
|
57 |
;; |
|
58 |
;; YYYY-MM-DD{ CLASS-OR-FORMAT-STRING} |
|
59 |
;; {\nYYYY-MM-DD CLASS-OR-FORMAT-STRING}... |
|
60 |
;; |
|
61 |
;; CLASS-OR-FORMAT-STRING is one of two things: |
|
62 |
;; |
|
63 |
;; - an identifier for a class of anniversaries (eg. birthday or |
|
64 |
;; wedding) from `org-bbdb-anniversary-format-alist' which then |
|
65 |
;; defines the format string for this class |
|
66 |
;; - the (format) string displayed in the diary. |
|
67 |
;; |
|
68 |
;; You can enter multiple anniversaries for a single BBDB record by |
|
69 |
;; separating them with a newline character. At the BBDB prompt for |
|
70 |
;; the field value, type `C-q C-j' to enter a newline between two |
|
71 |
;; anniversaries. |
|
72 |
;; |
|
73 |
;; If you omit the CLASS-OR-FORMAT-STRING entirely, it defaults to the |
|
74 |
;; value of `org-bbdb-default-anniversary-format' ("birthday" by |
|
75 |
;; default). |
|
76 |
;; |
|
77 |
;; The substitutions in the format string are (in order): |
|
78 |
;; - the name of the record containing this anniversary |
|
79 |
;; - the number of years |
|
80 |
;; - an ordinal suffix (st, nd, rd, th) for the year |
|
81 |
;; |
|
82 |
;; See the documentation of `org-bbdb-anniversary-format-alist' for |
|
83 |
;; further options. |
|
84 |
;; |
|
85 |
;; Example |
|
86 |
;; |
|
87 |
;; 1973-06-22 |
|
88 |
;; 20??-??-?? wedding |
|
89 |
;; 1998-03-12 %s created bbdb-anniv.el %d years ago |
|
90 |
;; |
|
91 |
;; From Org's agenda, you can use `C-c C-o' to jump to the BBDB |
|
92 |
;; link from which the entry at point originates. |
|
93 |
;; |
|
94 |
;;; Code: |
|
95 |
|
|
96 |
(require 'org) |
|
97 |
(require 'cl-lib) |
|
98 |
|
|
99 |
;; Declare external functions and variables |
|
100 |
|
|
101 |
(declare-function bbdb "ext:bbdb-com" (string elidep)) |
|
102 |
(declare-function bbdb-company "ext:bbdb-com" (string elidep)) |
|
103 |
(declare-function bbdb-current-record "ext:bbdb-com" |
|
104 |
(&optional planning-on-modifying)) |
|
105 |
(declare-function bbdb-name "ext:bbdb-com" (string elidep)) |
|
106 |
(declare-function bbdb-completing-read-record "ext:bbdb-com" |
|
107 |
(prompt &optional omit-records)) |
|
108 |
(declare-function bbdb-record-field "ext:bbdb" (record field)) |
|
109 |
(declare-function bbdb-record-getprop "ext:bbdb" (record property)) |
|
110 |
(declare-function bbdb-record-name "ext:bbdb" (record)) |
|
111 |
(declare-function bbdb-records "ext:bbdb" |
|
112 |
(&optional dont-check-disk already-in-db-buffer)) |
|
113 |
(declare-function bbdb-split "ext:bbdb" (string separators)) |
|
114 |
(declare-function bbdb-string-trim "ext:bbdb" (string)) |
|
115 |
(declare-function bbdb-record-get-field "ext:bbdb" (record field)) |
|
116 |
(declare-function bbdb-search-name "ext:bbdb-com" (regexp &optional layout)) |
|
117 |
(declare-function bbdb-search-organization "ext:bbdb-com" (regexp &optional layout)) |
|
118 |
|
|
119 |
;; `bbdb-record-note' was part of BBDB v3.x |
|
120 |
(declare-function bbdb-record-note "ext:bbdb" (record label)) |
|
121 |
;; `bbdb-record-xfield' replaces it in recent BBDB v3.x+ |
|
122 |
(declare-function bbdb-record-xfield "ext:bbdb" (record label)) |
|
123 |
|
|
124 |
(declare-function calendar-leap-year-p "calendar" (year)) |
|
125 |
(declare-function diary-ordinal-suffix "diary-lib" (n)) |
|
126 |
|
|
127 |
(with-no-warnings (defvar date)) ;; unprefixed, from calendar.el |
|
128 |
|
|
129 |
;; Customization |
|
130 |
|
|
131 |
(defgroup org-bbdb-anniversaries nil |
|
132 |
"Customizations for including anniversaries from BBDB into Agenda." |
|
133 |
:group 'org-bbdb) |
|
134 |
|
|
135 |
(defcustom org-bbdb-default-anniversary-format "birthday" |
|
136 |
"Default anniversary class." |
|
137 |
:type 'string |
|
138 |
:group 'org-bbdb-anniversaries |
|
139 |
:require 'bbdb) |
|
140 |
|
|
141 |
(defcustom org-bbdb-general-anniversary-description-after 7 |
|
142 |
"When to switch anniversary descriptions to a more general format. |
|
143 |
|
|
144 |
Anniversary descriptions include the point in time, when the |
|
145 |
anniversary appears. This is, in its most general form, just the |
|
146 |
date of the anniversary. Or more specific terms, like \"today\", |
|
147 |
\"tomorrow\" or \"in n days\" are used to describe the time span. |
|
148 |
|
|
149 |
If the anniversary happens in less than that number of days, the |
|
150 |
specific description is used. Otherwise, the general one is |
|
151 |
used." |
|
152 |
:group 'org-bbdb-anniversaries |
|
153 |
:version "26.1" |
|
154 |
:package-version '(Org . "9.1") |
|
155 |
:type 'integer |
|
156 |
:require 'bbdb |
|
157 |
:safe #'integerp) |
|
158 |
|
|
159 |
(defcustom org-bbdb-anniversary-format-alist |
|
160 |
'(("birthday" . |
|
161 |
(lambda (name years suffix) |
|
162 |
(concat "Birthday: [[bbdb:" name "][" name " (" |
|
163 |
(format "%s" years) ; handles numbers as well as strings |
|
164 |
suffix ")]]"))) |
|
165 |
("wedding" . |
|
166 |
(lambda (name years suffix) |
|
167 |
(concat "[[bbdb:" name "][" name "'s " |
|
168 |
(format "%s" years) |
|
169 |
suffix " wedding anniversary]]")))) |
|
170 |
"How different types of anniversaries should be formatted. |
|
171 |
An alist of elements (STRING . FORMAT) where STRING is the name of an |
|
172 |
anniversary class and format is either: |
|
173 |
1) A format string with the following substitutions (in order): |
|
174 |
- the name of the record containing this anniversary |
|
175 |
- the number of years |
|
176 |
- an ordinal suffix (st, nd, rd, th) for the year |
|
177 |
|
|
178 |
2) A function to be called with three arguments: NAME YEARS SUFFIX |
|
179 |
(string int string) returning a string for the diary or nil. |
|
180 |
|
|
181 |
3) An Emacs Lisp form that should evaluate to a string (or nil) in the |
|
182 |
scope of variables NAME, YEARS and SUFFIX (among others)." |
|
183 |
:type '(alist :key-type (string :tag "Class") |
|
184 |
:value-type (function :tag "Function")) |
|
185 |
:group 'org-bbdb-anniversaries |
|
186 |
:require 'bbdb) |
|
187 |
|
|
188 |
(defcustom org-bbdb-anniversary-field 'anniversary |
|
189 |
"The BBDB field which contains anniversaries. |
|
190 |
The anniversaries are stored in the following format |
|
191 |
|
|
192 |
YYYY-MM-DD Class-or-Format-String |
|
193 |
|
|
194 |
where class is one of the customized classes for anniversaries; |
|
195 |
birthday and wedding are predefined. Format-String can take three |
|
196 |
substitutions 1) the name of the record containing this |
|
197 |
anniversary, 2) the number of years, and 3) an ordinal suffix for |
|
198 |
the year. |
|
199 |
|
|
200 |
Multiple anniversaries can be separated by \\n." |
|
201 |
:type 'symbol |
|
202 |
:group 'org-bbdb-anniversaries |
|
203 |
:require 'bbdb) |
|
204 |
|
|
205 |
(defcustom org-bbdb-extract-date-fun 'org-bbdb-anniv-extract-date |
|
206 |
"How to retrieve `month date year' from the anniversary field. |
|
207 |
|
|
208 |
Customize if you have already filled your BBDB with dates |
|
209 |
different from YYYY-MM-DD. The function must return a list (month |
|
210 |
date year)." |
|
211 |
:type 'function |
|
212 |
:group 'org-bbdb-anniversaries |
|
213 |
:require 'bbdb) |
|
214 |
|
|
215 |
;; Install the link type |
|
216 |
(org-link-set-parameters "bbdb" |
|
217 |
:follow #'org-bbdb-open |
|
218 |
:export #'org-bbdb-export |
|
219 |
:complete #'org-bbdb-complete-link |
|
220 |
:store #'org-bbdb-store-link) |
|
221 |
|
|
222 |
;; Implementation |
|
223 |
(defun org-bbdb-store-link () |
|
224 |
"Store a link to a BBDB database entry." |
|
225 |
(when (eq major-mode 'bbdb-mode) |
|
226 |
;; This is BBDB, we make this link! |
|
227 |
(let* ((rec (bbdb-current-record)) |
|
228 |
(name (bbdb-record-name rec)) |
|
229 |
(company (if (fboundp 'bbdb-record-getprop) |
|
230 |
(bbdb-record-getprop rec 'company) |
|
231 |
(car (bbdb-record-field rec 'organization)))) |
|
232 |
(link (concat "bbdb:" name))) |
|
233 |
(org-store-link-props :type "bbdb" :name name :company company |
|
234 |
:link link :description name) |
|
235 |
link))) |
|
236 |
|
|
237 |
(defun org-bbdb-export (path desc format) |
|
238 |
"Create the export version of a BBDB link specified by PATH or DESC. |
|
239 |
If exporting to either HTML or LaTeX FORMAT the link will be |
|
240 |
italicized, in all other cases it is left unchanged." |
|
241 |
(when (string= desc (format "bbdb:%s" path)) |
|
242 |
(setq desc path)) |
|
243 |
(cond |
|
244 |
((eq format 'html) (format "<i>%s</i>" desc)) |
|
245 |
((eq format 'latex) (format "\\textit{%s}" desc)) |
|
246 |
((eq format 'odt) |
|
247 |
(format "<text:span text:style-name=\"Emphasis\">%s</text:span>" desc)) |
|
248 |
(t desc))) |
|
249 |
|
|
250 |
(defun org-bbdb-open (name) |
|
251 |
"Follow a BBDB link to NAME." |
|
252 |
(require 'bbdb-com) |
|
253 |
(let ((inhibit-redisplay (not debug-on-error))) |
|
254 |
(if (fboundp 'bbdb-name) |
|
255 |
(org-bbdb-open-old name) |
|
256 |
(org-bbdb-open-new name)))) |
|
257 |
|
|
258 |
(defun org-bbdb-open-old (name) |
|
259 |
(catch 'exit |
|
260 |
;; Exact match on name |
|
261 |
(bbdb-name (concat "\\`" name "\\'") nil) |
|
262 |
(if (< 0 (buffer-size (get-buffer "*BBDB*"))) (throw 'exit nil)) |
|
263 |
;; Exact match on name |
|
264 |
(bbdb-company (concat "\\`" name "\\'") nil) |
|
265 |
(if (< 0 (buffer-size (get-buffer "*BBDB*"))) (throw 'exit nil)) |
|
266 |
;; Partial match on name |
|
267 |
(bbdb-name name nil) |
|
268 |
(if (< 0 (buffer-size (get-buffer "*BBDB*"))) (throw 'exit nil)) |
|
269 |
;; Partial match on company |
|
270 |
(bbdb-company name nil) |
|
271 |
(if (< 0 (buffer-size (get-buffer "*BBDB*"))) (throw 'exit nil)) |
|
272 |
;; General match including network address and notes |
|
273 |
(bbdb name nil) |
|
274 |
(when (= 0 (buffer-size (get-buffer "*BBDB*"))) |
|
275 |
(delete-window (get-buffer-window "*BBDB*")) |
|
276 |
(error "No matching BBDB record")))) |
|
277 |
|
|
278 |
(defun org-bbdb-open-new (name) |
|
279 |
(catch 'exit |
|
280 |
;; Exact match on name |
|
281 |
(bbdb-search-name (concat "\\`" name "\\'") nil) |
|
282 |
(if (< 0 (buffer-size (get-buffer "*BBDB*"))) (throw 'exit nil)) |
|
283 |
;; Exact match on name |
|
284 |
(bbdb-search-organization (concat "\\`" name "\\'") nil) |
|
285 |
(if (< 0 (buffer-size (get-buffer "*BBDB*"))) (throw 'exit nil)) |
|
286 |
;; Partial match on name |
|
287 |
(bbdb-search-name name nil) |
|
288 |
(if (< 0 (buffer-size (get-buffer "*BBDB*"))) (throw 'exit nil)) |
|
289 |
;; Partial match on company |
|
290 |
(bbdb-search-organization name nil) |
|
291 |
(if (< 0 (buffer-size (get-buffer "*BBDB*"))) (throw 'exit nil)) |
|
292 |
;; General match including network address and notes |
|
293 |
(bbdb name nil) |
|
294 |
(when (= 0 (buffer-size (get-buffer "*BBDB*"))) |
|
295 |
(delete-window (get-buffer-window "*BBDB*")) |
|
296 |
(error "No matching BBDB record")))) |
|
297 |
|
|
298 |
(defun org-bbdb-anniv-extract-date (time-str) |
|
299 |
"Convert YYYY-MM-DD to (month date year). |
|
300 |
Argument TIME-STR is the value retrieved from BBDB. If YYYY- is omitted |
|
301 |
it will be considered unknown." |
|
302 |
(pcase (org-split-string time-str "-") |
|
303 |
(`(,a ,b nil) (list (string-to-number a) (string-to-number b) nil)) |
|
304 |
(`(,a ,b ,c) (list (string-to-number b) |
|
305 |
(string-to-number c) |
|
306 |
(string-to-number a))))) |
|
307 |
|
|
308 |
(defun org-bbdb-anniv-split (str) |
|
309 |
"Split multiple entries in the BBDB anniversary field. |
|
310 |
Argument STR is the anniversary field in BBDB." |
|
311 |
(let ((pos (string-match "[ \t]" str))) |
|
312 |
(if pos (list (substring str 0 pos) |
|
313 |
(bbdb-string-trim (substring str pos))) |
|
314 |
(list str nil)))) |
|
315 |
|
|
316 |
(defvar org-bbdb-anniv-hash nil |
|
317 |
"A hash holding anniversaries extracted from BBDB. |
|
318 |
The hash table is created on first use.") |
|
319 |
|
|
320 |
(defvar org-bbdb-updated-p t |
|
321 |
"This is non-nil if BBDB has been updated since we last built the hash.") |
|
322 |
|
|
323 |
(defun org-bbdb-make-anniv-hash () |
|
324 |
"Create a hash with anniversaries extracted from BBDB, for fast access. |
|
325 |
The anniversaries are assumed to be stored `org-bbdb-anniversary-field'." |
|
326 |
(let ((old-bbdb (fboundp 'bbdb-record-getprop)) |
|
327 |
(record-func (if (fboundp 'bbdb-record-xfield) |
|
328 |
'bbdb-record-xfield |
|
329 |
'bbdb-record-note)) |
|
330 |
split tmp annivs) |
|
331 |
(clrhash org-bbdb-anniv-hash) |
|
332 |
(dolist (rec (bbdb-records)) |
|
333 |
(when (setq annivs (if old-bbdb |
|
334 |
(bbdb-record-getprop |
|
335 |
rec org-bbdb-anniversary-field) |
|
336 |
(funcall record-func |
|
337 |
rec org-bbdb-anniversary-field))) |
|
338 |
(setq annivs (if old-bbdb |
|
339 |
(bbdb-split annivs "\n") |
|
340 |
;; parameter order is reversed in new bbdb |
|
341 |
(bbdb-split "\n" annivs))) |
|
342 |
(while annivs |
|
343 |
(setq split (org-bbdb-anniv-split (pop annivs))) |
|
344 |
(pcase-let ((`(,m ,d ,y) (funcall org-bbdb-extract-date-fun |
|
345 |
(car split)))) |
|
346 |
(setq tmp (gethash (list m d) org-bbdb-anniv-hash)) |
|
347 |
(puthash (list m d) (cons (list y |
|
348 |
(bbdb-record-name rec) |
|
349 |
(cadr split)) |
|
350 |
tmp) |
|
351 |
org-bbdb-anniv-hash)))))) |
|
352 |
(setq org-bbdb-updated-p nil)) |
|
353 |
|
|
354 |
(defun org-bbdb-updated (_rec) |
|
355 |
"Record the fact that BBDB has been updated. |
|
356 |
This is used by Org to re-create the anniversary hash table." |
|
357 |
(setq org-bbdb-updated-p t)) |
|
358 |
|
|
359 |
(add-hook 'bbdb-after-change-hook 'org-bbdb-updated) |
|
360 |
|
|
361 |
;;;###autoload |
|
362 |
(defun org-bbdb-anniversaries () |
|
363 |
"Extract anniversaries from BBDB for display in the agenda." |
|
364 |
(require 'bbdb) |
|
365 |
(require 'diary-lib) |
|
366 |
(unless (hash-table-p org-bbdb-anniv-hash) |
|
367 |
(setq org-bbdb-anniv-hash |
|
368 |
(make-hash-table :test 'equal :size 366))) |
|
369 |
|
|
370 |
(when (or org-bbdb-updated-p |
|
371 |
(= 0 (hash-table-count org-bbdb-anniv-hash))) |
|
372 |
(org-bbdb-make-anniv-hash)) |
|
373 |
|
|
374 |
(let* ((m (car date)) ; month |
|
375 |
(d (nth 1 date)) ; day |
|
376 |
(y (nth 2 date)) ; year |
|
377 |
(annivs (gethash (list m d) org-bbdb-anniv-hash)) |
|
378 |
(text ()) |
|
379 |
rec recs) |
|
380 |
|
|
381 |
;; we don't want to miss people born on Feb. 29th |
|
382 |
(when (and (= m 3) (= d 1) |
|
383 |
(not (null (gethash (list 2 29) org-bbdb-anniv-hash))) |
|
384 |
(not (calendar-leap-year-p y))) |
|
385 |
(setq recs (gethash (list 2 29) org-bbdb-anniv-hash)) |
|
386 |
(while (setq rec (pop recs)) |
|
387 |
(push rec annivs))) |
|
388 |
|
|
389 |
(when annivs |
|
390 |
(while (setq rec (pop annivs)) |
|
391 |
(when rec |
|
392 |
(let* ((class (or (nth 2 rec) |
|
393 |
org-bbdb-default-anniversary-format)) |
|
394 |
(form (or (cdr (assoc-string |
|
395 |
class org-bbdb-anniversary-format-alist t)) |
|
396 |
class)) ; (as format string) |
|
397 |
(name (nth 1 rec)) |
|
398 |
(years (if (eq (car rec) nil) |
|
399 |
"unknown" |
|
400 |
(- y (car rec)))) |
|
401 |
(suffix (if (eq (car rec) nil) |
|
402 |
"" |
|
403 |
(diary-ordinal-suffix years))) |
|
404 |
(tmp (cond |
|
405 |
((functionp form) |
|
406 |
(funcall form name years suffix)) |
|
407 |
((listp form) (eval form)) |
|
408 |
(t (format form name years suffix))))) |
|
409 |
(org-add-props tmp nil 'org-bbdb-name name) |
|
410 |
(if text |
|
411 |
(setq text (append text (list tmp))) |
|
412 |
(setq text (list tmp))))) |
|
413 |
)) |
|
414 |
text)) |
|
415 |
|
|
416 |
;;; Return list of anniversaries for today and the next n-1 (default: n=7) days. |
|
417 |
;;; This is meant to be used in an org file instead of org-bbdb-anniversaries: |
|
418 |
;;; |
|
419 |
;;; %%(org-bbdb-anniversaries-future) |
|
420 |
;;; |
|
421 |
;;; or |
|
422 |
;;; |
|
423 |
;;; %%(org-bbdb-anniversaries-future 3) |
|
424 |
;;; |
|
425 |
;;; to override the 7-day default. |
|
426 |
|
|
427 |
(defun org-bbdb-date-list (d n) |
|
428 |
"Return a list of dates in (m d y) format from the given date D to n-1 days hence." |
|
429 |
(let ((abs (calendar-absolute-from-gregorian d))) |
|
430 |
(mapcar (lambda (i) (calendar-gregorian-from-absolute (+ abs i))) |
|
431 |
(number-sequence 0 (1- n))))) |
|
432 |
|
|
433 |
(defun org-bbdb-anniversary-description (agenda-date anniv-date) |
|
434 |
"Return a string used to incorporate into an agenda anniversary entry. |
|
435 |
The calculation of the anniversary description string is based on |
|
436 |
the difference between the anniversary date, given as ANNIV-DATE, |
|
437 |
and the date on which the entry appears in the agenda, given as |
|
438 |
AGENDA-DATE. This makes it possible to have different entries |
|
439 |
for the same event depending on if it occurs in the next few days |
|
440 |
or far away in the future." |
|
441 |
(let ((delta (- (calendar-absolute-from-gregorian anniv-date) |
|
442 |
(calendar-absolute-from-gregorian agenda-date)))) |
|
443 |
|
|
444 |
(cond |
|
445 |
((= delta 0) " -- today\\&") |
|
446 |
((= delta 1) " -- tomorrow\\&") |
|
447 |
((< delta org-bbdb-general-anniversary-description-after) (format " -- in %d days\\&" delta)) |
|
448 |
((pcase-let ((`(,month ,day ,year) anniv-date)) |
|
449 |
(format " -- %d-%02d-%02d\\&" year month day)))))) |
|
450 |
|
|
451 |
|
|
452 |
(defun org-bbdb-anniversaries-future (&optional n) |
|
453 |
"Return list of anniversaries for today and the next n-1 days (default n=7)." |
|
454 |
(let ((n (or n 7))) |
|
455 |
(when (<= n 0) |
|
456 |
(error "The (optional) argument of `org-bbdb-anniversaries-future' \ |
|
457 |
must be positive")) |
|
458 |
(let ( |
|
459 |
;; List of relevant dates. |
|
460 |
(dates (org-bbdb-date-list date n)) |
|
461 |
;; Function to annotate text of each element of l with the |
|
462 |
;; anniversary date d. |
|
463 |
(annotate-descriptions |
|
464 |
(lambda (agenda-date d l) |
|
465 |
(mapcar (lambda (x) |
|
466 |
;; The assumption here is that x is a bbdb link |
|
467 |
;; of the form [[bbdb:name][description]]. |
|
468 |
;; This function rather arbitrarily modifies |
|
469 |
;; the description by adding the date to it in |
|
470 |
;; a fixed format. |
|
471 |
(let ((desc (org-bbdb-anniversary-description |
|
472 |
agenda-date d))) |
|
473 |
(string-match "]]" x) |
|
474 |
(replace-match desc nil nil x))) |
|
475 |
l)))) |
|
476 |
;; Map a function that generates anniversaries for each date |
|
477 |
;; over the dates and nconc the results into a single list. When |
|
478 |
;; it is no longer necessary to support older versions of Emacs, |
|
479 |
;; this can be done with a cl-mapcan; for now, we use the (apply |
|
480 |
;; #'nconc ...) method for compatibility. |
|
481 |
(apply #'nconc |
|
482 |
(mapcar |
|
483 |
(lambda (d) |
|
484 |
(let ((agenda-date date) |
|
485 |
(date d)) |
|
486 |
;; Rebind 'date' so that org-bbdb-anniversaries will |
|
487 |
;; be fooled into giving us the list for the given |
|
488 |
;; date and then annotate the descriptions for that |
|
489 |
;; date. |
|
490 |
(funcall annotate-descriptions agenda-date d (org-bbdb-anniversaries)))) |
|
491 |
dates))))) |
|
492 |
|
|
493 |
(defun org-bbdb-complete-link () |
|
494 |
"Read a bbdb link with name completion." |
|
495 |
(require 'bbdb-com) |
|
496 |
(let ((rec (bbdb-completing-read-record "Name: "))) |
|
497 |
(concat "bbdb:" |
|
498 |
(bbdb-record-name (if (listp rec) |
|
499 |
(car rec) |
|
500 |
rec))))) |
|
501 |
|
|
502 |
(defun org-bbdb-anniv-export-ical () |
|
503 |
"Extract anniversaries from BBDB and convert them to icalendar format." |
|
504 |
(require 'bbdb) |
|
505 |
(require 'diary-lib) |
|
506 |
(unless (hash-table-p org-bbdb-anniv-hash) |
|
507 |
(setq org-bbdb-anniv-hash |
|
508 |
(make-hash-table :test 'equal :size 366))) |
|
509 |
(when (or org-bbdb-updated-p |
|
510 |
(= 0 (hash-table-count org-bbdb-anniv-hash))) |
|
511 |
(org-bbdb-make-anniv-hash)) |
|
512 |
(maphash 'org-bbdb-format-vevent org-bbdb-anniv-hash)) |
|
513 |
|
|
514 |
(defun org-bbdb-format-vevent (key recs) |
|
515 |
(let (rec categ) |
|
516 |
(while (setq rec (pop recs)) |
|
517 |
(setq categ (or (nth 2 rec) org-bbdb-default-anniversary-format)) |
|
518 |
(princ (format "BEGIN:VEVENT |
|
519 |
UID: ANNIV-%4i%02i%02i-%s |
|
520 |
DTSTART:%4i%02i%02i |
|
521 |
SUMMARY:%s |
|
522 |
DESCRIPTION:%s |
|
523 |
CATEGORIES:%s |
|
524 |
RRULE:FREQ=YEARLY |
|
525 |
END:VEVENT\n" |
|
526 |
(nth 0 rec) (nth 0 key) (nth 1 key) |
|
527 |
(mapconcat 'identity |
|
528 |
(org-split-string (nth 1 rec) "[^a-zA-Z0-90]+") |
|
529 |
"-") |
|
530 |
(nth 0 rec) (nth 0 key) (nth 1 key) |
|
531 |
(nth 1 rec) |
|
532 |
(concat (capitalize categ) " " (nth 1 rec)) |
|
533 |
categ))))) |
|
534 |
|
|
535 |
(provide 'org-bbdb) |
|
536 |
|
|
537 |
;; Local variables: |
|
538 |
;; generated-autoload-file: "org-loaddefs.el" |
|
539 |
;; End: |
|
540 |
|
|
541 |
;;; org-bbdb.el ends here |