diff 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
line wrap: on
line diff
--- a/lisp/utils/ph.el	Mon Aug 13 09:43:39 2007 +0200
+++ b/lisp/utils/ph.el	Mon Aug 13 09:44:42 2007 +0200
@@ -1,11 +1,11 @@
 ;;; ph.el --- Client for the CCSO directory system (aka PH/QI)
 
-;; Copyright (C) 1997 Oscar Figueiredo
+;; Copyright (C) 1997 Free Software Foundation, Inc.
 
 ;; Author: Oscar Figueiredo <Oscar.Figueiredo@di.epfl.ch>
 ;; Maintainer: Oscar Figueiredo <Oscar.Figueiredo@di.epfl.ch>
 ;; Created: May 1997
-;; Version: $Revision: 1.2 $
+;; Version: 2.1
 ;; Keywords: help
 
 ;; This file is part of XEmacs
@@ -21,26 +21,21 @@
 ;; General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with XEmacs; see the file COPYING.  If not, write to
+;; along with XEmacs; see the file COPYING.  If not, write to 
 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 ;; Boston, MA 02111-1307, USA.
 
 ;;; Commentary:
-
-;; LCD Archive Entry (not registered yet):
-;; ph|Oscar Figueiredo|Oscar.Figueiredo@di.epfl.ch|
-;; Client for the CCSO directory system (aka PH/QI)|
-;; 27-May-1997|Version $Revision: 1.2 $|ftp://(Not Available)
-
-;;    This package provides functions to query CCSO nameservers through an
-;;    interactive form or replace inline query strings in buffers with
-;;    appropriately formatted query results (especially used to expand email
-;;    addresses in message buffers). It also interfaces with the BBDB package
-;;    to let you register entries of the CCSO directory into your own database.
-;;    The CCSO white pages system was developped at UIUC and is in use in more
+;;    This package provides functions to query CCSO PH/QI nameservers
+;;    through an interactive form or replace inline query strings in
+;;    buffers with appropriately formatted query results (especially
+;;    used to expand email addresses in message buffers). It also
+;;    interfaces with the BBDB package to let you register entries of
+;;    the CCSO PH/QI directory into your own database.  The CCSO PH/QI
+;;    white pages system was developped at UIUC and is in use in more
 ;;    than 300 sites in the world. The distribution can be found at
-;;    ftp://uiarchive.cso.uiuc.edu/pub/packages/ph 
-;;    Traditionally the server is called QI while the client is called PH.
+;;    ftp://uiarchive.cso.uiuc.edu/pub/packages/ph Traditionally the
+;;    server is called QI while the client is called PH.
 
 ;;; Installation:
 ;;    This package uses the custom and widget libraries. If they are not already 
