comparison lisp/utils/ph.el @ 165:5a88923fcbfe r20-3b9

Import from CVS: tag r20-3b9
author cvs
date Mon, 13 Aug 2007 09:44:42 +0200
parents 28f395d8dc7a
children 8eaf7971accc
comparison
equal deleted inserted replaced
164:4e0740e5aab2 165:5a88923fcbfe
1 ;;; ph.el --- Client for the CCSO directory system (aka PH/QI) 1 ;;; ph.el --- Client for the CCSO directory system (aka PH/QI)
2 2
3 ;; Copyright (C) 1997 Oscar Figueiredo 3 ;; Copyright (C) 1997 Free Software Foundation, Inc.
4 4
5 ;; Author: Oscar Figueiredo <Oscar.Figueiredo@di.epfl.ch> 5 ;; Author: Oscar Figueiredo <Oscar.Figueiredo@di.epfl.ch>
6 ;; Maintainer: Oscar Figueiredo <Oscar.Figueiredo@di.epfl.ch> 6 ;; Maintainer: Oscar Figueiredo <Oscar.Figueiredo@di.epfl.ch>
7 ;; Created: May 1997 7 ;; Created: May 1997
8 ;; Version: $Revision: 1.2 $ 8 ;; Version: 2.1
9 ;; Keywords: help 9 ;; Keywords: help
10 10
11 ;; This file is part of XEmacs 11 ;; This file is part of XEmacs
12 12
13 ;; XEmacs is free software; you can redistribute it and/or modify it 13 ;; XEmacs is free software; you can redistribute it and/or modify it
19 ;; WITHOUT ANY WARRANTY; without even the implied warranty of 19 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 ;; General Public License for more details. 21 ;; General Public License for more details.
22 22
23 ;; You should have received a copy of the GNU General Public License 23 ;; You should have received a copy of the GNU General Public License
24 ;; along with XEmacs; see the file COPYING. If not, write to 24 ;; along with XEmacs; see the file COPYING. If not, write to
25 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 25 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26 ;; Boston, MA 02111-1307, USA. 26 ;; Boston, MA 02111-1307, USA.
27 27
28 ;;; Commentary: 28 ;;; Commentary:
29 29 ;; This package provides functions to query CCSO PH/QI nameservers
30 ;; LCD Archive Entry (not registered yet): 30 ;; through an interactive form or replace inline query strings in
31 ;; ph|Oscar Figueiredo|Oscar.Figueiredo@di.epfl.ch| 31 ;; buffers with appropriately formatted query results (especially
32 ;; Client for the CCSO directory system (aka PH/QI)| 32 ;; used to expand email addresses in message buffers). It also
33 ;; 27-May-1997|Version $Revision: 1.2 $|ftp://(Not Available) 33 ;; interfaces with the BBDB package to let you register entries of
34 34 ;; the CCSO PH/QI directory into your own database. The CCSO PH/QI
35 ;; This package provides functions to query CCSO nameservers through an 35 ;; white pages system was developped at UIUC and is in use in more
36 ;; interactive form or replace inline query strings in buffers with
37 ;; appropriately formatted query results (especially used to expand email
38 ;; addresses in message buffers). It also interfaces with the BBDB package
39 ;; to let you register entries of the CCSO directory into your own database.
40 ;; The CCSO white pages system was developped at UIUC and is in use in more
41 ;; than 300 sites in the world. The distribution can be found at 36 ;; than 300 sites in the world. The distribution can be found at
42 ;; ftp://uiarchive.cso.uiuc.edu/pub/packages/ph 37 ;; ftp://uiarchive.cso.uiuc.edu/pub/packages/ph Traditionally the
43 ;; Traditionally the server is called QI while the client is called PH. 38 ;; server is called QI while the client is called PH.
44 39
45 ;;; Installation: 40 ;;; Installation:
46 ;; This package uses the custom and widget libraries. If they are not already 41 ;; This package uses the custom and widget libraries. If they are not already
47 ;; installed on your system get them from http://www.dina.kvl.dk/~abraham/custom/ 42 ;; installed on your system get them from http://www.dina.kvl.dk/~abraham/custom/
48 ;; Then uncomment and add the following to your .emacs file: 43 ;; Then uncomment and add the following to your .emacs file:
49 ;; (require 'ph) 44 ;; (require 'ph)
50 ;; (eval-after-load "message" 45 ;; (eval-after-load "message"
51 ;; (define-key message-mode-map [(control ?c) (tab)] 'ph-expand-inline)) 46 ;; '(define-key message-mode-map [(control ?c) (tab)] 'ph-expand-inline))
52 ;; (eval-after-load "mail" 47 ;; (eval-after-load "mail"
53 ;; (define-key mail-mode-map [(control ?c) (tab)] 'ph-expand-inline)) 48 ;; '(define-key mail-mode-map [(control ?c) (tab)] 'ph-expand-inline))
54
55 ;; This package should run under XEmacs 19.15 or 20 as well as under Emacs 19.34 and above
56 49
57 ;;; Usage: 50 ;;; Usage:
58 ;; * Provided you did the installation as proposed in the above section, 51 ;; - Provided you did the installation as proposed in the above section,
59 ;; inline expansion will be available when you compose an email 52 ;; inline expansion will be available when you compose an email
60 ;; message. Type the name of somebody recorded in your PH/QI server and hit 53 ;; message. Type the name of somebody recorded in your PH/QI server and hit
61 ;; C-c TAB, this will overwrite the name with the corresponding email 54 ;; C-c TAB, this will overwrite the name with the corresponding email
62 ;; address 55 ;; address
63 ;; * M-x ph-customize to customize inline expansion and other features to 56 ;; - M-x ph-customize to customize inline expansion and other features to
64 ;; your needs. 57 ;; your needs.
65 ;; * Look for the Ph submenu in the tools menu for more. 58 ;; - Look for the Ph submenu in the Tools menu for more.
66 59
67 ;;; Code: 60 ;;; Code:
68 61
69 (eval-when-compile 62 (eval-when-compile
70 (require 'wid-edit)) 63 (require 'wid-edit))
71 (require 'custom) 64 (require 'custom)
72 (if (not (fboundp 'make-overlay)) 65 (if (not (fboundp 'make-overlay))
73 (require 'overlay)) 66 (require 'overlay))
74 (if (locate-library "timer") 67 (if (locate-library "timer")
75 (require 'timer)) 68 (require 'timer))
69
76 (autoload 'custom-menu-create "cus-edit") 70 (autoload 'custom-menu-create "cus-edit")
71 (autoload 'bbdb-create-internal "bbdb-com")
72 (autoload 'bbdb-display-records "bbdb")
77 73
78 ;;{{{ Package customization variables 74 ;;{{{ Package customization variables
79 75
80 (defgroup ph nil 76 (defgroup ph nil
81 "CCSO (PH/QI) directory system client" 77 "CCSO (PH/QI) directory system client"
114 (const :menu-tag "All" all) 110 (const :menu-tag "All" all)
115 (const :menu-tag "Abort" abort) 111 (const :menu-tag "Abort" abort)
116 (const :menu-tag "None" nil)) 112 (const :menu-tag "None" nil))
117 :group 'ph) 113 :group 'ph)
118 114
119 (defcustom ph-duplicate-fields-handling-method 'list 115 (defcustom ph-duplicate-fields-handling-method '((email . duplicate))
120 "*A method to handle entries containing duplicate fields. 116 "*A method to handle entries containing duplicate fields.
121 This is either an alist (FIELD . METHOD) or a symbol METHOD. 117 This is either an alist (FIELD . METHOD) or a symbol METHOD.
122 The alist form of the variable associates a method to an individual field, 118 The alist form of the variable associates a method to an individual field,
123 the second form specifies a method applicable to all fields. 119 the second form specifies a method applicable to all fields.
124 Available methods are: 120 Available methods are:
168 (repeat :inline t 164 (repeat :inline t
169 :tag "Field names" 165 :tag "Field names"
170 (symbol :tag ""))) 166 (symbol :tag "")))
171 :group 'ph) 167 :group 'ph)
172 168
173 (defcustom ph-form-fields '(name firstname email phone) 169 (defcustom ph-form-fields '(name email phone)
174 "*A list of fields presented in the query form." 170 "*A list of fields presented in the query form."
175 :tag "Default Fields in Query Forms" 171 :tag "Default Fields in Query Forms"
176 :type '(repeat (symbol :tag "Field name")) 172 :type '(repeat (symbol :tag "Field name"))
177 :group 'ph) 173 :group 'ph)
178 174
179 (defcustom ph-fieldname-formstring-alist '((url . "URL") 175 (defcustom ph-fieldname-formstring-alist '((url . "URL")
180 (unix_gid . "Unix GID") 176 (unix_gid . "Unix GID")
181 (unix_uid . "Unix UID") 177 (unix_uid . "Unix UID")
182 (unit_code . "Unit Code") 178 (unit_code . "Unit Code")
183 (department_code . "Department Code") 179 (department_code . "Department Code")
184 (high_school . "High School") 180 (high_school . "High School")
185 (home_phone . "Home Phone") 181 (home_phone . "Home Phone")
186 (office_phone . "Office Phone") 182 (office_phone . "Office Phone")
187 (callsign . "HAM Call Sign") 183 (callsign . "HAM Call Sign")
188 (office_address . "Office Address") 184 (office_address . "Office Address")
189 (office_location . "Office Location") 185 (office_location . "Office Location")
190 (id . "ID") 186 (id . "ID")
191 (email . "E-Mail") 187 (email . "E-Mail")
192 (firstname . "First Name")) 188 (firstname . "First Name"))
193 "*A mapping of CCSO database field names onto prompt strings used in query/response forms. 189 "*A mapping of CCSO database field names onto prompt strings used in query/response forms.
194 Prompt strings for fields that are not in this are derived by capitalizing 190 Prompt strings for fields that are not in this are derived by capitalizing
195 the field name." 191 the field name."
196 :tag "Mapping of Field Names onto Prompt Strings" 192 :tag "Mapping of Field Names onto Prompt Strings"
197 :type '(repeat (cons :tag "Field" 193 :type '(repeat (cons :tag "Field"
198 (symbol :tag "Name") 194 (symbol :tag "Name")
199 (string :tag "Prompt string"))) 195 (string :tag "Prompt string")))
200 :group 'ph) 196 :group 'ph)
201 197
202 (defcustom ph-bbdb-mapping-alist '((name . (firstname name)) 198 (defcustom ph-bbdb-conversion-alist '((name . name)
203 (email . net)) 199 (net . email)
204 "*A mapping of CCSO database field names onto BBDB field names" 200 (address . (ph-bbdbify-address address "Address"))
205 :tag "CCSO to BBDB Field Name Mapping" 201 (phone . ((ph-bbdbify-phone phone "Phone")
202 (ph-bbdbify-phone office_phone "Office Phone"))))
203 "*A mapping from BBDB to PH/QI fields.
204 This is a list of cons cells (BBDB-FIELD . SPEC-OR-LIST) where BBDB-FIELD
205 is the name of a field that must be defined in your BBDB (standard field
206 names are `name', `company', `net', `phone', `address' and `notes').
207 SPEC-OR-LIST is either a single SPEC or a list of SPECs. Lists of specs are
208 valid only for the `phone' and `address' BBDB fields.
209 SPECs are sexps which are evaluated:
210 -a string evaluates to itself
211 -a symbol evaluates to the symbol value. Symbols naming PH/QI fields
212 present in the record evaluate to the value of the field in the record
213 -a form is evaluated as a function. The argument list may contain PH/QI
214 field names which eval to the corresponding values in the record. The form
215 evaluation should return something appropriate for the particular
216 BBDB-FIELD (see bbdb-create-internal). ph-bbdbify-phone and
217 ph-bbdbify-address are provided as convenience functions to parse phones
218 and addresses."
219 :tag "BBDB to CCSO Field Name Mapping"
206 :type '(repeat (cons :tag "Field Name" 220 :type '(repeat (cons :tag "Field Name"
207 (symbol :tag "CCSO") 221 (symbol :tag "BBDB Field")
208 (sexp :tag "BBDB"))) 222 (sexp :tag "Conversion Spec")))
209 :group 'ph) 223 :group 'ph)
210 224
211 (defcustom ph-options-file "~/.emacs" 225 (defcustom ph-options-file "~/.emacs"
212 "*A file where the servers hotlist is stored. 226 "*A file where the servers hotlist is stored.
213 It should be loaded automatically at startup so ~/.emacs is a reasonable 227 It should be loaded automatically at startup so ~/.emacs is a reasonable
232 "Default TCP port for CCSO directory services") 246 "Default TCP port for CCSO directory services")
233 247
234 (defvar ph-form-widget-list nil) 248 (defvar ph-form-widget-list nil)
235 (defvar ph-process-buffer nil) 249 (defvar ph-process-buffer nil)
236 (defvar ph-read-point) 250 (defvar ph-read-point)
251
252
253
254
255 ;;; FSF Emacs does not provide that one
256 (if (not (fboundp 'split-string))
257 (defun split-string (string pattern)
258 "Return a list of substrings of STRING which are separated by PATTERN."
259 (let (parts (start 0))
260 (while (string-match pattern string start)
261 (setq parts (cons (substring string start (match-beginning 0)) parts)
262 start (match-end 0)))
263 (nreverse (cons (substring string start) parts))
264 )))
237 265
238 (defun ph-display-records (records &optional raw-field-names) 266 (defun ph-display-records (records &optional raw-field-names)
239 "Display the record list RECORDS in a formatted buffer. 267 "Display the record list RECORDS in a formatted buffer.
240 If RAW-FIELD-NAMES is non nil, no translation to form strings or 268 If RAW-FIELD-NAMES is non nil, no translation to form strings or
241 capitalization is done on field names." 269 capitalization is done on field names."
396 (setq current-key key)) 424 (setq current-key key))
397 (if (or (null fields) 425 (if (or (null fields)
398 (memq 'all fields) 426 (memq 'all fields)
399 (memq current-key fields)) 427 (memq current-key fields))
400 (if key 428 (if key
401 (setq record (cons (cons key value) record)) 429 (setq record (cons (cons key value) record)) ; New key
402 (setcdr (car record) (cons value (if (listp (cdar record)) 430 (setcdr (car record) (if (listp (cdar record))
403 (cdar record) 431 (append (cdar record) (list value))
404 (cons (cdar record) nil))))))))) 432 (list (cdar record) value))))))))
405 (and (not ignore) 433 (and (not ignore)
406 (or (null fields) 434 (or (null fields)
407 (memq 'all fields) 435 (memq 'all fields)
408 (setq record (nreverse record))) 436 (setq record (nreverse record)))
409 (setq record (if (not (eq 'list ph-duplicate-fields-handling-method)) 437 (setq record (if (not (eq 'list ph-duplicate-fields-handling-method))
571 (setq ph-read-point match-end) 599 (setq ph-read-point match-end)
572 (if return-response 600 (if return-response
573 (buffer-substring (point) match-end) 601 (buffer-substring (point) match-end)
574 return-code)))) 602 return-code))))
575 603
576 ;;; FSF Emacs does not provide that one 604 (defun ph-create-bbdb-record (record)
577 (if (not (fboundp 'split-string)) 605 "Create a BBDB record using the RECORD alist.
578 (defun split-string (string pattern) 606 RECORD is an alist of (KEY . VALUE) where KEY is a symbol naming a field
579 "Return a list of substrings of STRING which are separated by PATTERN." 607 of the PH/QI database and VALUE is the corresponding value for the record"
580 (let (parts (start 0)) 608 ;; This function runs in a special context where lisp symbols corresponding
581 (while (string-match pattern string start) 609 ;; to field names in record are bound to the corresponding values
582 (setq parts (cons (substring string start (match-beginning 0)) parts) 610 (eval
583 start (match-end 0))) 611 `(let* (,@(mapcar '(lambda (c)
584 (nreverse (cons (substring string start) parts)) 612 (list (car c) (if (listp (cdr c))
585 ))) 613 (list 'quote (cdr c))
586 614 (cdr c))))
615 record)
616 bbdb-name
617 bbdb-company
618 bbdb-net
619 bbdb-address
620 bbdb-phones
621 bbdb-notes
622 spec
623 bbdb-record
624 value)
625
626 ;; BBDB standard fields
627 (setq bbdb-name (ph-parse-spec (cdr (assq 'name ph-bbdb-conversion-alist)) record nil)
628 bbdb-company (ph-parse-spec (cdr (assq 'company ph-bbdb-conversion-alist)) record nil)
629 bbdb-net (ph-parse-spec (cdr (assq 'net ph-bbdb-conversion-alist)) record nil)
630 bbdb-notes (ph-parse-spec (cdr (assq 'notes ph-bbdb-conversion-alist)) record nil))
631 (setq spec (cdr (assq 'address ph-bbdb-conversion-alist)))
632 (setq bbdb-address (delq nil (ph-parse-spec (if (listp (car spec))
633 spec
634 (list spec))
635 record t)))
636 (setq spec (cdr (assq 'phone ph-bbdb-conversion-alist)))
637 (setq bbdb-phones (delq nil (ph-parse-spec (if (listp (car spec))
638 spec
639 (list spec))
640 record t)))
641 ;; BBDB custom fields
642 (setq bbdb-notes (append (list (and bbdb-notes (cons 'notes bbdb-notes)))
643 (mapcar (function
644 (lambda (mapping)
645 (if (and (not (memq (car mapping)
646 '(name company net address phone notes)))
647 (setq value (ph-parse-spec (cdr mapping) record nil)))
648 (cons (car mapping) value))))
649 ph-bbdb-conversion-alist)))
650 (setq bbdb-notes (delq nil bbdb-notes))
651 (setq bbdb-record (bbdb-create-internal bbdb-name
652 bbdb-company
653 bbdb-net
654 bbdb-address
655 bbdb-phones
656 bbdb-notes))
657
658 (bbdb-display-records (list bbdb-record))
659 )))
660
661 (defun ph-parse-spec (spec record recurse)
662 "Parse the conversion SPEC using RECORD.
663 If RECURSE is non-nil then SPEC may be a list of atomic specs"
664 (cond
665 ((or (stringp spec)
666 (symbolp spec)
667 (and (listp spec)
668 (symbolp (car spec))
669 (fboundp (car spec))))
670 (condition-case nil
671 (eval spec)
672 (void-variable nil)))
673 ((and recurse
674 (listp spec))
675 (mapcar '(lambda (spec-elem)
676 (ph-parse-spec spec-elem record nil))
677 spec))
678 (t
679 (error "Invalid mapping specification for `%s'. Fix ph-bbdb-conversion-alist" spec))))
680
681 (defun ph-bbdbify-address (addr location)
682 "Parse ADDR into a vector compatible with bbdb-create-internal.
683 ADR should be an address string of no more than four lines or a
684 list of lines.
685 The last line is searched for the zip code, city and state name.
686 LOCATION is used as the address location for bbdb"
687 (let* ((addr-components (if (listp addr)
688 (reverse addr)
689 (reverse (split-string addr "\n"))))
690 (lastl (pop addr-components))
691 zip city state)
692 (setq addr-components (nreverse addr-components))
693 (cond
694 ;; American style
695 ((string-match "\\(\\w+\\)\\W*\\([A-Z][A-Z]\\)\\W*\\([0-9]+\\)" lastl)
696 (setq city (match-string 1 lastl)
697 state (match-string 2 lastl)
698 zip (string-to-number (match-string 3 lastl))))
699 ;; European style
700 ((string-match "\\([0-9]+\\)[ \t]+\\(.*\\)" lastl)
701 (setq city (match-string 2 lastl)
702 zip (string-to-number (match-string 1 lastl))))
703 (t
704 (error "ph-bbdbify-address was unable to parse the address. Customize ph-bbdb-conversion-alist")))
705 (vector location
706 (or (nth 0 addr-components) "")
707 (or (nth 1 addr-components) "")
708 (or (nth 2 addr-components) "")
709 (or city "")
710 (or state "")
711 zip)))
712
713 (defun ph-bbdbify-phone (phone location)
714 "Parse PHONE into a vector compatible with bbdb-create-internal.
715 PHONE is either a string supposedly containing a phone number or
716 a list of such strings which are concatenated.
717 LOCATION is used as the phone location for bbdb"
718 (let ((phone-string (cond
719 ((stringp phone)
720 phone)
721 ((listp phone)
722 (mapconcat 'identity phone ", "))
723 (t
724 (error "Invalid phone specification. Cannot create bbdb record")))))
725 (vector location phone-string)))
726
587 ;;}}} 727 ;;}}}
588 728
589 ;;{{{ High-level interfaces (interactive functions) 729 ;;{{{ High-level interfaces (interactive functions)
590 730
591 (defun ph-customize () 731 (defun ph-customize ()
594 (customize 'ph)) 734 (customize 'ph))
595 735
596 (defun ph-set-server (server) 736 (defun ph-set-server (server)
597 "Set the server to SERVER." 737 "Set the server to SERVER."
598 (interactive "sNew PH/QI Server: ") 738 (interactive "sNew PH/QI Server: ")
599 (setq ph-server server) 739 (message "Selected PH/QI server is now %s" server)
600 (message "Selected PH/QI server is now %s" server)) 740 (setq ph-server server))
601 741
602 (defun ph-get-email (name) 742 (defun ph-get-email (name)
603 "Get the email field of NAME from the PH/QI directory server." 743 "Get the email field of NAME from the PH/QI directory server."
604 (interactive "sName: ") 744 (interactive "sName: ")
605 (let ((email (cdaar (ph-query-internal name '(email))))) 745 (let ((email (cdaar (ph-query-internal name '(email)))))
832 (prin1 ph-server-hotlist) 972 (prin1 ph-server-hotlist)
833 (princ ")\n")) 973 (princ ")\n"))
834 (save-buffer)) 974 (save-buffer))
835 ) 975 )
836 976
977
978 (defun ph-insert-record-at-point-into-bbdb ()
979 "Insert record at point into the BBDB database.
980 This function can only be called from a PH/QI query result buffer."
981 (interactive)
982 (let ((record (and (overlays-at (point))
983 (overlay-get (car (overlays-at (point))) 'ph-record))))
984 (if (null record)
985 (error "Point is not over a record.")
986 (ph-create-bbdb-record record))))
987
837 ;;}}} 988 ;;}}}
838 989
839 ;;{{{ Menu interface 990 ;;{{{ Menu interface
840 991
841 (require 'easymenu) 992 (require 'easymenu)
842 993
843 (defconst ph-tail-menu 994 (defconst ph-tail-menu
844 `(["---" nil nil] 995 `(["---" nil nil]
845 ["Query Form" ph-query-form t] 996 ["Query Form" ph-query-form t]
846 ["Expand Inline" ph-expand-inline t] 997 ["Expand Inline" ph-expand-inline t]
998 ["Insert Record into BBDB" ph-insert-record-at-point-into-bbdb
999 (and (or (featurep 'bbdb)
1000 (locate-library 'bbdb))
1001 (overlays-at (point))
1002 (overlay-get (car (overlays-at (point))) 'ph-record))]
847 ["---" nil nil] 1003 ["---" nil nil]
848 ["Get Email" ph-get-email t] 1004 ["Get Email" ph-get-email t]
849 ["Get Phone" ph-get-phone t] 1005 ["Get Phone" ph-get-phone t]
850 ["List Valid Field Names" ph-get-field-list t] 1006 ["List Valid Field Names" ph-get-field-list t]
851 ["---" nil nil] 1007 ["---" nil nil]