(latexenc-find-file-coding-system): Don't inherit the EOL part of the
[emacs.git] / lisp / net / eudc.el
blobf234ab059d5925cbd09e6a5fb6f8ea9a17e0b651
1 ;;; eudc.el --- Emacs Unified Directory Client
3 ;; Copyright (C) 1998, 1999, 2000, 2002, 2005 Free Software Foundation, Inc.
5 ;; Author: Oscar Figueiredo <oscar@cpe.fr>
6 ;; Maintainer: Pavel Janík <Pavel@Janik.cz>
7 ;; Keywords: comm
9 ;; This file is part of GNU Emacs.
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
26 ;;; Commentary:
27 ;; This package provides a common interface to query directory servers using
28 ;; different protocols such as LDAP, CCSO PH/QI or BBDB. Queries can be
29 ;; made through an interactive form or inline. Inline query strings in
30 ;; buffers are expanded with appropriately formatted query results
31 ;; (especially used to expand email addresses in message buffers). EUDC
32 ;; also interfaces with the BBDB package to let you register query results
33 ;; into your own BBDB database.
35 ;;; Usage:
36 ;; EUDC comes with an extensive documentation, please refer to it.
38 ;; The main entry points of EUDC are:
39 ;; `eudc-query-form': Query a directory server from a query form
40 ;; `eudc-expand-inline': Query a directory server for the e-mail address
41 ;; of the name before cursor and insert it in the
42 ;; buffer
43 ;; `eudc-get-phone': Get a phone number from a directory server
44 ;; `eudc-get-email': Get an e-mail address from a directory server
45 ;; `eudc-customize': Customize various aspects of EUDC
47 ;;; Code:
49 (require 'wid-edit)
51 (eval-and-compile
52 (if (not (fboundp 'make-overlay))
53 (require 'overlay))
54 (if (not (fboundp 'unless))
55 (require 'cl)))
57 (unless (fboundp 'custom-menu-create)
58 (autoload 'custom-menu-create "cus-edit"))
60 (require 'eudc-vars)
64 ;;{{{ Internal cooking
66 ;;{{{ Internal variables and compatibility tricks
68 (defconst eudc-xemacs-p (string-match "XEmacs" emacs-version))
69 (defconst eudc-emacs-p (not eudc-xemacs-p))
70 (defconst eudc-xemacs-mule-p (and eudc-xemacs-p
71 (featurep 'mule)))
72 (defconst eudc-emacs-mule-p (and eudc-emacs-p
73 (featurep 'mule)))
75 (defvar eudc-form-widget-list nil)
76 (defvar eudc-mode-map nil)
78 ;; List of known servers
79 ;; Alist of (SERVER . PROTOCOL)
80 (defvar eudc-server-hotlist nil)
82 ;; List of variables that have server- or protocol-local bindings
83 (defvar eudc-local-vars nil)
85 ;; Protocol local. Query function
86 (defvar eudc-query-function nil)
88 ;; Protocol local. A function that retrieves a list of valid attribute names
89 (defvar eudc-list-attributes-function nil)
91 ;; Protocol local. A mapping between EUDC attribute names and corresponding
92 ;; protocol specific names. The following names are defined by EUDC and may be
93 ;; included in that list: `name' , `firstname', `email', `phone'
94 (defvar eudc-protocol-attributes-translation-alist nil)
96 ;; Protocol local. Mapping between protocol attribute names and BBDB field
97 ;; names
98 (defvar eudc-bbdb-conversion-alist nil)
100 ;; Protocol/Server local. Hook called upon switching to that server
101 (defvar eudc-switch-to-server-hook nil)
103 ;; Protocol/Server local. Hook called upon switching from that server
104 (defvar eudc-switch-from-server-hook nil)
106 ;; Protocol local. Whether the protocol supports queries with no specified
107 ;; attribute name
108 (defvar eudc-protocol-has-default-query-attributes nil)
110 (defun eudc-cadr (obj)
111 (car (cdr obj)))
113 (defun eudc-cdar (obj)
114 (cdr (car obj)))
116 (defun eudc-caar (obj)
117 (car (car obj)))
119 (defun eudc-cdaar (obj)
120 (cdr (car (car obj))))
122 (defun eudc-plist-member (plist prop)
123 "Return t if PROP has a value specified in PLIST."
124 (if (not (= 0 (% (length plist) 2)))
125 (error "Malformed plist"))
126 (catch 'found
127 (while plist
128 (if (eq prop (car plist))
129 (throw 'found t))
130 (setq plist (cdr (cdr plist))))
131 nil))
133 ;; Emacs' plist-get lacks third parameter
134 (defun eudc-plist-get (plist prop &optional default)
135 "Extract a value from a property list.
136 PLIST is a property list, which is a list of the form
137 \(PROP1 VALUE1 PROP2 VALUE2...). This function returns the value
138 corresponding to the given PROP, or DEFAULT if PROP is not
139 one of the properties on the list."
140 (if (eudc-plist-member plist prop)
141 (plist-get plist prop)
142 default))
144 (defun eudc-lax-plist-get (plist prop &optional default)
145 "Extract a value from a lax property list.
147 PLIST is a lax property list, which is a list of the form (PROP1
148 VALUE1 PROP2 VALUE2...), where comparisons between properties are done
149 using `equal' instead of `eq'. This function returns the value
150 corresponding to PROP, or DEFAULT if PROP is not one of the
151 properties on the list."
152 (if (not (= 0 (% (length plist) 2)))
153 (error "Malformed plist"))
154 (catch 'found
155 (while plist
156 (if (equal prop (car plist))
157 (throw 'found (car (cdr plist))))
158 (setq plist (cdr (cdr plist))))
159 default))
161 (if (not (fboundp 'split-string))
162 (defun split-string (string &optional pattern)
163 "Return a list of substrings of STRING which are separated by PATTERN.
164 If PATTERN is omitted, it defaults to \"[ \\f\\t\\n\\r\\v]+\"."
165 (or pattern
166 (setq pattern "[ \f\t\n\r\v]+"))
167 (let (parts (start 0))
168 (when (string-match pattern string 0)
169 (if (> (match-beginning 0) 0)
170 (setq parts (cons (substring string 0 (match-beginning 0)) nil)))
171 (setq start (match-end 0))
172 (while (and (string-match pattern string start)
173 (> (match-end 0) start))
174 (setq parts (cons (substring string start (match-beginning 0)) parts)
175 start (match-end 0))))
176 (nreverse (if (< start (length string))
177 (cons (substring string start) parts)
178 parts)))))
180 (defun eudc-replace-in-string (str regexp newtext)
181 "Replace all matches in STR for REGEXP with NEWTEXT.
182 Value is the new string."
183 (let ((rtn-str "")
184 (start 0)
185 match prev-start)
186 (while (setq match (string-match regexp str start))
187 (setq prev-start start
188 start (match-end 0)
189 rtn-str
190 (concat rtn-str
191 (substring str prev-start match)
192 newtext)))
193 (concat rtn-str (substring str start))))
195 ;;}}}
197 ;;{{{ Server and Protocol Variable Routines
199 (defun eudc-server-local-variable-p (var)
200 "Return non-nil if VAR has server-local bindings."
201 (eudc-plist-member (get var 'eudc-locals) 'server))
203 (defun eudc-protocol-local-variable-p (var)
204 "Return non-nil if VAR has protocol-local bindings."
205 (eudc-plist-member (get var 'eudc-locals) 'protocol))
207 (defun eudc-default-set (var val)
208 "Set the EUDC default value of VAR to VAL.
209 The current binding of VAR is not changed."
210 (put var 'eudc-locals
211 (plist-put (get var 'eudc-locals) 'default val))
212 (add-to-list 'eudc-local-vars var))
214 (defun eudc-protocol-set (var val &optional protocol)
215 "Set the PROTOCOL-local binding of VAR to VAL.
216 If omitted PROTOCOL defaults to the current value of `eudc-protocol'.
217 The current binding of VAR is changed only if PROTOCOL is omitted."
218 (if (eq 'unbound (eudc-variable-default-value var))
219 (eudc-default-set var (symbol-value var)))
220 (let* ((eudc-locals (get var 'eudc-locals))
221 (protocol-locals (eudc-plist-get eudc-locals 'protocol)))
222 (setq protocol-locals (plist-put protocol-locals (or protocol
223 eudc-protocol) val))
224 (setq eudc-locals
225 (plist-put eudc-locals 'protocol protocol-locals))
226 (put var 'eudc-locals eudc-locals)
227 (add-to-list 'eudc-local-vars var)
228 (unless protocol
229 (eudc-update-variable var))))
231 (defun eudc-server-set (var val &optional server)
232 "Set the SERVER-local binding of VAR to VAL.
233 If omitted SERVER defaults to the current value of `eudc-server'.
234 The current binding of VAR is changed only if SERVER is omitted."
235 (if (eq 'unbound (eudc-variable-default-value var))
236 (eudc-default-set var (symbol-value var)))
237 (let* ((eudc-locals (get var 'eudc-locals))
238 (server-locals (eudc-plist-get eudc-locals 'server)))
239 (setq server-locals (plist-put server-locals (or server
240 eudc-server) val))
241 (setq eudc-locals
242 (plist-put eudc-locals 'server server-locals))
243 (put var 'eudc-locals eudc-locals)
244 (add-to-list 'eudc-local-vars var)
245 (unless server
246 (eudc-update-variable var))))
249 (defun eudc-set (var val)
250 "Set the most local (server, protocol or default) binding of VAR to VAL.
251 The current binding of VAR is also set to VAL"
252 (cond
253 ((not (eq 'unbound (eudc-variable-server-value var)))
254 (eudc-server-set var val))
255 ((not (eq 'unbound (eudc-variable-protocol-value var)))
256 (eudc-protocol-set var val))
258 (eudc-default-set var val)))
259 (set var val))
261 (defun eudc-variable-default-value (var)
262 "Return the default binding of VAR.
263 Return `unbound' if VAR has no EUDC default value."
264 (let ((eudc-locals (get var 'eudc-locals)))
265 (if (and (boundp var)
266 eudc-locals)
267 (eudc-plist-get eudc-locals 'default 'unbound)
268 'unbound)))
270 (defun eudc-variable-protocol-value (var &optional protocol)
271 "Return the value of VAR local to PROTOCOL.
272 Return `unbound' if VAR has no value local to PROTOCOL.
273 PROTOCOL defaults to `eudc-protocol'"
274 (let* ((eudc-locals (get var 'eudc-locals))
275 protocol-locals)
276 (if (not (and (boundp var)
277 eudc-locals
278 (eudc-plist-member eudc-locals 'protocol)))
279 'unbound
280 (setq protocol-locals (eudc-plist-get eudc-locals 'protocol))
281 (eudc-lax-plist-get protocol-locals
282 (or protocol
283 eudc-protocol) 'unbound))))
285 (defun eudc-variable-server-value (var &optional server)
286 "Return the value of VAR local to SERVER.
287 Return `unbound' if VAR has no value local to SERVER.
288 SERVER defaults to `eudc-server'"
289 (let* ((eudc-locals (get var 'eudc-locals))
290 server-locals)
291 (if (not (and (boundp var)
292 eudc-locals
293 (eudc-plist-member eudc-locals 'server)))
294 'unbound
295 (setq server-locals (eudc-plist-get eudc-locals 'server))
296 (eudc-lax-plist-get server-locals
297 (or server
298 eudc-server) 'unbound))))
300 (defun eudc-update-variable (var)
301 "Set the value of VAR according to its locals.
302 If the VAR has a server- or protocol-local value corresponding
303 to the current `eudc-server' and `eudc-protocol' then it is set
304 accordingly. Otherwise it is set to its EUDC default binding"
305 (let (val)
306 (cond
307 ((not (eq 'unbound (setq val (eudc-variable-server-value var))))
308 (set var val))
309 ((not (eq 'unbound (setq val (eudc-variable-protocol-value var))))
310 (set var val))
311 ((not (eq 'unbound (setq val (eudc-variable-default-value var))))
312 (set var val)))))
314 (defun eudc-update-local-variables ()
315 "Update all EUDC variables according to their local settings."
316 (interactive)
317 (mapcar 'eudc-update-variable eudc-local-vars))
319 (eudc-default-set 'eudc-query-function nil)
320 (eudc-default-set 'eudc-list-attributes-function nil)
321 (eudc-default-set 'eudc-protocol-attributes-translation-alist nil)
322 (eudc-default-set 'eudc-bbdb-conversion-alist nil)
323 (eudc-default-set 'eudc-switch-to-server-hook nil)
324 (eudc-default-set 'eudc-switch-from-server-hook nil)
325 (eudc-default-set 'eudc-protocol-has-default-query-attributes nil)
326 (eudc-default-set 'eudc-attribute-display-method-alist nil)
328 ;;}}}
331 ;; Add PROTOCOL to the list of supported protocols
332 (defun eudc-register-protocol (protocol)
333 (unless (memq protocol eudc-supported-protocols)
334 (setq eudc-supported-protocols
335 (cons protocol eudc-supported-protocols))
336 (put 'eudc-protocol 'custom-type
337 `(choice :menu-tag "Protocol"
338 ,@(mapcar (lambda (s)
339 (list 'string ':tag (symbol-name s)))
340 eudc-supported-protocols))))
341 (or (memq protocol eudc-known-protocols)
342 (setq eudc-known-protocols
343 (cons protocol eudc-known-protocols))))
346 (defun eudc-translate-query (query)
347 "Translate attribute names of QUERY.
348 The translation is done according to
349 `eudc-protocol-attributes-translation-alist'."
350 (if eudc-protocol-attributes-translation-alist
351 (mapcar '(lambda (attribute)
352 (let ((trans (assq (car attribute)
353 (symbol-value eudc-protocol-attributes-translation-alist))))
354 (if trans
355 (cons (cdr trans) (cdr attribute))
356 attribute)))
357 query)
358 query))
360 (defun eudc-translate-attribute-list (list)
361 "Translate a list of attribute names LIST.
362 The translation is done according to
363 `eudc-protocol-attributes-translation-alist'."
364 (if eudc-protocol-attributes-translation-alist
365 (let (trans)
366 (mapcar '(lambda (attribute)
367 (setq trans (assq attribute
368 (symbol-value eudc-protocol-attributes-translation-alist)))
369 (if trans
370 (cdr trans)
371 attribute))
372 list))
373 list))
375 (defun eudc-select (choices beg end)
376 "Choose one from CHOICES using a completion.
377 BEG and END delimit the text which is to be replaced."
378 (let ((replacement))
379 (setq replacement
380 (completing-read "Multiple matches found; choose one:"
381 (mapcar 'list choices)))
382 (delete-region beg end)
383 (insert replacement)))
385 (defun eudc-query (query &optional return-attributes no-translation)
386 "Query the current directory server with QUERY.
387 QUERY is a list of cons cells (ATTR . VALUE) where ATTR is an attribute
388 name and VALUE the corresponding value.
389 If NO-TRANSLATION is non-nil, ATTR is translated according to
390 `eudc-protocol-attributes-translation-alist'.
391 RETURN-ATTRIBUTES is a list of attributes to return defaulting to
392 `eudc-default-return-attributes'."
393 (unless eudc-query-function
394 (error "Don't know how to perform the query"))
395 (if no-translation
396 (funcall eudc-query-function query (or return-attributes
397 eudc-default-return-attributes))
399 (funcall eudc-query-function
400 (eudc-translate-query query)
401 (cond
402 (return-attributes
403 (eudc-translate-attribute-list return-attributes))
404 ((listp eudc-default-return-attributes)
405 (eudc-translate-attribute-list eudc-default-return-attributes))
407 eudc-default-return-attributes)))))
409 (defun eudc-format-attribute-name-for-display (attribute)
410 "Format a directory attribute name for display.
411 ATTRIBUTE is looked up in `eudc-user-attribute-names-alist' and replaced
412 by the corresponding user name if any. Otherwise it is capitalized and
413 underscore characters are replaced by spaces."
414 (let ((match (assq attribute eudc-user-attribute-names-alist)))
415 (if match
416 (cdr match)
417 (capitalize
418 (mapconcat 'identity
419 (split-string (symbol-name attribute) "_")
420 " ")))))
422 (defun eudc-print-attribute-value (field)
423 "Insert the value of the directory FIELD at point.
424 The directory attribute name in car of FIELD is looked up in
425 `eudc-attribute-display-method-alist' and the corresponding method,
426 if any, is called to print the value in cdr of FIELD."
427 (let ((match (assoc (downcase (car field))
428 eudc-attribute-display-method-alist))
429 (col (current-column))
430 (val (cdr field)))
431 (if match
432 (progn
433 (eval (list (cdr match) val))
434 (insert "\n"))
435 (mapcar
436 (function
437 (lambda (val-elem)
438 (indent-to col)
439 (insert val-elem "\n")))
440 (cond
441 ((listp val) val)
442 ((stringp val) (split-string val "\n"))
443 ((null val) '(""))
444 (t (list val)))))))
446 (defun eudc-print-record-field (field column-width)
447 "Print the record field FIELD.
448 FIELD is a list (ATTR VALUE1 VALUE2 ...) or cons-cell (ATTR . VAL)
449 COLUMN-WIDTH is the width of the first display column containing the
450 attribute name ATTR."
451 (let ((field-beg (point)))
452 ;; The record field that is passed to this function has already been processed
453 ;; by `eudc-format-attribute-name-for-display' so we don't need to call it
454 ;; again to display the attribute name
455 (insert (format (concat "%" (int-to-string column-width) "s: ")
456 (car field)))
457 (put-text-property field-beg (point) 'face 'bold)
458 (indent-to (+ 2 column-width))
459 (eudc-print-attribute-value field)))
461 (defun eudc-display-records (records &optional raw-attr-names)
462 "Display the record list RECORDS in a formatted buffer.
463 If RAW-ATTR-NAMES is non-nil, the raw attribute names are displayed
464 otherwise they are formatted according to `eudc-user-attribute-names-alist'."
465 (let (inhibit-read-only
466 precords
467 (width 0)
469 first-record
470 attribute-name)
471 (with-output-to-temp-buffer "*Directory Query Results*"
472 (with-current-buffer standard-output
473 (setq buffer-read-only t)
474 (setq inhibit-read-only t)
475 (erase-buffer)
476 (insert "Directory Query Result\n")
477 (insert "======================\n\n\n")
478 (if (null records)
479 (insert "No match found.\n"
480 (if eudc-strict-return-matches
481 "Try setting `eudc-strict-return-matches' to nil or change `eudc-default-return-attributes'.\n"
482 ""))
483 ;; Replace field names with user names, compute max width
484 (setq precords
485 (mapcar
486 (function
487 (lambda (record)
488 (mapcar
489 (function
490 (lambda (field)
491 (setq attribute-name
492 (if raw-attr-names
493 (symbol-name (car field))
494 (eudc-format-attribute-name-for-display (car field))))
495 (if (> (length attribute-name) width)
496 (setq width (length attribute-name)))
497 (cons attribute-name (cdr field))))
498 record)))
499 records))
500 ;; Display the records
501 (setq first-record (point))
502 (mapcar
503 (function
504 (lambda (record)
505 (setq beg (point))
506 ;; Map over the record fields to print the attribute/value pairs
507 (mapcar (function
508 (lambda (field)
509 (eudc-print-record-field field width)))
510 record)
511 ;; Store the record internal format in some convenient place
512 (overlay-put (make-overlay beg (point))
513 'eudc-record
514 (car records))
515 (setq records (cdr records))
516 (insert "\n")))
517 precords))
518 (insert "\n")
519 (widget-create 'push-button
520 :notify (lambda (&rest ignore)
521 (eudc-query-form))
522 "New query")
523 (widget-insert " ")
524 (widget-create 'push-button
525 :notify (lambda (&rest ignore)
526 (kill-this-buffer))
527 "Quit")
528 (eudc-mode)
529 (widget-setup)
530 (if first-record
531 (goto-char first-record))))))
533 (defun eudc-process-form ()
534 "Process the query form in current buffer and display the results."
535 (let (query-alist
536 value)
537 (if (not (and (boundp 'eudc-form-widget-list)
538 eudc-form-widget-list))
539 (error "Not in a directory query form buffer")
540 (mapcar (function
541 (lambda (wid-field)
542 (setq value (widget-value (cdr wid-field)))
543 (if (not (string= value ""))
544 (setq query-alist (cons (cons (car wid-field) value)
545 query-alist)))))
546 eudc-form-widget-list)
547 (kill-buffer (current-buffer))
548 (eudc-display-records (eudc-query query-alist) eudc-use-raw-directory-names))))
551 (defun eudc-filter-duplicate-attributes (record)
552 "Filter RECORD according to `eudc-duplicate-attribute-handling-method'."
553 (let ((rec record)
554 unique
555 duplicates
556 result)
558 ;; Search for multiple records
559 (while (and rec
560 (not (listp (eudc-cdar rec))))
561 (setq rec (cdr rec)))
563 (if (null (eudc-cdar rec))
564 (list record) ; No duplicate attrs in this record
565 (mapcar (function
566 (lambda (field)
567 (if (listp (cdr field))
568 (setq duplicates (cons field duplicates))
569 (setq unique (cons field unique)))))
570 record)
571 (setq result (list unique))
572 ;; Map over the record fields that have multiple values
573 (mapcar
574 (function
575 (lambda (field)
576 (let ((method (if (consp eudc-duplicate-attribute-handling-method)
577 (cdr
578 (assq
580 (car
581 (rassq
582 (car field)
583 (symbol-value
584 eudc-protocol-attributes-translation-alist)))
585 (car field))
586 eudc-duplicate-attribute-handling-method))
587 eudc-duplicate-attribute-handling-method)))
588 (cond
589 ((or (null method) (eq 'list method))
590 (setq result
591 (eudc-add-field-to-records field result)))
592 ((eq 'first method)
593 (setq result
594 (eudc-add-field-to-records (cons (car field)
595 (eudc-cadr field))
596 result)))
597 ((eq 'concat method)
598 (setq result
599 (eudc-add-field-to-records (cons (car field)
600 (mapconcat
601 'identity
602 (cdr field)
603 "\n")) result)))
604 ((eq 'duplicate method)
605 (setq result
606 (eudc-distribute-field-on-records field result)))))))
607 duplicates)
608 result)))
610 (defun eudc-filter-partial-records (records attrs)
611 "Eliminate records that do not contain all ATTRS from RECORDS."
612 (delq nil
613 (mapcar
614 (function
615 (lambda (rec)
616 (if (eval (cons 'and
617 (mapcar
618 (function
619 (lambda (attr)
620 (consp (assq attr rec))))
621 attrs)))
622 rec)))
623 records)))
625 (defun eudc-add-field-to-records (field records)
626 "Add FIELD to each individual record in RECORDS and return the resulting list."
627 (mapcar (function
628 (lambda (r)
629 (cons field r)))
630 records))
632 (defun eudc-distribute-field-on-records (field records)
633 "Duplicate each individual record in RECORDS according to value of FIELD.
634 Each copy is added a new field containing one of the values of FIELD."
635 (let (result
636 (values (cdr field)))
637 ;; Uniquify values first
638 (while values
639 (setcdr values (delete (car values) (cdr values)))
640 (setq values (cdr values)))
641 (mapcar
642 (function
643 (lambda (value)
644 (let ((result-list (copy-sequence records)))
645 (setq result-list (eudc-add-field-to-records
646 (cons (car field) value)
647 result-list))
648 (setq result (append result-list result))
650 (cdr field))
651 result))
654 (defun eudc-mode ()
655 "Major mode used in buffers displaying the results of directory queries.
656 There is no sense in calling this command from a buffer other than
657 one containing the results of a directory query.
659 These are the special commands of EUDC mode:
660 q -- Kill this buffer.
661 f -- Display a form to query the current directory server.
662 n -- Move to next record.
663 p -- Move to previous record.
664 b -- Insert record at point into the BBDB database."
665 (interactive)
666 (kill-all-local-variables)
667 (setq major-mode 'eudc-mode)
668 (setq mode-name "EUDC")
669 (use-local-map eudc-mode-map)
670 (if eudc-emacs-p
671 (easy-menu-define eudc-emacs-menu eudc-mode-map "" (eudc-menu))
672 (setq mode-popup-menu (eudc-menu)))
673 (run-mode-hooks 'eudc-mode-hook))
675 ;;}}}
677 ;;{{{ High-level interfaces (interactive functions)
679 (defun eudc-customize ()
680 "Customize the EUDC package."
681 (interactive)
682 (customize-group 'eudc))
684 ;;;###autoload
685 (defun eudc-set-server (server protocol &optional no-save)
686 "Set the directory server to SERVER using PROTOCOL.
687 Unless NO-SAVE is non-nil, the server is saved as the default
688 server for future sessions."
689 (interactive (list
690 (read-from-minibuffer "Directory Server: ")
691 (intern (completing-read "Protocol: "
692 (mapcar '(lambda (elt)
693 (cons (symbol-name elt)
694 elt))
695 eudc-known-protocols)))))
696 (unless (or (member protocol
697 eudc-supported-protocols)
698 (load (concat "eudcb-" (symbol-name protocol)) t))
699 (error "Unsupported protocol: %s" protocol))
700 (run-hooks 'eudc-switch-from-server-hook)
701 (setq eudc-protocol protocol)
702 (setq eudc-server server)
703 (eudc-update-local-variables)
704 (run-hooks 'eudc-switch-to-server-hook)
705 (if (interactive-p)
706 (message "Current directory server is now %s (%s)" eudc-server eudc-protocol))
707 (if (null no-save)
708 (eudc-save-options)))
710 ;;;###autoload
711 (defun eudc-get-email (name &optional error)
712 "Get the email field of NAME from the directory server.
713 If ERROR is non-nil, report an error if there is none."
714 (interactive "sName: \np")
715 (or eudc-server
716 (call-interactively 'eudc-set-server))
717 (let ((result (eudc-query (list (cons 'name name)) '(email)))
718 email)
719 (if (null (cdr result))
720 (setq email (eudc-cdaar result))
721 (error "Multiple match--use the query form"))
722 (if error
723 (if email
724 (message "%s" email)
725 (error "No record matching %s" name)))
726 email))
728 ;;;###autoload
729 (defun eudc-get-phone (name &optional error)
730 "Get the phone field of NAME from the directory server.
731 If ERROR is non-nil, report an error if there is none."
732 (interactive "sName: \np")
733 (or eudc-server
734 (call-interactively 'eudc-set-server))
735 (let ((result (eudc-query (list (cons 'name name)) '(phone)))
736 phone)
737 (if (null (cdr result))
738 (setq phone (eudc-cdaar result))
739 (error "Multiple match--use the query form"))
740 (if error
741 (if phone
742 (message "%s" phone)
743 (error "No record matching %s" name)))
744 phone))
746 (defun eudc-get-attribute-list ()
747 "Return a list of valid attributes for the current server.
748 When called interactively the list is formatted in a dedicated buffer
749 otherwise a list of symbols is returned."
750 (interactive)
751 (if eudc-list-attributes-function
752 (let ((entries (funcall eudc-list-attributes-function (interactive-p))))
753 (if entries
754 (if (interactive-p)
755 (eudc-display-records entries t)
756 entries)))
757 (error "The %s protocol has no support for listing attributes" eudc-protocol)))
759 (defun eudc-format-query (words format)
760 "Use FORMAT to build a EUDC query from WORDS."
761 (let (query
762 query-alist
763 key val cell)
764 (if format
765 (progn
766 (while (and words format)
767 (setq query-alist (cons (cons (car format) (car words))
768 query-alist))
769 (setq words (cdr words)
770 format (cdr format)))
771 ;; If the same attribute appears more than once, merge
772 ;; the corresponding values
773 (setq query-alist (nreverse query-alist))
774 (while query-alist
775 (setq key (eudc-caar query-alist)
776 val (eudc-cdar query-alist)
777 cell (assq key query))
778 (if cell
779 (setcdr cell (concat (cdr cell) " " val))
780 (setq query (cons (car query-alist) query)))
781 (setq query-alist (cdr query-alist)))
782 query)
783 (if eudc-protocol-has-default-query-attributes
784 (mapconcat 'identity words " ")
785 (list (cons 'name (mapconcat 'identity words " ")))))))
787 (defun eudc-extract-n-word-formats (format-list n)
788 "Extract a list of N-long formats from FORMAT-LIST.
789 If none try N - 1 and so forth."
790 (let (formats)
791 (while (and (null formats)
792 (> n 0))
793 (setq formats
794 (delq nil
795 (mapcar '(lambda (format)
796 (if (= n
797 (length format))
798 format
799 nil))
800 format-list)))
801 (setq n (1- n)))
802 formats))
805 ;;;###autoload
806 (defun eudc-expand-inline (&optional replace)
807 "Query the directory server, and expand the query string before point.
808 The query string consists of the buffer substring from the point back to
809 the preceding comma, colon or beginning of line.
810 The variable `eudc-inline-query-format' controls how to associate the
811 individual inline query words with directory attribute names.
812 After querying the server for the given string, the expansion specified by
813 `eudc-inline-expansion-format' is inserted in the buffer at point.
814 If REPLACE is non-nil, then this expansion replaces the name in the buffer.
815 `eudc-expansion-overwrites-query' being non-nil inverts the meaning of REPLACE.
816 Multiple servers can be tried with the same query until one finds a match,
817 see `eudc-inline-expansion-servers'"
818 (interactive)
819 (if (memq eudc-inline-expansion-servers
820 '(current-server server-then-hotlist))
821 (or eudc-server
822 (call-interactively 'eudc-set-server))
823 (or eudc-server-hotlist
824 (error "No server in the hotlist")))
825 (let* ((end (point))
826 (beg (save-excursion
827 (if (re-search-backward "\\([:,]\\|^\\)[ \t]*"
828 (save-excursion
829 (beginning-of-line)
830 (point))
831 'move)
832 (goto-char (match-end 0)))
833 (point)))
834 (query-words (split-string (buffer-substring beg end) "[ \t]+"))
835 query-formats
836 response
837 response-string
838 response-strings
839 (eudc-former-server eudc-server)
840 (eudc-former-protocol eudc-protocol)
841 servers)
843 ;; Prepare the list of servers to query
844 (setq servers (copy-sequence eudc-server-hotlist))
845 (setq servers
846 (cond
847 ((eq eudc-inline-expansion-servers 'hotlist)
848 eudc-server-hotlist)
849 ((eq eudc-inline-expansion-servers 'server-then-hotlist)
850 (cons (cons eudc-server eudc-protocol)
851 (delete (cons eudc-server eudc-protocol) servers)))
852 ((eq eudc-inline-expansion-servers 'current-server)
853 (list (cons eudc-server eudc-protocol)))
855 (error "Wrong value for `eudc-inline-expansion-servers': %S"
856 eudc-inline-expansion-servers))))
857 (if (and eudc-max-servers-to-query
858 (> (length servers) eudc-max-servers-to-query))
859 (setcdr (nthcdr (1- eudc-max-servers-to-query) servers) nil))
861 (condition-case signal
862 (progn
863 (setq response
864 (catch 'found
865 ;; Loop on the servers
866 (while servers
867 (eudc-set-server (eudc-caar servers) (eudc-cdar servers) t)
869 ;; Determine which formats apply in the query-format list
870 (setq query-formats
872 (eudc-extract-n-word-formats eudc-inline-query-format
873 (length query-words))
874 (if (null eudc-protocol-has-default-query-attributes)
875 '(name))))
877 ;; Loop on query-formats
878 (while query-formats
879 (setq response
880 (eudc-query
881 (eudc-format-query query-words (car query-formats))
882 (eudc-translate-attribute-list
883 (cdr eudc-inline-expansion-format))))
884 (if response
885 (throw 'found response))
886 (setq query-formats (cdr query-formats)))
887 (setq servers (cdr servers)))
888 ;; No more servers to try... no match found
889 nil))
892 (if (null response)
893 (error "No match")
895 ;; Process response through eudc-inline-expansion-format
896 (while response
897 (setq response-string (apply 'format
898 (car eudc-inline-expansion-format)
899 (mapcar (function
900 (lambda (field)
901 (or (cdr (assq field (car response)))
902 "")))
903 (eudc-translate-attribute-list
904 (cdr eudc-inline-expansion-format)))))
905 (if (> (length response-string) 0)
906 (setq response-strings
907 (cons response-string response-strings)))
908 (setq response (cdr response)))
910 (if (or
911 (and replace (not eudc-expansion-overwrites-query))
912 (and (not replace) eudc-expansion-overwrites-query))
913 (kill-ring-save beg end))
914 (cond
915 ((or (= (length response-strings) 1)
916 (null eudc-multiple-match-handling-method)
917 (eq eudc-multiple-match-handling-method 'first))
918 (delete-region beg end)
919 (insert (car response-strings)))
920 ((eq eudc-multiple-match-handling-method 'select)
921 (eudc-select response-strings beg end))
922 ((eq eudc-multiple-match-handling-method 'all)
923 (insert (mapconcat 'identity response-strings ", ")))
924 ((eq eudc-multiple-match-handling-method 'abort)
925 (error "There is more than one match for the query"))))
926 (or (and (equal eudc-server eudc-former-server)
927 (equal eudc-protocol eudc-former-protocol))
928 (eudc-set-server eudc-former-server eudc-former-protocol t)))
930 (or (and (equal eudc-server eudc-former-server)
931 (equal eudc-protocol eudc-former-protocol))
932 (eudc-set-server eudc-former-server eudc-former-protocol t))
933 (signal (car signal) (cdr signal))))))
935 ;;;###autoload
936 (defun eudc-query-form (&optional get-fields-from-server)
937 "Display a form to query the directory server.
938 If given a non-nil argument GET-FIELDS-FROM-SERVER, the function first
939 queries the server for the existing fields and displays a corresponding form."
940 (interactive "P")
941 (let ((fields (or (and get-fields-from-server
942 (eudc-get-attribute-list))
943 eudc-query-form-attributes))
944 (buffer (get-buffer-create "*Directory Query Form*"))
945 prompts
946 widget
947 (width 0)
948 inhibit-read-only
950 (switch-to-buffer buffer)
951 (setq inhibit-read-only t)
952 (erase-buffer)
953 (kill-all-local-variables)
954 (make-local-variable 'eudc-form-widget-list)
955 (widget-insert "Directory Query Form\n")
956 (widget-insert "====================\n\n")
957 (widget-insert "Current server is: " (or eudc-server
958 (progn
959 (call-interactively 'eudc-set-server)
960 eudc-server))
961 "\n")
962 (widget-insert "Protocol : " (symbol-name eudc-protocol) "\n")
963 ;; Build the list of prompts
964 (setq prompts (if eudc-use-raw-directory-names
965 (mapcar 'symbol-name (eudc-translate-attribute-list fields))
966 (mapcar (function
967 (lambda (field)
968 (or (and (assq field eudc-user-attribute-names-alist)
969 (cdr (assq field eudc-user-attribute-names-alist)))
970 (capitalize (symbol-name field)))))
971 fields)))
972 ;; Loop over prompt strings to find the longest one
973 (mapcar (function
974 (lambda (prompt)
975 (if (> (length prompt) width)
976 (setq width (length prompt)))))
977 prompts)
978 ;; Insert the first widget out of the mapcar to leave the cursor
979 ;; in the first field
980 (widget-insert "\n\n" (format (concat "%" (int-to-string width) "s: ") (car prompts)))
981 (setq pt (point))
982 (setq widget (widget-create 'editable-field :size 15))
983 (setq eudc-form-widget-list (cons (cons (car fields) widget)
984 eudc-form-widget-list))
985 (setq fields (cdr fields))
986 (setq prompts (cdr prompts))
987 (mapcar (function
988 (lambda (field)
989 (widget-insert "\n\n" (format (concat "%" (int-to-string width) "s: ") (car prompts)))
990 (setq widget (widget-create 'editable-field
991 :size 15))
992 (setq eudc-form-widget-list (cons (cons field widget)
993 eudc-form-widget-list))
994 (setq prompts (cdr prompts))))
995 fields)
996 (widget-insert "\n\n")
997 (widget-create 'push-button
998 :notify (lambda (&rest ignore)
999 (eudc-process-form))
1000 "Query Server")
1001 (widget-insert " ")
1002 (widget-create 'push-button
1003 :notify (lambda (&rest ignore)
1004 (eudc-query-form))
1005 "Reset Form")
1006 (widget-insert " ")
1007 (widget-create 'push-button
1008 :notify (lambda (&rest ignore)
1009 (kill-this-buffer))
1010 "Quit")
1011 (goto-char pt)
1012 (use-local-map widget-keymap)
1013 (widget-setup))
1016 (defun eudc-bookmark-server (server protocol)
1017 "Add SERVER using PROTOCOL to the EUDC `servers' hotlist."
1018 (interactive "sDirectory server: \nsProtocol: ")
1019 (if (member (cons server protocol) eudc-server-hotlist)
1020 (error "%s:%s is already in the hotlist" protocol server)
1021 (setq eudc-server-hotlist (cons (cons server protocol) eudc-server-hotlist))
1022 (eudc-install-menu)
1023 (eudc-save-options)))
1025 (defun eudc-bookmark-current-server ()
1026 "Add current server to the EUDC `servers' hotlist."
1027 (interactive)
1028 (eudc-bookmark-server eudc-server eudc-protocol))
1030 (defun eudc-save-options ()
1031 "Save options to `eudc-options-file'."
1032 (interactive)
1033 (save-excursion
1034 (set-buffer (find-file-noselect eudc-options-file t))
1035 (goto-char (point-min))
1036 ;; delete the previous setq
1037 (let ((standard-output (current-buffer))
1038 provide-p
1039 set-hotlist-p
1040 set-server-p)
1041 (catch 'found
1042 (while t
1043 (let ((sexp (condition-case nil
1044 (read (current-buffer))
1045 (end-of-file (throw 'found nil)))))
1046 (if (listp sexp)
1047 (cond
1048 ((eq (car sexp) 'eudc-set-server)
1049 (delete-region (save-excursion
1050 (backward-sexp)
1051 (point))
1052 (point))
1053 (setq set-server-p t))
1054 ((and (eq (car sexp) 'setq)
1055 (eq (eudc-cadr sexp) 'eudc-server-hotlist))
1056 (delete-region (save-excursion
1057 (backward-sexp)
1058 (point))
1059 (point))
1060 (setq set-hotlist-p t))
1061 ((and (eq (car sexp) 'provide)
1062 (equal (eudc-cadr sexp) '(quote eudc-options-file)))
1063 (setq provide-p t)))
1064 (if (and provide-p
1065 set-hotlist-p
1066 set-server-p)
1067 (throw 'found t))))))
1068 (if (eq (point-min) (point-max))
1069 (princ ";; This file was automatically generated by eudc.el.\n\n"))
1070 (or provide-p
1071 (princ "(provide 'eudc-options-file)\n"))
1072 (or (bolp)
1073 (princ "\n"))
1074 (delete-blank-lines)
1075 (princ "(eudc-set-server ")
1076 (prin1 eudc-server)
1077 (princ " '")
1078 (prin1 eudc-protocol)
1079 (princ " t)\n")
1080 (princ "(setq eudc-server-hotlist '")
1081 (prin1 eudc-server-hotlist)
1082 (princ ")\n")
1083 (save-buffer))))
1085 (defun eudc-move-to-next-record ()
1086 "Move to next record, in a buffer displaying directory query results."
1087 (interactive)
1088 (if (not (eq major-mode 'eudc-mode))
1089 (error "Not in a EUDC buffer")
1090 (let ((pt (next-overlay-change (point))))
1091 (if (< pt (point-max))
1092 (goto-char (1+ pt))
1093 (error "No more records after point")))))
1095 (defun eudc-move-to-previous-record ()
1096 "Move to previous record, in a buffer displaying directory query results."
1097 (interactive)
1098 (if (not (eq major-mode 'eudc-mode))
1099 (error "Not in a EUDC buffer")
1100 (let ((pt (previous-overlay-change (point))))
1101 (if (> pt (point-min))
1102 (goto-char pt)
1103 (error "No more records before point")))))
1105 ;;}}}
1107 ;;{{{ Menus and keymaps
1109 (require 'easymenu)
1111 (setq eudc-mode-map
1112 (let ((map (make-sparse-keymap)))
1113 (define-key map "q" 'kill-this-buffer)
1114 (define-key map "x" 'kill-this-buffer)
1115 (define-key map "f" 'eudc-query-form)
1116 (define-key map "b" 'eudc-try-bbdb-insert)
1117 (define-key map "n" 'eudc-move-to-next-record)
1118 (define-key map "p" 'eudc-move-to-previous-record)
1119 map))
1120 (set-keymap-parent eudc-mode-map widget-keymap)
1122 (defconst eudc-custom-generated-menu (cdr (custom-menu-create 'eudc)))
1124 (defconst eudc-tail-menu
1125 `(["---" nil nil]
1126 ["Query with Form" eudc-query-form t]
1127 ["Expand Inline Query" eudc-expand-inline t]
1128 ["Insert Record into BBDB" eudc-insert-record-at-point-into-bbdb
1129 (and (or (featurep 'bbdb)
1130 (prog1 (locate-library "bbdb") (message "")))
1131 (overlays-at (point))
1132 (overlay-get (car (overlays-at (point))) 'eudc-record))]
1133 ["Insert All Records into BBDB" eudc-batch-export-records-to-bbdb
1134 (and (eq major-mode 'eudc-mode)
1135 (or (featurep 'bbdb)
1136 (prog1 (locate-library "bbdb") (message ""))))]
1137 ["---" nil nil]
1138 ["Get Email" eudc-get-email t]
1139 ["Get Phone" eudc-get-phone t]
1140 ["List Valid Attribute Names" eudc-get-attribute-list t]
1141 ["---" nil nil]
1142 ,(cons "Customize" eudc-custom-generated-menu)))
1145 (defconst eudc-server-menu
1146 '(["---" nil nil]
1147 ["Bookmark Current Server" eudc-bookmark-current-server t]
1148 ["Edit Server List" eudc-edit-hotlist t]
1149 ["New Server" eudc-set-server t]))
1151 (defun eudc-menu ()
1152 (let (command)
1153 (append '("Directory Search")
1154 (list
1155 (append
1156 '("Server")
1157 (mapcar
1158 (function
1159 (lambda (servspec)
1160 (let* ((server (car servspec))
1161 (protocol (cdr servspec))
1162 (proto-name (symbol-name protocol)))
1163 (setq command (intern (concat "eudc-set-server-"
1164 server
1166 proto-name)))
1167 (if (not (fboundp command))
1168 (fset command
1169 `(lambda ()
1170 (interactive)
1171 (eudc-set-server ,server (quote ,protocol))
1172 (message "Selected directory server is now %s (%s)"
1173 ,server
1174 ,proto-name))))
1175 (vector (format "%s (%s)" server proto-name)
1176 command
1177 :style 'radio
1178 :selected `(equal eudc-server ,server)))))
1179 eudc-server-hotlist)
1180 eudc-server-menu))
1181 eudc-tail-menu)))
1183 (defun eudc-install-menu ()
1184 (cond
1185 ((and eudc-xemacs-p (featurep 'menubar))
1186 (add-submenu '("Tools") (eudc-menu)))
1187 (eudc-emacs-p
1188 (cond
1189 ((fboundp 'easy-menu-create-menu)
1190 (define-key
1191 global-map
1192 [menu-bar tools directory-search]
1193 (cons "Directory Search"
1194 (easy-menu-create-menu "Directory Search" (cdr (eudc-menu))))))
1195 ((fboundp 'easy-menu-add-item)
1196 (let ((menu (eudc-menu)))
1197 (easy-menu-add-item nil '("tools") (easy-menu-create-menu (car menu)
1198 (cdr menu)))))
1199 ((fboundp 'easy-menu-create-keymaps)
1200 (easy-menu-define eudc-menu-map eudc-mode-map "Directory Client Menu" (eudc-menu))
1201 (define-key
1202 global-map
1203 [menu-bar tools eudc]
1204 (cons "Directory Search"
1205 (easy-menu-create-keymaps "Directory Search" (cdr (eudc-menu))))))
1207 (error "Unknown version of easymenu"))))
1211 ;;; Load time initializations :
1213 ;;; Load the options file
1214 (if (and (not noninteractive)
1215 (and (locate-library eudc-options-file)
1216 (progn (message "") t)) ; Remove modeline message
1217 (not (featurep 'eudc-options-file)))
1218 (load eudc-options-file))
1220 ;;; Install the full menu
1221 (unless (featurep 'infodock)
1222 (eudc-install-menu))
1225 ;;; The following installs a short menu for EUDC at XEmacs startup.
1227 ;;;###autoload
1228 (defun eudc-load-eudc ()
1229 "Load the Emacs Unified Directory Client.
1230 This does nothing except loading eudc by autoload side-effect."
1231 (interactive)
1232 nil)
1234 ;;;###autoload
1235 (cond ((not (string-match "XEmacs" emacs-version))
1236 (defvar eudc-tools-menu (make-sparse-keymap "Directory Search"))
1237 (fset 'eudc-tools-menu (symbol-value 'eudc-tools-menu))
1238 (define-key eudc-tools-menu [phone]
1239 '("Get Phone" . eudc-get-phone))
1240 (define-key eudc-tools-menu [email]
1241 '("Get Email" . eudc-get-email))
1242 (define-key eudc-tools-menu [separator-eudc-email]
1243 '("--"))
1244 (define-key eudc-tools-menu [expand-inline]
1245 '("Expand Inline Query" . eudc-expand-inline))
1246 (define-key eudc-tools-menu [query]
1247 '("Query with Form" . eudc-query-form))
1248 (define-key eudc-tools-menu [separator-eudc-query]
1249 '("--"))
1250 (define-key eudc-tools-menu [new]
1251 '("New Server" . eudc-set-server))
1252 (define-key eudc-tools-menu [load]
1253 '("Load Hotlist of Servers" . eudc-load-eudc)))
1256 (let ((menu '("Directory Search"
1257 ["Load Hotlist of Servers" eudc-load-eudc t]
1258 ["New Server" eudc-set-server t]
1259 ["---" nil nil]
1260 ["Query with Form" eudc-query-form t]
1261 ["Expand Inline Query" eudc-expand-inline t]
1262 ["---" nil nil]
1263 ["Get Email" eudc-get-email t]
1264 ["Get Phone" eudc-get-phone t])))
1265 (if (not (featurep 'eudc-autoloads))
1266 (if eudc-xemacs-p
1267 (if (and (featurep 'menubar)
1268 (not (featurep 'infodock)))
1269 (add-submenu '("Tools") menu))
1270 (require 'easymenu)
1271 (cond
1272 ((fboundp 'easy-menu-add-item)
1273 (easy-menu-add-item nil '("tools")
1274 (easy-menu-create-menu (car menu)
1275 (cdr menu))))
1276 ((fboundp 'easy-menu-create-keymaps)
1277 (define-key
1278 global-map
1279 [menu-bar tools eudc]
1280 (cons "Directory Search"
1281 (easy-menu-create-keymaps "Directory Search"
1282 (cdr menu)))))))))))
1284 ;;}}}
1286 (provide 'eudc)
1288 ;;; arch-tag: e18872b6-db83-400b-869d-be54e9a4160c
1289 ;;; eudc.el ends here