@@ -48,21 +43,19 @@
 ;;    Then uncomment and add the following to your .emacs file:
 ;;      (require 'ph)
 ;;      (eval-after-load "message"
-;;                       (define-key message-mode-map [(control ?c) (tab)] 'ph-expand-inline))
+;;                       '(define-key message-mode-map [(control ?c) (tab)] 'ph-expand-inline))
 ;;      (eval-after-load "mail"
-;;                       (define-key mail-mode-map [(control ?c) (tab)] 'ph-expand-inline))
-
-;;    This package should run under XEmacs 19.15 or 20 as well as under Emacs 19.34 and above
+;;                       '(define-key mail-mode-map [(control ?c) (tab)] 'ph-expand-inline))
 
 ;;; Usage:
-;;    * Provided you did the installation as proposed in the above section, 
+;;    - Provided you did the installation as proposed in the above section, 
 ;;      inline expansion will be available when you compose an email
 ;;      message. Type the name of somebody recorded in your PH/QI server and hit
 ;;      C-c TAB, this will overwrite the name with the corresponding email 
 ;;      address
-;;    * M-x ph-customize to customize inline expansion and other features to
+;;    - M-x ph-customize to customize inline expansion and other features to
 ;;      your needs.
-;;    * Look for the Ph submenu in the tools menu for more.
+;;    - Look for the Ph submenu in the Tools menu for more.
 
 ;;; Code:
 
@@ -73,7 +66,10 @@
     (require 'overlay))
 (if (locate-library "timer")
     (require 'timer))
+
 (autoload 'custom-menu-create "cus-edit")
+(autoload 'bbdb-create-internal "bbdb-com")
+(autoload 'bbdb-display-records "bbdb")
 
 ;;{{{      Package customization variables
 
@@ -116,7 +112,7 @@
 		  (const :menu-tag "None" nil))
   :group 'ph)
 
-(defcustom ph-duplicate-fields-handling-method 'list
+(defcustom ph-duplicate-fields-handling-method '((email . duplicate))
   "*A method to handle entries containing duplicate fields.
 This is either an alist (FIELD . METHOD) or a symbol METHOD.
 The alist form of the variable associates a method to an individual field,
@@ -170,26 +166,26 @@
 			(symbol :tag "")))
   :group 'ph)
 
-(defcustom ph-form-fields '(name firstname email phone)
+(defcustom ph-form-fields '(name email phone)
   "*A list of fields presented in the query form."
   :tag   "Default Fields in Query Forms"
   :type  '(repeat (symbol :tag "Field name"))
   :group 'ph)
 
 (defcustom ph-fieldname-formstring-alist '((url . "URL")
-					    (unix_gid . "Unix GID")
-					    (unix_uid . "Unix UID")
-					    (unit_code . "Unit Code")
-					    (department_code . "Department Code")
-					    (high_school . "High School")
-					    (home_phone . "Home Phone")
-					    (office_phone . "Office Phone")
-					    (callsign . "HAM Call Sign")
-					    (office_address . "Office Address")
-					    (office_location . "Office Location")
-					    (id . "ID")
-					    (email . "E-Mail")
-					    (firstname . "First Name"))
+					   (unix_gid . "Unix GID")
+					   (unix_uid . "Unix UID")
+					   (unit_code . "Unit Code")
+					   (department_code . "Department Code")
+					   (high_school . "High School")
+					   (home_phone . "Home Phone")
+					   (office_phone . "Office Phone")
+					   (callsign . "HAM Call Sign")
+					   (office_address . "Office Address")
+					   (office_location . "Office Location")
+					   (id . "ID")
+					   (email . "E-Mail")
+					   (firstname . "First Name"))
   "*A mapping of CCSO database field names onto prompt strings used in query/response forms.
 Prompt strings for fields that are not in this are derived by capitalizing
 the field name."
@@ -199,13 +195,31 @@
 		        (string :tag "Prompt string")))
   :group 'ph)
 
-(defcustom ph-bbdb-mapping-alist '((name . (firstname name))
-				    (email . net))
-  "*A mapping of CCSO database field names onto BBDB field names"
-  :tag "CCSO to BBDB Field Name Mapping"
+(defcustom ph-bbdb-conversion-alist '((name . name)
+				      (net . email)
+				      (address . (ph-bbdbify-address address "Address"))
+				      (phone . ((ph-bbdbify-phone phone "Phone")
+						(ph-bbdbify-phone office_phone "Office Phone"))))
+  "*A mapping from BBDB to PH/QI fields.
+This is a list of cons cells (BBDB-FIELD . SPEC-OR-LIST) where BBDB-FIELD
+is the name of a field that must be defined in your BBDB (standard field
+names are `name', `company', `net', `phone', `address' and `notes').
+SPEC-OR-LIST is either a single SPEC or a list of SPECs. Lists of specs are
+valid only for the `phone' and `address' BBDB fields. 
+SPECs are sexps which are evaluated:
+  -a string evaluates to itself
+  -a symbol evaluates to the symbol value. Symbols naming PH/QI fields
+   present in the record evaluate to the value of the field in the record
+  -a form is evaluated as a function. The argument list may contain PH/QI 
+   field names which eval to the corresponding values in the record. The form
+   evaluation should return something appropriate for the particular 
+   BBDB-FIELD (see bbdb-create-internal). ph-bbdbify-phone and 
+   ph-bbdbify-address are provided as convenience functions to parse phones
+   and addresses."
+  :tag "BBDB to CCSO Field Name Mapping"
   :type '(repeat (cons :tag "Field Name"
-		       (symbol :tag "CCSO")
-		       (sexp :tag "BBDB")))
+		       (symbol :tag "BBDB Field")
+		       (sexp :tag "Conversion Spec")))
   :group 'ph)
 
 (defcustom ph-options-file "~/.emacs"
@@ -235,6 +249,20 @@
 (defvar ph-process-buffer nil)
 (defvar ph-read-point)
 
+
+
+
+;;; FSF Emacs does not provide that one
+(if (not (fboundp 'split-string))
+    (defun split-string (string pattern)
+      "Return a list of substrings of STRING which are separated by PATTERN."
+      (let (parts (start 0))
+	(while (string-match pattern string start)
+	  (setq parts (cons (substring string start (match-beginning 0)) parts)
+		start (match-end 0)))
+	(nreverse (cons (substring string start) parts))
+	)))
+
 (defun ph-display-records (records &optional raw-field-names)
   "Display the record list RECORDS in a formatted buffer. 
 If RAW-FIELD-NAMES is non nil, no translation to form strings or 
@@ -398,10 +426,10 @@
 		      (memq 'all fields)
 		      (memq current-key fields))
 		  (if key
-		      (setq record (cons (cons key value) record))
-		    (setcdr (car record) (cons value (if (listp (cdar record))
-							 (cdar record)
-						       (cons (cdar record) nil)))))))))
+		      (setq record (cons (cons key value) record)) ; New key
+		    (setcdr (car record) (if (listp (cdar record))
+					     (append (cdar record) (list value))
+					   (list (cdar record) value))))))))
 	(and (not ignore)
 	     (or (null fields)
 		 (memq 'all fields)
@@ -573,17 +601,129 @@
 	  (buffer-substring (point) match-end)
 	return-code))))
 
