1 ;; wl-addrbook.el --- Aliases and personal information
3 ;; Author: Masahiro MURATA <muse@ba2.so-net.ne.jp>
4 ;; Kazu Yamamoto <Kazu@Mew.org>
5 ;; Keywords: mail, net news
9 ;; Insert the following lines in your ~/.wl
11 ;; (require 'wl-addrbook)
12 ;; (wl-addrbook-setup)
14 ;; Original code: Kazu Yamamoto <Kazu@Mew.org>
15 ;; mew-addrbook.el (Mew developing team)
21 (defvar wl-addrbook-file "~/.im/Addrbook"
22 "*Addrbook file for completion")
23 (defvar wl-addrbook-expand-max-depth 5
24 "*A value to limit alias(addrbook) expansion loop.")
25 (defvar wl-addrbook-comment-regexp "^;.*$\\|#.*$"
26 "*Regular expression for \".im/Addrbook\".")
27 (defvar wl-addrbook-override-by-newone t
28 "If non-nil, the 'user' entry in 'wl-alias-auto-alist'
29 is override by a new entry of (user different-address).
30 This means that addresses in To: and Cc: in Draft mode are
31 always learned with an exception 'user' is defined in Addrbook.
32 If nil, the old 'user' entry remains.")
34 ;;(defvar wl-anonymous-recipients ":;")
36 (defvar wl-addrbook-hashtb nil)
38 (defvar wl-addrbook-strip-domainpart t
39 "*If *non-nil*, a shortname is created by stripping its domain part.")
41 (defvar wl-addrbook-alist nil
42 "(key addr) or (key (addr1, addr2) nickname name)")
43 (defvar wl-alias-auto-alist nil
45 (defvar wl-alias-auto-file-name "auto-alias")
47 (defvar wl-summary-use-addrbook-from-func t)
51 (defun wl-uniq-alist (alst)
52 "Distractively uniqfy elements of ALST."
54 (while tmp (setq tmp (setcdr tmp (wl-delete-alist2 (car (car tmp)) (cdr tmp))))))
57 (defun wl-delete-alist2 (key alist)
58 "Destructively delete elements whose first member is equal to key"
62 (while (equal (car (nth 0 alist)) key)
63 (setq alist (cdr alist)))
66 (if (equal (car (nth 1 alist)) key)
67 (setcdr alist (cdr (cdr alist)))
68 (setq alist (cdr alist))))
71 (defun wl-get-next (LIST MEM)
73 (setq frst (car LIST))
74 (setq LIST (cdr LIST))
75 (setq next (car LIST))
81 (setq LIST (cdr LIST))
82 (setq next (car LIST))
84 (throw 'loop (if next next frst))))))))
86 (defun wl-address-extract-user (addr)
87 "Extracts username from ADDR"
88 (if (string-match "@.*:" addr) ;; xxx what's this?
89 (setq addr (substring addr (match-end 0) nil))
90 (setq addr (elmo-replace-in-string addr " " "_"))
91 (setq addr (substring addr 0 (string-match "%" addr)))
92 (setq addr (substring addr 0 (string-match "@" addr)))
93 ;; just for refile: "To: recipients:;" -> recipients
94 ;;(setq addr (substring addr 0 (string-match wl-anonymous-recipients addr)))
95 ;; removing Notes domain
96 (setq addr (substring addr 0 (string-match "/" addr)))))
98 (defun wl-address-parse-address-list (addrs)
99 (mapcar 'wl-address-header-extract-address (wl-parse-addresses addrs)))
101 ;; hash table for wl-addrbook-alist
102 (defmacro wl-addrbook-hashtb ()
103 (` (or wl-addrbook-hashtb
104 (setq wl-addrbook-hashtb (elmo-make-hash 1021)))))
106 (defsubst wl-addrbook-get-record-by-addr (addr &optional alist)
107 (elmo-get-hash-val (downcase addr) (wl-addrbook-hashtb)))
109 (defsubst wl-addrbook-get-record-by-alias (alias &optional alist)
110 (elmo-get-hash-val (format "#%s" (downcase alias)) (wl-addrbook-hashtb)))
112 (defun wl-addrbook-make-hashtb ()
113 (let ((ht (wl-addrbook-hashtb))
114 (alist wl-addrbook-alist)
117 (setq list (car alist)
121 (elmo-set-hash-val (format "#%s" (downcase (car list))) list ht))
122 (when (listp (setq addrs (nth 1 list)))
124 (setq addr (car addrs)
127 (elmo-set-hash-val (downcase addr) list ht))))))
129 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
134 (defun wl-addrbook-setup ()
135 (require 'wl-complete)
136 ;; replace wl-address-init function.
137 (setq wl-address-init-function 'wl-addrbook-init)
139 (when wl-summary-use-addrbook-from-func
140 (setq wl-summary-from-function 'wl-summary-addrbook-from))
141 (define-key wl-summary-mode-map "\C-c\C-a" 'wl-summary-addrbook-add)
142 (define-key wl-draft-mode-map "\C-i" 'wl-draft-addrbook-header-comp-or-tab)
143 (define-key wl-draft-mode-map "\e\t" 'wl-draft-addrbook-expand)
144 (define-key wl-draft-mode-map "\C-c\t" 'wl-draft-circular-comp)
145 (add-hook 'mail-send-hook 'wl-draft-learn-alias))
147 (defun wl-addrbook-init ()
148 (message "Updating addrbook...")
149 (or wl-alias-auto-alist
150 (if wl-alias-auto-file-name
151 (setq wl-alias-auto-alist
152 (elmo-object-load (expand-file-name
153 wl-alias-auto-file-name
154 elmo-msgdb-directory)))))
155 (setq wl-addrbook-alist (wl-addrbook-make-alist))
156 ;; wl-alias-auto-alist is used independently so must use copy-alist
157 (if wl-addrbook-alist
158 (nconc wl-addrbook-alist (copy-alist wl-alias-auto-alist))
159 (setq wl-addrbook-alist (copy-alist wl-alias-auto-alist)))
160 ;; (if wl-addrbook-alist
161 ;; (nconc wl-addrbook-alist (wl-petname-make-alist))
162 ;; (setq wl-addrbook-version (wl-petname-make-alist)))
163 (setq wl-addrbook-alist (wl-uniq-alist wl-addrbook-alist))
164 (wl-addrbook-make-hashtb)
165 (add-hook 'kill-emacs-hook (function wl-addrbook-clean-up))
166 (add-hook 'wl-exit-hook (function wl-addrbook-clean-up))
167 (message "Updating addrbook...done."))
169 (defun wl-addrbook-clean-up ()
170 (remove-hook 'kill-emacs-hook (function wl-addrbook-clean-up))
171 (remove-hook 'wl-exit-hook (function wl-addrbook-clean-up))
172 (when wl-alias-auto-file-name
173 (elmo-object-save (expand-file-name
174 wl-alias-auto-file-name
175 elmo-msgdb-directory)
177 (setq wl-alias-auto-alist nil)
178 (setq wl-addrbook-hashtb nil)))
182 (defmacro wl-alias-get (key)
183 (` (wl-addrbook-alias-get (, key) wl-addrbook-alist)))
185 (defmacro wl-alias-next (key)
186 (` (wl-addrbook-alias-next (, key) wl-addrbook-alist)))
188 (defalias 'wl-addrbook-alias-hit 'wl-addrbook-get-record-by-alias)
190 (defun wl-addrbook-alias-get (key alist)
191 (let ((addrs (wl-addrbook-alias-get1 key alist 0)))
193 ((stringp addrs) addrs)
195 (mapconcat (lambda (x) x) (nreverse addrs) ", "))
198 (defun wl-addrbook-alias-get1 (key alist n)
199 "Expand KEY to addresses according ALIST.
200 If addresses is a list, that follows one-of convention and
201 return the first member of the list.
202 If addresses is a string, expands it recursively."
203 (let* ((crnt (nth 1 (wl-addrbook-alias-hit key alist)))
204 (keys (and (stringp crnt)
205 (elmo-parse crnt "\\([^, \t]+\\)")))
208 ((> n wl-addrbook-expand-max-depth) key)
210 ((listp crnt) (car crnt))
213 (setq tmp (wl-addrbook-alias-get1 (car keys) alist (1+ n)))
215 (setq ret (nconc tmp ret))
216 (setq ret (cons tmp ret)))
217 (setq keys (cdr keys)))
220 (defun wl-addrbook-alias-next (key alist)
221 (let* ((addrs (nth 1 (wl-addrbook-get-record-by-addr key alist))))
222 (if (and addrs (listp addrs))
223 (wl-get-next addrs key))))
225 (defun wl-addrbook-alias-add (addr)
226 (if (and (stringp addr) (string-match "@" addr))
227 (let* ((user (wl-address-extract-user addr))
228 (match-auto (assoc user wl-alias-auto-alist))
229 (match-adbk (assoc user wl-addrbook-alist)))
233 ((equal addr (nth 1 match-auto))
234 ;; move the entry to the top for the recent-used-first.
235 (setq wl-alias-auto-alist
236 (cons match-auto (delete match-auto wl-alias-auto-alist))))
237 (wl-addrbook-override-by-newone
238 ;; override match-auto by (user addr)
239 (setq wl-addrbook-alist
240 (cons (list user addr)
241 (delete match-auto wl-addrbook-alist)))
242 (setq wl-alias-auto-alist
243 (cons (list user addr)
244 (delete match-auto wl-alias-auto-alist))))
246 ;; the old entry remains
252 (setq wl-addrbook-alist (cons (list user addr) wl-addrbook-alist))
253 (setq wl-alias-auto-alist
254 (cons (list user addr) wl-alias-auto-alist)))))))
256 (defun wl-addrbook-alias-delete (addr)
257 (if (and (stringp addr) (string-match "@" addr))
258 (let* ((user (wl-address-extract-user addr))
259 (ent (assoc user wl-addrbook-alist)))
260 (if (and ent (equal (cdr ent) addr))
262 (setq wl-addrbook-alist (delete ent wl-addrbook-alist))
263 (setq wl-alias-auto-alist (delete ent wl-alias-auto-alist)))))))
267 (defun wl-addrbook-shortname-get (addr)
268 (nth 0 (wl-addrbook-get-record-by-addr addr)))
270 (defun wl-addrbook-nickname-get (addr)
271 (nth 2 (wl-addrbook-get-record-by-addr addr)))
273 (defun wl-addrbook-name-get (addr)
274 (nth 3 (wl-addrbook-get-record-by-addr addr)))
277 (defun wl-addrbook-insert-file (file cregexp &optional unquote)
278 (let* ((case-fold-search t)
279 (coding-system-for-read wl-cs-autoconv)
280 (pars (elmo-parse file "\\([^, ]+\\)")) ;; parents
281 (files pars) ;; included
282 par chr path beg qchar)
283 ;; include parents files
285 (setq par (car pars))
286 (setq pars (cdr pars))
287 (if (not (file-readable-p par))
289 (insert-file-contents par)
290 (setq path (file-name-directory par))
291 ;; include children files
292 (while (re-search-forward "^\<[ \t]*\\([^ \t\n]+\\).*$" nil t)
293 (setq chr (expand-file-name (wl-match-buffer 1) path))
294 (delete-region (match-beginning 0) (match-end 0))
295 (if (and (file-readable-p chr) (not (member chr files)))
297 (insert-file-contents chr)
298 (setq files (cons chr files)))))
299 (goto-char (point-max))))
301 (goto-char (point-min))
302 (while (re-search-forward cregexp nil t)
303 (delete-region (match-beginning 0) (match-end 0)))
304 ;; concat continuation lines
305 (goto-char (point-min))
306 (while (re-search-forward "\\\\\n" nil t)
307 (delete-region (match-beginning 0) (match-end 0)))
308 ;; concat separated lines by comma
309 (goto-char (point-min))
310 (while (re-search-forward ",[ \t]*$" nil t)
313 (delete-backward-char 1)
314 (delete-horizontal-space))
315 ;; unquote, replace white spaces to "\0".
318 (goto-char (point-min))
319 (while (re-search-forward "[\"']" nil t)
320 (setq qchar (char-before (point)))
321 ;; (point) is for backward compatibility
322 (backward-delete-char 1) ;; delete quote
324 (if (not (re-search-forward (char-to-string qchar) nil t))
325 (throw 'quote nil) ;; error
326 (backward-delete-char 1) ;; delete quote
328 (narrow-to-region beg (point))
329 (goto-char (point-min))
330 (while (re-search-forward "[ \t]+" nil t)
331 (replace-match "\0"))
332 (goto-char (point-max))))))) ;; just in case
333 ;; remove optional white spaces
334 (goto-char (point-min))
335 (while (re-search-forward "[ \t]+" nil t)
336 (replace-match " "))))
338 (defun wl-addrbook-strsafe (var)
339 (if (or (string-equal "" var) (string-equal "*" var))
342 (elmo-replace-in-string var (char-to-string 0) " "))))
344 (defun wl-addrbook-make-alist ()
345 (let (alias colon addrs nick name alist)
347 (wl-addrbook-insert-file
348 wl-addrbook-file wl-addrbook-comment-regexp 'unquote)
349 (goto-char (point-min))
350 (while (re-search-forward "^ ?\\([^ \n:]+\\) ?\\(:?\\) ?\\([^ \n]+\\)" nil t)
351 (setq alias (wl-addrbook-strsafe (wl-match-buffer 1)))
352 (setq colon (wl-match-buffer 2))
353 (setq addrs (wl-addrbook-strsafe (wl-match-buffer 3)))
354 (if (equal colon ":")
355 (setq alist (cons (list alias addrs) alist))
356 (and addrs (setq addrs (elmo-parse addrs "\\([^, \t\r\n]+\\)")))
357 (if (looking-at " ?\\([^ \n]*\\) ?\\([^ \n]*\\)")
359 (setq nick (wl-addrbook-strsafe (wl-match-buffer 1)))
360 (setq name (wl-addrbook-strsafe (wl-match-buffer 2))))
363 (setq alist (cons (list alias addrs nick name) alist))))
366 (defun wl-draft-learn-alias ()
368 (let ((recipients (mapconcat 'identity
369 (delq nil (std11-field-bodies '("To" "Cc")))
371 (mapcar '(lambda (addr)
372 (wl-addrbook-alias-add
373 (wl-address-header-extract-address addr)))
374 (wl-parse-addresses recipients))))
376 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
381 (defvar wl-addrbook-mode-map nil)
383 (if wl-addrbook-mode-map
385 ;;(setq wl-addrbook-mode-map (make-sparse-keymap))
386 ;;(set-keymap-parent wl-addrbook-mode-map text-mode-map)
387 (setq wl-addrbook-mode-map (copy-keymap text-mode-map))
388 (define-key wl-addrbook-mode-map "\C-c\C-c" 'wl-addrbook-register)
389 (define-key wl-addrbook-mode-map "\C-c\C-q" 'wl-addrbook-kill))
391 (defvar wl-addrbook-mode-alias "Alias")
392 (defvar wl-addrbook-mode-personalinfo "Personal Info")
393 (defconst wl-addrbook-buffer-name "*WL Addrbook*")
395 (defun wl-summary-addrbook-add (&optional personalinfo)
396 "Adding the value of From: or To: in Message mode to Addrbook. When
397 executed with '\\[universal-argument]', it will add personal information. Otherwise,
398 it will add an alias."
400 (wl-summary-redisplay)
401 (let ((buf wl-message-buffer)
402 from shortname address addrs name)
405 (setq address (std11-field-body "From"))
406 (if (wl-address-user-mail-address-p address)
407 (setq address (std11-field-body "To")))
409 (message "No address to be registered")
410 (setq addrs (wl-address-header-extract-address address))
411 (if wl-addrbook-strip-domainpart
412 (setq shortname (wl-address-extract-user addrs))
413 (setq shortname addrs))
414 (if (string-match "\\(.*\\)<.*>" address)
416 (setq name (wl-match-string 1 address))
417 (setq name (elmo-replace-in-string name "[ \t]$" ""))))
418 (wl-addrbook-prepare-template personalinfo shortname addrs nil name)))))
420 (defun wl-addrbook-prepare-template (personalinfop shortname addrs &optional nickname name)
421 (delete-other-windows)
422 (switch-to-buffer (get-buffer-create wl-addrbook-buffer-name))
424 (insert "#If you want to register this entry, type "
425 (substitute-command-keys
426 "'\\<wl-addrbook-mode-map>\\[wl-addrbook-register]'.\n")
427 "#If you want to NOT register this entry, type "
428 (substitute-command-keys
429 "'\\<wl-addrbook-mode-map>\\[wl-addrbook-kill]'.\n"))
430 (wl-addrbook-insert-template "Shortname" shortname)
431 (wl-addrbook-insert-template "Addresses" addrs)
434 (wl-addrbook-insert-template "Nickname" nickname)
435 (wl-addrbook-insert-template "Name" name)
436 (wl-addrbook-mode wl-addrbook-mode-personalinfo))
438 (wl-addrbook-mode wl-addrbook-mode-alias)))
439 (wl-addrbook-insert-template "Comments" nil)
440 (goto-char (point-min))
441 (search-forward ": " nil t))
443 (defun wl-addrbook-insert-template (key val)
444 (let ((buffer-read-only nil)
445 (inhibit-read-only t)
448 (put-text-property beg (point) 'read-only t)
449 (put-text-property (1- (point)) (point)
450 (if wl-on-xemacs 'end-open 'rear-nonsticky)
452 (and val (insert val))
455 (defun wl-addrbook-mode (mname)
456 "\\<wl-addrbook-mode-map>
457 Mew Addrbook mode:: major mode to resistor Addrbook.
458 The keys that are defined for this mode are:
460 \\[wl-addrbook-register] Register information in Addrbook mode to Addrbook.
461 \\[wl-addrbook-kill] Kill Addrbook mode.
464 (setq major-mode 'wl-addrbook-mode)
465 (setq mode-name mname)
466 (setq mode-line-buffer-identification
467 (format "Wanderlust: %s" mname))
468 (use-local-map wl-addrbook-mode-map)
469 (run-hooks 'wl-addrbook-mode-hook)
470 (setq buffer-undo-list nil))
472 (defun wl-addrbook-register ()
473 "Register information in Addrbook mode to Addrbook."
475 (let ((shortname (std11-field-body "Shortname"))
476 (addrs (std11-field-body "Addresses"))
477 (nickname (std11-field-body "Nickname"))
478 (name (std11-field-body "Name"))
479 (comments (std11-field-body "Comments"))
481 buf addrsl errmsg not-uniq)
483 ((equal mode wl-addrbook-mode-alias)
485 ((and (null shortname) (null addrs))
486 (setq errmsg "Must fill both Shortname and Addresses."))
488 (setq errmsg "Must fill Shortname."))
490 (setq errmsg "Must fill Addresses."))))
494 (setq errmsg "Must fill Addresses."))
495 ((and (null shortname) (null nickname) (null name))
496 (setq errmsg "Must fill Shortname or Nickname or Name."))
497 ((and name (string-match "^\"[^\"]*[^\000-\177]" name))
498 (setq errmsg "Remove quote around non-ASCII Name.")))))
502 (setq buf (find-file-noselect wl-addrbook-file))
504 (goto-char (point-min))
507 (concat "^" (regexp-quote shortname) "[ \t]*:?[ \t]+") nil t))
511 ;; All errors are checked.
512 (goto-char (point-max))
513 (if (not (bolp)) (insert "\n"))
515 ((equal mode wl-addrbook-mode-alias)
516 (setq wl-addrbook-alist
517 (cons (list shortname addrs) wl-addrbook-alist))
518 (insert shortname ":\t" addrs))
520 (setq addrsl (wl-address-parse-address-list addrs))
521 (setq wl-addrbook-alist
522 (cons (list shortname addrsl nickname name) wl-addrbook-alist))
523 (if (null shortname) (setq shortname "*"))
524 (if (and nickname (string-match "^[^\" \t]+[ \t]+.*$" nickname))
525 (setq nickname (concat "\"" nickname "\"")))
526 (if (and name (string-match "^[^\" \t]+[ \t]+.*$" name))
527 (setq name (concat "\"" name "\"")))
529 (insert shortname "\t" addrs "\t" (or nickname "*") "\t" name)
531 (insert shortname "\t" addrs "\t" nickname)
532 (insert shortname "\t" addrs)))))
534 (insert "\t#" comments "\n")
537 (wl-addrbook-make-hashtb)
541 (message "Shortname is already used. Change Shortname.")
542 (wl-addrbook-kill 'no-msg)
543 (message "Registered to Addrbook.")))))
545 (defun wl-addrbook-kill (&optional no-msg)
546 "Kill Addrbook mode."
548 (kill-buffer (current-buffer))
549 (or no-msg (message "Not registered.")))
551 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
553 ;;; Show nick name of Addrbook in summary.
556 (defsubst wl-addrbook-get-names (names)
561 (or (wl-addrbook-nickname-get
562 (wl-address-header-extract-address name))
563 (and (setq addrs (std11-extract-address-components name))
564 (or (car addrs) (cadr addrs))))))
565 (wl-parse-addresses names)
568 (eval-when-compile (defvar-maybe entity nil)) ; silence byte compiler.
569 (defun wl-summary-addrbook-from (from)
570 "A candidate for wl-summary-from-function.
571 Show destination in summary matched by `wl-summary-show-dest-folder-regexp'.
572 And use Addrbook for get user name."
573 (let ((fromaddr (wl-address-header-extract-address from))
576 (and (eq major-mode 'wl-summary-mode)
577 (string-match wl-summary-showto-folder-regexp
578 wl-summary-buffer-folder-name)
579 (wl-address-user-mail-address-p fromaddr)
580 (cond ((setq dest (elmo-msgdb-overview-entity-get-to entity))
581 (concat "To:" (eword-decode-string (wl-addrbook-get-names dest))))
582 ((setq dest (elmo-msgdb-overview-entity-get-extra-field
583 entity "newsgroups"))
584 (concat "Ng:" dest))))
585 (wl-addrbook-nickname-get fromaddr)
588 (provide 'wl-addrbook)
590 ;;; Copyright Notice:
592 ;; Copyright (C) 1999-2001 Mew developing team.
593 ;; Copyright (C) 2001 Masahiro Murata <muse@ba2.so-net.ne.jp>
594 ;; All rights reserved.
596 ;; Redistribution and use in source and binary forms, with or without
597 ;; modification, are permitted provided that the following conditions
600 ;; 1. Redistributions of source code must retain the above copyright
601 ;; notice, this list of conditions and the following disclaimer.
602 ;; 2. Redistributions in binary form must reproduce the above copyright
603 ;; notice, this list of conditions and the following disclaimer in the
604 ;; documentation and/or other materials provided with the distribution.
605 ;; 3. Neither the name of the team nor the names of its contributors
606 ;; may be used to endorse or promote products derived from this software
607 ;; without specific prior written permission.
609 ;; THIS SOFTWARE IS PROVIDED BY THE TEAM AND CONTRIBUTORS ``AS IS'' AND
610 ;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
611 ;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
612 ;; PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE TEAM OR CONTRIBUTORS BE
613 ;; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
614 ;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
615 ;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
616 ;; BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
617 ;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
618 ;; OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
619 ;; IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
621 ;;; wl-addrbook.el ends here