-;;; FSF Emacs does not provide that one
-(if (not (fboundp 'split-string))
-    (defun split-string (string pattern)
-      "Return a list of substrings of STRING which are separated by PATTERN."
-      (let (parts (start 0))
-	(while (string-match pattern string start)
-	  (setq parts (cons (substring string start (match-beginning 0)) parts)
-		start (match-end 0)))
-	(nreverse (cons (substring string start) parts))
-	)))
+(defun ph-create-bbdb-record (record)
+  "Create a BBDB record using the RECORD alist.
+RECORD is an alist of (KEY . VALUE) where KEY is a symbol naming a field
+of the PH/QI database and VALUE is the corresponding value for the record"
+  ;; This function runs in a special context where lisp symbols corresponding
+  ;; to field names in record are bound to the corresponding values
+  (eval 
+   `(let* (,@(mapcar '(lambda (c)
+			(list (car c) (if (listp (cdr c))
+					  (list 'quote (cdr c))
+					(cdr c))))
+		     record)
+	     bbdb-name
+	     bbdb-company
+	     bbdb-net
+	     bbdb-address
+	     bbdb-phones
+	     bbdb-notes
+	     spec
+	     bbdb-record
+	     value)
+
+      ;; BBDB standard fields
+      (setq bbdb-name (ph-parse-spec (cdr (assq 'name ph-bbdb-conversion-alist)) record nil)
+	    bbdb-company (ph-parse-spec (cdr (assq 'company ph-bbdb-conversion-alist)) record nil)
+	    bbdb-net (ph-parse-spec (cdr (assq 'net ph-bbdb-conversion-alist)) record nil)
+	    bbdb-notes (ph-parse-spec (cdr (assq 'notes ph-bbdb-conversion-alist)) record nil))
+      (setq spec (cdr (assq 'address ph-bbdb-conversion-alist)))
+      (setq bbdb-address (delq nil (ph-parse-spec (if (listp (car spec))
+						      spec
+						    (list spec))
+						  record t)))
+      (setq spec (cdr (assq 'phone ph-bbdb-conversion-alist)))
+      (setq bbdb-phones (delq nil (ph-parse-spec (if (listp (car spec))
+						     spec
+						   (list spec))
+						 record t)))
+      ;; BBDB custom fields
+      (setq bbdb-notes (append (list (and bbdb-notes (cons 'notes bbdb-notes)))
+			       (mapcar (function
+					(lambda (mapping)
+					  (if (and (not (memq (car mapping)
+							      '(name company net address phone notes)))
+						   (setq value (ph-parse-spec (cdr mapping) record nil)))
+					      (cons (car mapping) value))))
+				       ph-bbdb-conversion-alist)))
+      (setq bbdb-notes (delq nil bbdb-notes))
+      (setq bbdb-record (bbdb-create-internal bbdb-name 
+					      bbdb-company 
+					      bbdb-net
+					      bbdb-address
+					      bbdb-phones
+					      bbdb-notes))
+
+      (bbdb-display-records (list bbdb-record))
+      )))
 
+(defun ph-parse-spec (spec record recurse)
+  "Parse the conversion SPEC using RECORD. 
+If RECURSE is non-nil then SPEC may be a list of atomic specs"
+  (cond 
+   ((or (stringp spec)
+	(symbolp spec)
+	(and (listp spec)
+	     (symbolp (car spec))
+	     (fboundp (car spec))))
+    (condition-case nil
+	(eval spec)
+      (void-variable nil)))
+   ((and recurse
+	 (listp spec))
+    (mapcar '(lambda (spec-elem)
+	       (ph-parse-spec spec-elem record nil))
+	    spec))
+   (t
+    (error "Invalid mapping specification for `%s'. Fix ph-bbdb-conversion-alist" spec))))
+
+(defun ph-bbdbify-address (addr location)
+  "Parse ADDR into a vector compatible with bbdb-create-internal.
+ADR should be an address string of no more than four lines or a
+list of lines. 
+The last line is searched for the zip code, city and state name.
+LOCATION is used as the address location for bbdb"
+  (let* ((addr-components (if (listp addr)
+			      (reverse addr)
+			    (reverse (split-string addr "\n"))))
+	 (lastl (pop addr-components))
+	 zip city state)
+    (setq addr-components (nreverse addr-components))
+    (cond
+     ;; American style
+     ((string-match "\\(\\w+\\)\\W*\\([A-Z][A-Z]\\)\\W*\\([0-9]+\\)" lastl)
+      (setq city (match-string 1 lastl)
+	    state (match-string 2 lastl)
+	    zip (string-to-number (match-string 3 lastl))))
+     ;; European style
+     ((string-match "\\([0-9]+\\)[ \t]+\\(.*\\)" lastl)
+      (setq city (match-string 2 lastl)
+	    zip (string-to-number (match-string 1 lastl))))
+     (t
+      (error "ph-bbdbify-address was unable to parse the address. Customize ph-bbdb-conversion-alist")))
+    (vector location 
+	    (or (nth 0 addr-components) "")
+	    (or (nth 1 addr-components) "")
+	    (or (nth 2 addr-components) "")
+	    (or city "")
+	    (or state "")
+	    zip)))
+
+(defun ph-bbdbify-phone (phone location)
+  "Parse PHONE into a vector compatible with bbdb-create-internal.
+PHONE is either a string supposedly containing a phone number or
+a list of such strings which are concatenated.
+LOCATION is used as the phone location for bbdb"
+  (let ((phone-string (cond
+		       ((stringp phone)
+			phone)
+		       ((listp phone)
+			(mapconcat 'identity phone ", "))
+		       (t
+			(error "Invalid phone specification. Cannot create bbdb record")))))
+    (vector location phone-string)))
+			
 ;;}}}				 
 
 ;;{{{      High-level interfaces (interactive functions)
@@ -596,8 +736,8 @@
 (defun ph-set-server (server)
   "Set the server to SERVER."
   (interactive "sNew PH/QI Server: ")
-  (setq ph-server server)
-  (message "Selected PH/QI server is now %s" server))
+  (message "Selected PH/QI server is now %s" server)
+  (setq ph-server server))
 
 (defun ph-get-email (name)
   "Get the email field of NAME from the PH/QI directory server."
@@ -834,6 +974,17 @@
     (save-buffer))
 )
 
+
+(defun ph-insert-record-at-point-into-bbdb ()
+  "Insert record at point into the BBDB database.
+This function can only be called from a PH/QI query result buffer."
+  (interactive)
+  (let ((record (and (overlays-at (point))
+		     (overlay-get (car (overlays-at (point))) 'ph-record))))
+    (if (null record)
+	(error "Point is not over a record.")
+      (ph-create-bbdb-record record))))
+    
 ;;}}}
 
 ;;{{{      Menu interface
@@ -844,6 +995,11 @@
   `(["---" nil nil]
     ["Query Form" ph-query-form t]
     ["Expand Inline" ph-expand-inline t]
+    ["Insert Record into BBDB" ph-insert-record-at-point-into-bbdb 
+     (and (or (featurep 'bbdb)
+	      (locate-library 'bbdb))
+	  (overlays-at (point))
+	  (overlay-get (car (overlays-at (point))) 'ph-record))]
     ["---" nil nil]
     ["Get Email" ph-get-email t]
     ["Get Phone" ph-get-phone t]