emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/ebdb 3210ad7 338/350: Compiler-inspired fixes version 4


From: Eric Abrahamsen
Subject: [elpa] externals/ebdb 3210ad7 338/350: Compiler-inspired fixes version 443992
Date: Mon, 14 Aug 2017 11:47:06 -0400 (EDT)

branch: externals/ebdb
commit 3210ad793cd8a418328e7093dc055c1ab1416bf9
Author: Eric Abrahamsen <address@hidden>
Commit: Eric Abrahamsen <address@hidden>

    Compiler-inspired fixes version 443992
    
    Getting as close to zero warnings as possible before going into ELPA
    
    Mostly moving code around so it's defined in the right order, more
    defvars, declare-functions and autoloads, underscores on unused
    parameters, some deletion of unused/unreachable code. And I snuck in
    some edits to phone dialing (sorry!).
---
 ebdb-com.el     |  128 ++---
 ebdb-format.el  |    4 +-
 ebdb-gnorb.el   |    6 +-
 ebdb-gnus.el    |    3 +-
 ebdb-i18n.el    |  867 +++++++++++++++----------------
 ebdb-message.el |    2 +
 ebdb-migrate.el |    6 +-
 ebdb-mua.el     |    2 +
 ebdb-org.el     |    3 +-
 ebdb-vcard.el   |    2 +
 ebdb.el         | 1544 ++++++++++++++++++++++++++-----------------------------
 helm-ebdb.el    |    2 +-
 12 files changed, 1240 insertions(+), 1329 deletions(-)

diff --git a/ebdb-com.el b/ebdb-com.el
index 527605d..7023c1d 100644
--- a/ebdb-com.el
+++ b/ebdb-com.el
@@ -91,6 +91,13 @@ Used by `ebdb-mouse-menu'."
   :group 'ebdb
   :group 'faces)
 
+(defcustom ebdb-name-face-alist '((ebdb-record-person . ebdb-person-name)
+                                 (ebdb-record-organization . 
ebdb-organization-name))
+  "Alist matching record class types to the face that should be
+  used to font-lock their names in the *EBDB* buffer."
+  :group 'ebdb-faces
+  :type '(repeat (cons (ebdb-record :tag "Record type") (face :tag "Face"))))
+
 (defface ebdb-person-name
   '((t (:inherit font-lock-function-name-face)))
   "Face used for EBDB person names."
@@ -101,13 +108,6 @@ Used by `ebdb-mouse-menu'."
   "Face used for EBDB organization names."
   :group 'ebdb-faces)
 
-(defcustom ebdb-name-face-alist '((ebdb-record-person . ebdb-person-name)
-                                 (ebdb-record-organization . 
ebdb-organization-name))
-  "Alist matching record class types to the face that should be
-  used to font-lock their names in the *EBDB* buffer."
-  :group 'ebdb-faces
-  :type '(repeat (cons (ebdb-record :tag "Record type") (face :tag "Face"))))
-
 (defface ebdb-marked
   '((t (:background "LightBlue")))
   "Face used for currently-marked records."
@@ -193,9 +193,6 @@ If FULL is non-nil, assume that RECORDS include display 
information."
 ;; Note about EBDB prefix commands: `ebdb-search-invert' is a fake
 ;; prefix commands. They need not precede the main commands.
 
-(defvar ebdb-search-invert nil
-  "Bind this variable to t in order to invert the result of `ebdb-search'.")
-
 (defun ebdb-search-invert-p ()
   "Return variable `ebdb-search-invert' and set it to nil.
 To set it again, use command `ebdb-search-invert'."
@@ -2008,10 +2005,6 @@ the record to be displayed or nil otherwise."
 
 ;;; Send-Mail interface
 
-(defun ebdb-compose-mail (&rest args)
-  "Start composing a mail message to send."
-  (apply 'compose-mail args))
-
 ;;;###autoload
 (defun ebdb-mail (records &optional subject arg)
   "Compose a mail message to RECORDS (optional: using SUBJECT).
@@ -2528,64 +2521,51 @@ actions."
       (funcall action record field)
     (message "No action for field")))
 
-
-;;; Dialing numbers from EBDB
-
-(defun ebdb-dial-number (phone-string)
-  "Dial the number specified by PHONE-STRING.
-This uses the tel URI syntax passed to `browse-url' to make the call.
-If `ebdb-dial-function' is non-nil then that is called to make the phone call."
-  (interactive "sDial number: ")
-  (if ebdb-dial-function
-      (funcall ebdb-dial-function phone-string)
-    (browse-url (concat "tel:" phone-string))))
-
-;;;###autoload
-(defun ebdb-dial (phone force-area-code)
-  "Dial the number at point.
-If the point is at the beginning of a record, dial the first phone number.
-Use rules from `ebdb-dial-local-prefix-alist' unless prefix FORCE-AREA-CODE
-is non-nil.  Do not dial the extension."
-  (interactive (list (ebdb-current-field) current-prefix-arg))
-  (if (eq phone 'ebdb-record-name)
-      (setq phone (car (ebdb-record-phone (ebdb-current-record)))))
-  (or (and (eieio-object-p phone)
-          (object-of-class-p phone 'ebdb-field-phone))
-      (error "Not on a phone field"))
-
-  (let ((number (ebdb-string phone))
-        shortnumber)
-
-    ;; cut off the extension
-    (if (string-match "x[0-9]+$" number)
-        (setq number (substring number 0 (match-beginning 0))))
-
-    (unless force-area-code
-      (let ((alist ebdb-dial-local-prefix-alist) prefix)
-        (while (setq prefix (pop alist))
-          (if (string-match (concat "^" (eval (car prefix))) number)
-              (setq shortnumber (concat (cdr prefix)
-                                        (substring number (match-end 0)))
-                    alist nil)))))
-
-    (if shortnumber
-        (setq number shortnumber)
-
-      ;; This is terrifically Americanized...
-      ;; Leading 0 => local number (?)
-      (if (and ebdb-dial-local-prefix
-               (string-match "^0" number))
-          (setq number (concat ebdb-dial-local-prefix number)))
-
-      ;; Leading + => long distance/international number
-      (if (and ebdb-dial-long-distance-prefix
-               (string-match "^\+" number))
-          (setq number (concat ebdb-dial-long-distance-prefix " "
-                               (substring number 1)))))
-
-    (unless ebdb-silent
-      (message "Dialing %s" number))
-    (ebdb-dial-number number)))
+(defun ebdb-dial ()
+  "Dial the phone number under point, or the first number of record under 
point."
+  (interactive)
+  (let* ((rec (ebdb-current-record))
+        (phone (or (when (object-of-class-p (ebdb-current-field)
+                                            'ebdb-field-phone)
+                     (ebdb-current-field))
+                   (ebdb-record-phone rec)
+                   (error "No phone to dial"))))
+    (ebdb-field-phone-dial rec phone)))
+
+;; This function is in addition to the phone field method
+;; `ebdb-field-phone-signal-text', because that will only allow you to
+;; text the single number, while this will allow texting to multiple
+;; recipients.
+(defun ebdb-signal-text (sender records message attachments)
+  "Compose and send a text message using the Signal protocol.
+
+SENDER should be a phone number (with leading \"+\") to send
+from.  If `ebdb-record-self' is set, this record will be used as
+the sender, while RECORDS will be used as the list of recipients.
+In both cases, `ebdb-signal-get-number' will be used to find a
+usable number from the record.
+
+MESSAGE is the string to send as the body of the text message.
+ATTACHMENTS is a list of filenames to send as attachments on the
+message."
+  (interactive
+   (list (or (and ebdb-record-self
+                 (ebdb-signal-get-number
+                  (ebdb-gethash ebdb-record-self 'uuid)
+                  t))
+            (ebdb-read-string
+             "Number to send from (or set `ebdb-record-self'): "))
+        (ebdb-do-records)
+        (ebdb-read-string "Message contents: ")
+        (ebdb-loop-with-exit
+         (expand-file-name
+          (read-file-name "Attach file (C-g when done): "
+                          nil nil nil)))))
+  (let ((recipients
+        (delq nil (mapcar #'ebdb-signal-get-number records))))
+    (if ebdb-signal-program
+       (ebdb--signal-text sender message recipients attachments)
+      (message "Please set `ebdb-signal-program'"))))
 
 ;;; Adding urls
 
@@ -2600,8 +2580,8 @@ is non-nil.  Do not dial the extension."
                       (ebdb-read-string "URL label: "
                                         nil ebdb-url-label-list))))
   (let ((url-field (make-instance 'ebdb-field-url :url url :object-name 
label)))
-      (ebdb-record-insert-field record url-field 'fields)
-   (ebdb-display-records (list record))))
+    (ebdb-record-insert-field record url-field 'fields)
+    (ebdb-display-records (list record))))
 
 ;;; Copy to kill ring
 
diff --git a/ebdb-format.el b/ebdb-format.el
index 0c13d09..cd8ccc5 100644
--- a/ebdb-format.el
+++ b/ebdb-format.el
@@ -27,6 +27,8 @@
 ;;; Code:
 
 (require 'ebdb)
+(declare-function ebdb-do-records "ebdb-com")
+(declare-function ebdb-display-records "ebdb-com")
 ;; qp = quoted-printable, might not end up needing this.
 (require 'qp)
 
@@ -276,7 +278,7 @@ which formats them appropriately."
 (cl-defmethod ebdb-fmt-collect-fields ((fmt ebdb-formatter)
                                       (record ebdb-record-person)
                                       &optional field-list)
-  
+
   (with-slots (exclude include) fmt
     (with-slots (aka organizations relations) record
       (when (and aka
diff --git a/ebdb-gnorb.el b/ebdb-gnorb.el
index e3832af..eba3f16 100644
--- a/ebdb-gnorb.el
+++ b/ebdb-gnorb.el
@@ -106,7 +106,7 @@ message was received."
    (actions :initform '(("Follow link" . gnorb-ebdb-follow-link))))
   :human-readable "gnus messages")
 
-(defun gnorb-ebdb-follow-link (record field)
+(defun gnorb-ebdb-follow-link (_record _field)
   (when-let ((link (or
                    (get-text-property (point) 'gnorb-link)
                    (get-text-property
@@ -133,10 +133,10 @@ message was received."
         (sec (and delta (abs real-sec))))
     (floor (/ sec 86400))))
 
-(cl-defmethod ebdb-fmt-field ((fmt ebdb-formatter-ebdb)
+(cl-defmethod ebdb-fmt-field ((_fmt ebdb-formatter-ebdb)
                              (field gnorb-ebdb-field-messages)
                              _style
-                             (record ebdb-record))
+                             (_record ebdb-record))
   (let* ((msgs (slot-value field 'messages))
         (outstring
          (if (= (length msgs) 0)
diff --git a/ebdb-gnus.el b/ebdb-gnus.el
index fea53c6..f4d3e56 100644
--- a/ebdb-gnus.el
+++ b/ebdb-gnus.el
@@ -25,7 +25,8 @@
 
 (require 'ebdb-com)
 (require 'ebdb-mua)
-(require 'gnus)
+(require 'gnus-sum)
+(require 'gnus-art)
 
 (autoload 'message-make-domain "message")
 
diff --git a/ebdb-i18n.el b/ebdb-i18n.el
index 9293521..c9a4f01 100644
--- a/ebdb-i18n.el
+++ b/ebdb-i18n.el
@@ -21,11 +21,10 @@
 
 ;; This file contains extensions to EBDB, making it more
 ;; internationally aware.  It works by hijacking many of the common
-;; methods for field manipulation, by attaching :extra methods to
-;; them, and dispatching to new methods that allow for specialization
-;; on country codes, area codes, character scripts, etc.  The new
-;; methods are generally named the same as the old methods, plus a
-;; "-i18n" suffix.
+;; methods for field manipulation, attaching :extra methods to them,
+;; and dispatching to new methods that allow for specialization on
+;; country codes, area codes, character scripts, etc.  The new method
+;; names are the same as the old names, plus a "-i18n" suffix.
 
 ;; The methods in this file are only responsible for doing the
 ;; hijacking, and calling the i18n versions of the original methods --
@@ -54,438 +53,270 @@
   :type 'list
   :group 'ebdb-i18n)
 
-(cl-defgeneric ebdb-read-i18n (class slots obj spec)
-  "An internationalized version of `ebdb-read'.
-
-This works the same as `ebdb-read', plus an additional argument
-SPEC.  What SPEC is depends on CLASS, but might be a phone
-country code, or a country symbol, or a script symbol.
-
-This method should return a plist of slots for object creation.")
-
-(cl-defgeneric ebdb-parse-i18n (class string spec &optional slots)
-  "An internationalized version of `ebdb-parse'.
-
-This works the same as `ebdb-read', plus an additional argument
-SPEC.  What SPEC is depends on CLASS, but might be a phone
-country code, or a country symbol, or a script symbol.  SLOTS is
-a plist of existing slot values.
-
-This method should return a new instance of CLASS.")
-
-(cl-defgeneric ebdb-string-i18n (field spec)
-  "An internationalized version of `ebdb-string'.")
-
-(cl-defgeneric ebdb-init-field-i18n (field record spec)
-  "An internationalized version of `ebdb-init-field'.")
-
-(cl-defgeneric ebdb-delete-field-i18n (field record spec unload)
-  "An internationalized version of `ebdb-delete-field'.")
-
-(cl-defmethod ebdb-parse-i18n :around (_class _string _spec &optional _slots)
-  "Don't clobber match data when testing names."
-  (save-match-data
-    (cl-call-next-method)))
-
-;;;###autoload
-(defun ebdb-internationalize-addresses ()
-  "Go through all the EBDB contacts and \"internationalize\"
-  address fields.
-
-Essentially this just means swapping out the string country names
-for their symbol representations.")
-
-(cl-defmethod ebdb-read :extra "i18n" ((class (subclass ebdb-field-address))
-                                      &optional slots obj)
-  (let ((country
-        (cdr (assoc (completing-read
-                     "Country: "
-                     ebdb-i18n-countries nil nil
-                     (when obj (car (rassoc (ebdb-address-country obj)
-                                            ebdb-i18n-countries))))
-                    ebdb-i18n-countries))))
-    (setq slots
-         (condition-case nil
-             (ebdb-read-i18n class
-                             (plist-put slots :country country) obj country)
-           (cl-no-method
-            (plist-put slots :country country))))
-    (cl-call-next-method class slots obj)))
-
-(cl-defmethod ebdb-read :extra "i18n" ((class (subclass ebdb-field-phone))
-                                      &optional slots obj)
-  (let ((country
-        (if obj
-            (slot-value obj 'country-code)
-          (cdr (assoc (completing-read
-                       "Country/Region: "
-                       ebdb-i18n-phone-codes nil nil)
-                      ebdb-i18n-phone-codes))))
-         area-code)
-      ;; Obviously this whole structure thing is just poorly
-      ;; thought-out.
-      (when (consp country)
-       (cond ((numberp (car country))
-              (setq area-code (second country)
-                    country (car country)))
-             ((consp (car country))
-              (setq country (assoc
-                             (string-to-number
-                              (completing-read
-                               "Choose: "
-                             (mapcar (lambda (x)
-                                       (number-to-string (car x)))
-                                     country)))
-                           country)
-                   area-code (second country))))
-      (when (consp area-code)
-       (setq area-code (string-to-number
-                        (completing-read
-                         "Area code: "
-                         (mapcar #'number-to-string area-code))))))
-    (when area-code
-      (setq slots (plist-put slots :area-code area-code)))
-    (setq slots (plist-put slots :country-code country))
-    (setq slots
-         (condition-case nil
-             (ebdb-read-i18n class slots obj country)
-           (cl-no-method
-            slots)))
-    (cl-call-next-method class slots obj)))
-
-(cl-defmethod ebdb-string :extra "i18n" ((phone ebdb-field-phone))
-  "Internationally-aware version of `ebdb-string' for phones."
-  (let ((cc (slot-value phone 'country-code)))
-    (or (and cc
-            (condition-case nil
-                (ebdb-string-i18n phone cc)
-              (cl-no-method nil)))
-       (cl-call-next-method))))
-
-(cl-defmethod ebdb-parse :extra "i18n" ((class (subclass ebdb-field-phone))
-                                       (str string)
-                                       &optional slots)
-  (let ((cc (or (plist-get slots :country-code)
-               (and (string-match "\\`(?\\+(?\\([0-9]\\{1,3\\}\\))?[ \t]+" str)
-                    (string-to-number (match-string 1 str))))))
-    (or (and cc
-            (condition-case nil
-                (ebdb-parse-i18n
-                 class
-                 (replace-match "" nil nil str 0)
-                 cc (plist-put slots :country-code cc))
-              (cl-no-method nil)))
-       (cl-call-next-method))))
-
-;; We don't need to override the `ebdb-read' method for names.  It
-;; only matters what script the name is in if the user has set
-;; `ebdb-read-name-articulate' to nil, in which case the name is
-;; passed to this `ebdb-parse' method.
-(cl-defmethod ebdb-parse :extra "i18n" ((class (subclass 
ebdb-field-name-complex))
-                                       (string string)
-                                       &optional slots)
-  ;; For now, only test the first character of whatever string the
-  ;; user has entered.
-  (let ((script (unless (string-empty-p string)
-                 (aref char-script-table (aref string 0)))))
-    (or (and script
-            (null (memq script ebdb-i18n-ignorable-scripts))
-            (condition-case nil
-                (ebdb-parse-i18n class string script slots)
-              (cl-no-method
-               nil)))
-       (cl-call-next-method))))
-
-(cl-defmethod ebdb-string :extra "i18n" ((name ebdb-field-name-complex))
-  (let* ((str (cl-call-next-method name))
-        (script (aref char-script-table (aref str 0))))
-    (unless (memq script ebdb-i18n-ignorable-scripts)
-      (condition-case nil
-         (setq str (ebdb-string-i18n name script))
-       (cl-no-method nil)))
-    str))
-
-(cl-defmethod ebdb-init-field :extra "i18n" ((name ebdb-field-name) &optional 
record)
-  "Do additional initialization work for international names."
-  (let* ((res (cl-call-next-method name record))
-        (str (ebdb-string name))
-        (script (aref char-script-table (aref str 0))))
-    (unless (memq script ebdb-i18n-ignorable-scripts)
-      (condition-case nil
-         (ebdb-init-field-i18n name record script)
-       (cl-no-method nil)))
-    res))
-
-(cl-defmethod ebdb-delete-field :extra "i18n" ((name ebdb-field-name) 
&optional record unload)
-  "Do additional deletion work for international names."
-  (let* ((str (ebdb-string name))
-        (script (aref char-script-table (aref str 0))))
-    (unless (memq script ebdb-i18n-ignorable-scripts)
-      (condition-case nil
-         (ebdb-delete-field-i18n name record script unload)
-       (cl-no-method nil))))
-  (cl-call-next-method))
+;; defvars come first to pacify compiler.
 
+;; Taken from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3, on Feb
+;; 27, 2016
 (defvar ebdb-i18n-countries
   '(
-("Afghanistan" . afg)
-("Åland Islands" . ala)
-("Albania" . alb)
-("Algeria" . dza)
-("American Samoa" . asm)
-("Andorra" . and)
-("Angola" . ago)
-("Anguilla" . aia)
-("Antarctica" . ata)
-("Antigua and Barbuda" . atg)
-("Argentina" . arg)
-("Armenia" . arm)
-("Aruba" . abw)
-("Australia" . aus)
-("Austria" . aut)
-("Azerbaijan" . aze)
-("Bahamas" . bhs)
-("Bahrain" . bhr)
-("Bangladesh" . bgd)
-("Barbados" . brb)
-("Belarus" . blr)
-("Belgium" . bel)
-("Belize" . blz)
-("Benin" . ben)
-("Bermuda" . bmu)
-("Bhutan" . btn)
-("Bolivia" . bol)
-("Bonaire" . bes)
-("Sint Eustatius" . bes)
-("Saba" . bes)
-("Bosnia and Herzegovina" . bih)
-("Botswana" . bwa)
-("Bouvet Island" . bvt)
-("Brazil" . bra)
-("British Indian Ocean Territory" . iot)
-("Brunei Darussalam" . brn)
-("Bulgaria" . bgr)
-("Burkina Faso" . bfa)
-("Burundi" . bdi)
-("Cabo Verde" . cpv)
-("Cambodia" . khm)
-("Cameroon" . cmr)
-("Canada" . can)
-("Cayman Islands" . cym)
-("Central African Republic" . caf)
-("Chad" . tcd)
-("Chile" . chl)
-("China" . chn)
-("Christmas Island" . cxr)
-("Cocos (Keeling) Islands" . cck)
-("Colombia" . col)
-("Comoros" . com)
-("Congo" . cog)
-("Congo" . cod)
-("Cook Islands" . cok)
-("Costa Rica" . cri)
-("Côte d'Ivoire" . civ)
-("Croatia" . hrv)
-("Cuba" . cub)
-("Curaçao" . cuw)
-("Cyprus" . cyp)
-("Czech Republic" . cze)
-("Denmark" . dnk)
-("Djibouti" . dji)
-("Dominica" . dma)
-("Dominican Republic" . dom)
-("Ecuador" . ecu)
-("Egypt" . egy)
-("El Salvador" . slv)
-("Emacs" . emc)
-("Equatorial Guinea" . gnq)
-("Eritrea" . eri)
-("Estonia" . est)
-("Ethiopia" . eth)
-("Falkland Islands" . flk)
-("Faroe Islands" . fro)
-("Fiji" . fji)
-("Finland" . fin)
-("France" . fra)
-("French Guiana" . guf)
-("French Polynesia" . pyf)
-("French Southern Territories" . atf)
-("Gabon" . gab)
-("Gambia" . gmb)
-("Georgia" . geo)
-("Germany" . deu)
-("Ghana" . gha)
-("Gibraltar" . gib)
-("Greece" . grc)
-("Greenland" . grl)
-("Grenada" . grd)
-("Guadeloupe" . glp)
-("Guam" . gum)
-("Guatemala" . gtm)
-("Guernsey" . ggy)
-("Guinea" . gin)
-("Guinea-Bissau" . gnb)
-("Guyana" . guy)
-("Haiti" . hti)
-("Heard Island and McDonald Islands" . hmd)
-("Holy See" . vat)
-("Honduras" . hnd)
-("Hong Kong" . hkg)
-("Hungary" . hun)
-("Iceland" . isl)
-("India" . ind)
-("Indonesia" . idn)
-("Iran" . irn)
-("Iraq" . irq)
-("Ireland" . irl)
-("Isle of Man" . imn)
-("Israel" . isr)
-("Italy" . ita)
-("Jamaica" . jam)
-("Japan" . jpn)
-("Jersey" . jey)
-("Jordan" . jor)
-("Kazakhstan" . kaz)
-("Kenya" . ken)
-("Kiribati" . kir)
-("North Korea" . prk)
-("South Korea" . kor)
-("Kuwait" . kwt)
-("Kyrgyzstan" . kgz)
-("Lao People's Democratic Republic" . lao)
-("Latvia" . lva)
-("Lebanon" . lbn)
-("Lesotho" . lso)
-("Liberia" . lbr)
-("Libya" . lby)
-("Liechtenstein" . lie)
-("Lithuania" . ltu)
-("Luxembourg" . lux)
-("Macao" . mac)
-("Macedonia" . mkd)
-("Madagascar" . mdg)
-("Malawi" . mwi)
-("Malaysia" . mys)
-("Maldives" . mdv)
-("Mali" . mli)
-("Malta" . mlt)
-("Marshall Islands" . mhl)
-("Martinique" . mtq)
-("Mauritania" . mrt)
-("Mauritius" . mus)
-("Mayotte" . myt)
-("Mexico" . mex)
-("Micronesia" . fsm)
-("Moldova" . mda)
-("Monaco" . mco)
-("Mongolia" . mng)
-("Montenegro" . mne)
-("Montserrat" . msr)
-("Morocco" . mar)
-("Mozambique" . moz)
-("Myanmar" . mmr)
-("Namibia" . nam)
-("Nauru" . nru)
-("Nepal" . npl)
-("Netherlands" . nld)
-("New Caledonia" . ncl)
-("New Zealand" . nzl)
-("Nicaragua" . nic)
-("Niger" . ner)
-("Nigeria" . nga)
-("Niue" . niu)
-("Norfolk Island" . nfk)
-("Northern Mariana Islands" . mnp)
-("Norway" . nor)
-("Oman" . omn)
-("Pakistan" . pak)
-("Palau" . plw)
-("Palestine" . pse)
-("Panama" . pan)
-("Papua New Guinea" . png)
-("Paraguay" . pry)
-("Peru" . per)
-("Philippines" . phl)
-("Pitcairn" . pcn)
-("Poland" . pol)
-("Portugal" . prt)
-("Puerto Rico" . pri)
-("Qatar" . qat)
-("Réunion" . reu)
-("Romania" . rou)
-("Russian Federation" . rus)
-("Rwanda" . rwa)
-("Saint Barthélemy" . blm)
-("Saint Helena, Ascension and Tristan da Cunha" . shn)
-("Saint Kitts and Nevis" . kna)
-("Saint Lucia" . lca)
-("Saint Martin" . maf)
-("Saint Pierre and Miquelon" . spm)
-("Saint Vincent and the Grenadines" . vct)
-("Samoa" . wsm)
-("San Marino" . smr)
-("Sao Tome and Principe" . stp)
-("Saudi Arabia" . sau)
-("Senegal" . sen)
-("Serbia" . srb)
-("Seychelles" . syc)
-("Sierra Leone" . sle)
-("Singapore" . sgp)
-("Sint Maarten" . sxm)
-("Slovakia" . svk)
-("Slovenia" . svn)
-("Solomon Islands" . slb)
-("Somalia" . som)
-("South Africa" . zaf)
-("South Georgia and the South Sandwich Islands" . sgs)
-("South Sudan" . ssd)
-("Spain" . esp)
-("Sri Lanka" . lka)
-("Sudan" . sdn)
-("Suriname" . sur)
-("Svalbard and Jan Mayen" . sjm)
-("Swaziland" . swz)
-("Sweden" . swe)
-("Switzerland" . che)
-("Syrian Arab Republic" . syr)
-("Taiwan" . twn)
-("Tajikistan" . tjk)
-("Tanzania" . tza)
-("Thailand" . tha)
-("Timor-Leste" . tls)
-("Togo" . tgo)
-("Tokelau" . tkl)
-("Tonga" . ton)
-("Trinidad and Tobago" . tto)
-("Tunisia" . tun)
-("Turkey" . tur)
-("Turkmenistan" . tkm)
-("Turks and Caicos Islands" . tca)
-("Tuvalu" . tuv)
-("Uganda" . uga)
-("Ukraine" . ukr)
-("United Arab Emirates" . are)
-("United Kingdom of Great Britain and Northern Ireland" . gbr)
-("United States of America" . usa)
-("United States Minor Outlying Islands" . umi)
-("Uruguay" . ury)
-("Uzbekistan" . uzb)
-("Vanuatu" . vut)
-("Venezuela" . ven)
-("Viet Nam" . vnm)
-("Virgin Islands (British)" . vgb)
-("Virgin Islands (U.S.)" . vir)
-("Wallis and Futuna" . wlf)
-("Western Sahara" . esh)
-("Yemen" . yem)
-("Zambia" . zmb)
-("Zimbabwe" . zwe))
+    ("Afghanistan" . afg)
+    ("Åland Islands" . ala)
+    ("Albania" . alb)
+    ("Algeria" . dza)
+    ("American Samoa" . asm)
+    ("Andorra" . and)
+    ("Angola" . ago)
+    ("Anguilla" . aia)
+    ("Antarctica" . ata)
+    ("Antigua and Barbuda" . atg)
+    ("Argentina" . arg)
+    ("Armenia" . arm)
+    ("Aruba" . abw)
+    ("Australia" . aus)
+    ("Austria" . aut)
+    ("Azerbaijan" . aze)
+    ("Bahamas" . bhs)
+    ("Bahrain" . bhr)
+    ("Bangladesh" . bgd)
+    ("Barbados" . brb)
+    ("Belarus" . blr)
+    ("Belgium" . bel)
+    ("Belize" . blz)
+    ("Benin" . ben)
+    ("Bermuda" . bmu)
+    ("Bhutan" . btn)
+    ("Bolivia" . bol)
+    ("Bonaire" . bes)
+    ("Sint Eustatius" . bes)
+    ("Saba" . bes)
+    ("Bosnia and Herzegovina" . bih)
+    ("Botswana" . bwa)
+    ("Bouvet Island" . bvt)
+    ("Brazil" . bra)
+    ("British Indian Ocean Territory" . iot)
+    ("Brunei Darussalam" . brn)
+    ("Bulgaria" . bgr)
+    ("Burkina Faso" . bfa)
+    ("Burundi" . bdi)
+    ("Cabo Verde" . cpv)
+    ("Cambodia" . khm)
+    ("Cameroon" . cmr)
+    ("Canada" . can)
+    ("Cayman Islands" . cym)
+    ("Central African Republic" . caf)
+    ("Chad" . tcd)
+    ("Chile" . chl)
+    ("China" . chn)
+    ("Christmas Island" . cxr)
+    ("Cocos (Keeling) Islands" . cck)
+    ("Colombia" . col)
+    ("Comoros" . com)
+    ("Congo" . cog)
+    ("Congo" . cod)
+    ("Cook Islands" . cok)
+    ("Costa Rica" . cri)
+    ("Côte d'Ivoire" . civ)
+    ("Croatia" . hrv)
+    ("Cuba" . cub)
+    ("Curaçao" . cuw)
+    ("Cyprus" . cyp)
+    ("Czech Republic" . cze)
+    ("Denmark" . dnk)
+    ("Djibouti" . dji)
+    ("Dominica" . dma)
+    ("Dominican Republic" . dom)
+    ("Ecuador" . ecu)
+    ("Egypt" . egy)
+    ("El Salvador" . slv)
+    ("Emacs" . emc)
+    ("Equatorial Guinea" . gnq)
+    ("Eritrea" . eri)
+    ("Estonia" . est)
+    ("Ethiopia" . eth)
+    ("Falkland Islands" . flk)
+    ("Faroe Islands" . fro)
+    ("Fiji" . fji)
+    ("Finland" . fin)
+    ("France" . fra)
+    ("French Guiana" . guf)
+    ("French Polynesia" . pyf)
+    ("French Southern Territories" . atf)
+    ("Gabon" . gab)
+    ("Gambia" . gmb)
+    ("Georgia" . geo)
+    ("Germany" . deu)
+    ("Ghana" . gha)
+    ("Gibraltar" . gib)
+    ("Greece" . grc)
+    ("Greenland" . grl)
+    ("Grenada" . grd)
+    ("Guadeloupe" . glp)
+    ("Guam" . gum)
+    ("Guatemala" . gtm)
+    ("Guernsey" . ggy)
+    ("Guinea" . gin)
+    ("Guinea-Bissau" . gnb)
+    ("Guyana" . guy)
+    ("Haiti" . hti)
+    ("Heard Island and McDonald Islands" . hmd)
+    ("Holy See" . vat)
+    ("Honduras" . hnd)
+    ("Hong Kong" . hkg)
+    ("Hungary" . hun)
+    ("Iceland" . isl)
+    ("India" . ind)
+    ("Indonesia" . idn)
+    ("Iran" . irn)
+    ("Iraq" . irq)
+    ("Ireland" . irl)
+    ("Isle of Man" . imn)
+    ("Israel" . isr)
+    ("Italy" . ita)
+    ("Jamaica" . jam)
+    ("Japan" . jpn)
+    ("Jersey" . jey)
+    ("Jordan" . jor)
+    ("Kazakhstan" . kaz)
+    ("Kenya" . ken)
+    ("Kiribati" . kir)
+    ("North Korea" . prk)
+    ("South Korea" . kor)
+    ("Kuwait" . kwt)
+    ("Kyrgyzstan" . kgz)
+    ("Lao People's Democratic Republic" . lao)
+    ("Latvia" . lva)
+    ("Lebanon" . lbn)
+    ("Lesotho" . lso)
+    ("Liberia" . lbr)
+    ("Libya" . lby)
+    ("Liechtenstein" . lie)
+    ("Lithuania" . ltu)
+    ("Luxembourg" . lux)
+    ("Macao" . mac)
+    ("Macedonia" . mkd)
+    ("Madagascar" . mdg)
+    ("Malawi" . mwi)
+    ("Malaysia" . mys)
+    ("Maldives" . mdv)
+    ("Mali" . mli)
+    ("Malta" . mlt)
+    ("Marshall Islands" . mhl)
+    ("Martinique" . mtq)
+    ("Mauritania" . mrt)
+    ("Mauritius" . mus)
+    ("Mayotte" . myt)
+    ("Mexico" . mex)
+    ("Micronesia" . fsm)
+    ("Moldova" . mda)
+    ("Monaco" . mco)
+    ("Mongolia" . mng)
+    ("Montenegro" . mne)
+    ("Montserrat" . msr)
+    ("Morocco" . mar)
+    ("Mozambique" . moz)
+    ("Myanmar" . mmr)
+    ("Namibia" . nam)
+    ("Nauru" . nru)
+    ("Nepal" . npl)
+    ("Netherlands" . nld)
+    ("New Caledonia" . ncl)
+    ("New Zealand" . nzl)
+    ("Nicaragua" . nic)
+    ("Niger" . ner)
+    ("Nigeria" . nga)
+    ("Niue" . niu)
+    ("Norfolk Island" . nfk)
+    ("Northern Mariana Islands" . mnp)
+    ("Norway" . nor)
+    ("Oman" . omn)
+    ("Pakistan" . pak)
+    ("Palau" . plw)
+    ("Palestine" . pse)
+    ("Panama" . pan)
+    ("Papua New Guinea" . png)
+    ("Paraguay" . pry)
+    ("Peru" . per)
+    ("Philippines" . phl)
+    ("Pitcairn" . pcn)
+    ("Poland" . pol)
+    ("Portugal" . prt)
+    ("Puerto Rico" . pri)
+    ("Qatar" . qat)
+    ("Réunion" . reu)
+    ("Romania" . rou)
+    ("Russian Federation" . rus)
+    ("Rwanda" . rwa)
+    ("Saint Barthélemy" . blm)
+    ("Saint Helena, Ascension and Tristan da Cunha" . shn)
+    ("Saint Kitts and Nevis" . kna)
+    ("Saint Lucia" . lca)
+    ("Saint Martin" . maf)
+    ("Saint Pierre and Miquelon" . spm)
+    ("Saint Vincent and the Grenadines" . vct)
+    ("Samoa" . wsm)
+    ("San Marino" . smr)
+    ("Sao Tome and Principe" . stp)
+    ("Saudi Arabia" . sau)
+    ("Senegal" . sen)
+    ("Serbia" . srb)
+    ("Seychelles" . syc)
+    ("Sierra Leone" . sle)
+    ("Singapore" . sgp)
+    ("Sint Maarten" . sxm)
+    ("Slovakia" . svk)
+    ("Slovenia" . svn)
+    ("Solomon Islands" . slb)
+    ("Somalia" . som)
+    ("South Africa" . zaf)
+    ("South Georgia and the South Sandwich Islands" . sgs)
+    ("South Sudan" . ssd)
+    ("Spain" . esp)
+    ("Sri Lanka" . lka)
+    ("Sudan" . sdn)
+    ("Suriname" . sur)
+    ("Svalbard and Jan Mayen" . sjm)
+    ("Swaziland" . swz)
+    ("Sweden" . swe)
+    ("Switzerland" . che)
+    ("Syrian Arab Republic" . syr)
+    ("Taiwan" . twn)
+    ("Tajikistan" . tjk)
+    ("Tanzania" . tza)
+    ("Thailand" . tha)
+    ("Timor-Leste" . tls)
+    ("Togo" . tgo)
+    ("Tokelau" . tkl)
+    ("Tonga" . ton)
+    ("Trinidad and Tobago" . tto)
+    ("Tunisia" . tun)
+    ("Turkey" . tur)
+    ("Turkmenistan" . tkm)
+    ("Turks and Caicos Islands" . tca)
+    ("Tuvalu" . tuv)
+    ("Uganda" . uga)
+    ("Ukraine" . ukr)
+    ("United Arab Emirates" . are)
+    ("United Kingdom of Great Britain and Northern Ireland" . gbr)
+    ("United States of America" . usa)
+    ("United States Minor Outlying Islands" . umi)
+    ("Uruguay" . ury)
+    ("Uzbekistan" . uzb)
+    ("Vanuatu" . vut)
+    ("Venezuela" . ven)
+    ("Viet Nam" . vnm)
+    ("Virgin Islands (British)" . vgb)
+    ("Virgin Islands (U.S.)" . vir)
+    ("Wallis and Futuna" . wlf)
+    ("Western Sahara" . esh)
+    ("Yemen" . yem)
+    ("Zambia" . zmb)
+    ("Zimbabwe" . zwe))
   "Mapping between a string label for countries or regions, in
 English, and a three-letter symbol identifying the country, as
 per ISO 3166-1 alpha 3.")
-;; Taken from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3, on Feb
-;; 27, 2016
 
+;; Taken from https://en.wikipedia.org/wiki/Telephone_country_codes,
+;; on Jul 30, 2016
 (defvar ebdb-i18n-phone-codes
   '(
     ;; Need a different way of doing this.
@@ -764,9 +595,179 @@ per ISO 3166-1 alpha 3.")
 ("Zambia" . 260)
 ("Zanzibar" . 255)
 ("Zimbabwe" .  263))
-"Mapping of country names to country-code numbers.")
-;; Taken from https://en.wikipedia.org/wiki/Telephone_country_codes,
-;; on Jul 30, 2016
+  "Mapping of country names to country-code numbers.")
+
+(cl-defgeneric ebdb-read-i18n (class slots obj spec)
+  "An internationalized version of `ebdb-read'.
+
+This works the same as `ebdb-read', plus an additional argument
+SPEC.  What SPEC is depends on CLASS, but might be a phone
+country code, or a country symbol, or a script symbol.
+
+This method should return a plist of slots for object creation.")
+
+(cl-defgeneric ebdb-parse-i18n (class string spec &optional slots)
+  "An internationalized version of `ebdb-parse'.
+
+This works the same as `ebdb-read', plus an additional argument
+SPEC.  What SPEC is depends on CLASS, but might be a phone
+country code, or a country symbol, or a script symbol.  SLOTS is
+a plist of existing slot values.
+
+This method should return a new instance of CLASS.")
+
+(cl-defgeneric ebdb-string-i18n (field spec)
+  "An internationalized version of `ebdb-string'.")
+
+(cl-defgeneric ebdb-init-field-i18n (field record spec)
+  "An internationalized version of `ebdb-init-field'.")
+
+(cl-defgeneric ebdb-delete-field-i18n (field record spec unload)
+  "An internationalized version of `ebdb-delete-field'.")
+
+(cl-defmethod ebdb-parse-i18n :around (_class _string _spec &optional _slots)
+  "Don't clobber match data when testing names."
+  (save-match-data
+    (cl-call-next-method)))
+
+;;;###autoload
+(defun ebdb-internationalize-addresses ()
+  "Go through all the EBDB contacts and \"internationalize\"
+  address fields.
+
+Essentially this just means swapping out the string country names
+for their symbol representations.")
+
+(cl-defmethod ebdb-read :extra "i18n" ((class (subclass ebdb-field-address))
+                                      &optional slots obj)
+  (let ((country
+        (cdr (assoc (completing-read
+                     "Country: "
+                     ebdb-i18n-countries nil nil
+                     (when obj (car (rassoc (ebdb-address-country obj)
+                                            ebdb-i18n-countries))))
+                    ebdb-i18n-countries))))
+    (setq slots
+         (condition-case nil
+             (ebdb-read-i18n class
+                             (plist-put slots :country country) obj country)
+           (cl-no-method
+            (plist-put slots :country country))))
+    (cl-call-next-method class slots obj)))
+
+(cl-defmethod ebdb-read :extra "i18n" ((class (subclass ebdb-field-phone))
+                                      &optional slots obj)
+  (let ((country
+        (if obj
+            (slot-value obj 'country-code)
+          (cdr (assoc (completing-read
+                       "Country/Region: "
+                       ebdb-i18n-phone-codes nil nil)
+                      ebdb-i18n-phone-codes))))
+         area-code)
+      ;; Obviously this whole structure thing is just poorly
+      ;; thought-out.
+      (when (consp country)
+       (cond ((numberp (car country))
+              (setq area-code (cl-second country)
+                    country (car country)))
+             ((consp (car country))
+              (setq country (assoc
+                             (string-to-number
+                              (completing-read
+                               "Choose: "
+                             (mapcar (lambda (x)
+                                       (number-to-string (car x)))
+                                     country)))
+                           country)
+                   area-code (cl-second country))))
+      (when (consp area-code)
+       (setq area-code (string-to-number
+                        (completing-read
+                         "Area code: "
+                         (mapcar #'number-to-string area-code))))))
+    (when area-code
+      (setq slots (plist-put slots :area-code area-code)))
+    (setq slots (plist-put slots :country-code country))
+    (setq slots
+         (condition-case nil
+             (ebdb-read-i18n class slots obj country)
+           (cl-no-method
+            slots)))
+    (cl-call-next-method class slots obj)))
+
+(cl-defmethod ebdb-string :extra "i18n" ((phone ebdb-field-phone))
+  "Internationally-aware version of `ebdb-string' for phones."
+  (let ((cc (slot-value phone 'country-code)))
+    (or (and cc
+            (condition-case nil
+                (ebdb-string-i18n phone cc)
+              (cl-no-method nil)))
+       (cl-call-next-method))))
+
+(cl-defmethod ebdb-parse :extra "i18n" ((class (subclass ebdb-field-phone))
+                                       (str string)
+                                       &optional slots)
+  (let ((cc (or (plist-get slots :country-code)
+               (and (string-match "\\`(?\\+(?\\([0-9]\\{1,3\\}\\))?[ \t]+" str)
+                    (string-to-number (match-string 1 str))))))
+    (or (and cc
+            (condition-case nil
+                (ebdb-parse-i18n
+                 class
+                 (replace-match "" nil nil str 0)
+                 cc (plist-put slots :country-code cc))
+              (cl-no-method nil)))
+       (cl-call-next-method))))
+
+;; We don't need to override the `ebdb-read' method for names.  It
+;; only matters what script the name is in if the user has set
+;; `ebdb-read-name-articulate' to nil, in which case the name is
+;; passed to this `ebdb-parse' method.
+(cl-defmethod ebdb-parse :extra "i18n" ((class (subclass 
ebdb-field-name-complex))
+                                       (string string)
+                                       &optional slots)
+  ;; For now, only test the first character of whatever string the
+  ;; user has entered.
+  (let ((script (unless (string-empty-p string)
+                 (aref char-script-table (aref string 0)))))
+    (or (and script
+            (null (memq script ebdb-i18n-ignorable-scripts))
+            (condition-case nil
+                (ebdb-parse-i18n class string script slots)
+              (cl-no-method
+               nil)))
+       (cl-call-next-method))))
+
+(cl-defmethod ebdb-string :extra "i18n" ((name ebdb-field-name-complex))
+  (let* ((str (cl-call-next-method name))
+        (script (aref char-script-table (aref str 0))))
+    (unless (memq script ebdb-i18n-ignorable-scripts)
+      (condition-case nil
+         (setq str (ebdb-string-i18n name script))
+       (cl-no-method nil)))
+    str))
+
+(cl-defmethod ebdb-init-field :extra "i18n" ((name ebdb-field-name) &optional 
record)
+  "Do additional initialization work for international names."
+  (let* ((res (cl-call-next-method name record))
+        (str (ebdb-string name))
+        (script (aref char-script-table (aref str 0))))
+    (unless (memq script ebdb-i18n-ignorable-scripts)
+      (condition-case nil
+         (ebdb-init-field-i18n name record script)
+       (cl-no-method nil)))
+    res))
+
+(cl-defmethod ebdb-delete-field :extra "i18n" ((name ebdb-field-name) 
&optional record unload)
+  "Do additional deletion work for international names."
+  (let* ((str (ebdb-string name))
+        (script (aref char-script-table (aref str 0))))
+    (unless (memq script ebdb-i18n-ignorable-scripts)
+      (condition-case nil
+         (ebdb-delete-field-i18n name record script unload)
+       (cl-no-method nil))))
+  (cl-call-next-method))
 
 (provide 'ebdb-i18n)
 ;;; ebdb-i18n.el ends here
diff --git a/ebdb-message.el b/ebdb-message.el
index c7210f7..8bd304e 100644
--- a/ebdb-message.el
+++ b/ebdb-message.el
@@ -28,6 +28,8 @@
 (require 'message)
 (require 'sendmail)
 
+(defvar gnus-window-to-buffer)
+
 (defgroup ebdb-mua-message nil
   "Message-specific EBDB customizations"
   :group 'ebdb-mua)
diff --git a/ebdb-migrate.el b/ebdb-migrate.el
index 493b1df..a492a66 100644
--- a/ebdb-migrate.el
+++ b/ebdb-migrate.el
@@ -251,7 +251,7 @@ Formats are changed in timestamp and creation-date fields 
from
                      (string-to-number (match-string 2 date))
                      (string-to-number (match-string 3 date))))
         ;; This should be fairly loud for GNU Emacs users
-        (ebdb-warn "EBDB is treating %s field value %s as %s %d %d"
+        (message "EBDB is treating %s field value %s as %s %d %d"
                (car field) (cdr field)
                (upcase-initials
                 (downcase (car (rassoc (aref parsed 1)
@@ -263,7 +263,7 @@ Formats are changed in timestamp and creation-date fields 
from
                      (string-to-number (match-string 1 date))
                      (string-to-number (match-string 2 date))))
         ;; This should be fairly loud for GNU Emacs users
-        (ebdb-warn "EBDB is treating %s field value %s as %s %d %d"
+        (message "EBDB is treating %s field value %s as %s %d %d"
                (car field) (cdr field)
                (upcase-initials
                 (downcase (car (rassoc (aref parsed 1)
@@ -651,7 +651,7 @@ holding valid contacts in a previous BBDB format."
 
         ;; Migrate if `bbdb-file' is outdated.
         (if migrate (setq records (ebdb-migrate records file-format)))
-       
+
        records))))
 
 (provide 'ebdb-migrate)
diff --git a/ebdb-mua.el b/ebdb-mua.el
index ac5eeb6..75750b5 100644
--- a/ebdb-mua.el
+++ b/ebdb-mua.el
@@ -49,6 +49,8 @@
 (require 'ebdb-com)
 
 (autoload 'ebdb-snarf "ebdb-snarf")
+(autoload 'message-goto-cc "message")
+(autoload 'mail-cc "sendmail")
 
 (eval-and-compile
   (autoload 'mail-decode-encoded-word-string "mail-parse"))
diff --git a/ebdb-org.el b/ebdb-org.el
index 9489196..07ebe09 100644
--- a/ebdb-org.el
+++ b/ebdb-org.el
@@ -52,7 +52,8 @@
 
 ;;; Code:
 
-(require 'ebdb)
+(require 'ebdb-com)
+(require 'org)
 
 (if (fboundp 'org-link-set-parameters)
     (org-link-set-parameters "ebdb"
diff --git a/ebdb-vcard.el b/ebdb-vcard.el
index 14d2e2c..68d5a4b 100644
--- a/ebdb-vcard.el
+++ b/ebdb-vcard.el
@@ -29,6 +29,8 @@
 
 (require 'ebdb-format)
 
+(autoload 'calendar-gregorian-from-absolute "calendar")
+
 (defclass ebdb-formatter-vcard (ebdb-formatter)
   ((coding-system :initform 'utf-8-dos)
    (version-string
diff --git a/ebdb.el b/ebdb.el
index 8acba96..1f02a10 100644
--- a/ebdb.el
+++ b/ebdb.el
@@ -55,13 +55,18 @@
 (autoload 'widget-group-match "wid-edit")
 (autoload 'ebdb-migrate-from-bbdb "ebdb-migrate")
 (autoload 'eieio-customize-object "eieio-custom")
-(autoload 'calendar-gregorian-from-absolute "calendar")
 (autoload 'calendar-absolute-from-gregorian "calendar")
 (autoload 'calendar-make-alist "calendar")
+(autoload 'calendar-goto-date "calendar")
+(autoload 'calendar-last-day-of-month "calendar")
+(autoload 'calendar- "calendar")
+(autoload 'calendar-read "calendar")
+(autoload 'calendar-read-date "calendar")
 (autoload 'diary-sexp-entry "diary-lib")
 (autoload 'diary-add-to-list "diary-lib")
 (autoload 'org-agenda-list "org-agenda")
 (defvar ebdb-i18n-countries)
+(defvar calendar-month-name-array)
 
 ;; These are the most important internal variables, holding EBDB's
 ;; data structures.
@@ -409,254 +414,609 @@ will match \"ì\", and so on.  This may slow searching 
down."
   :group 'ebdb-search
   :type 'boolean)
 
+(defcustom ebdb-hash-extra-predicates nil
+  "Extra predicates when looking up entries in the EBDB hashtable.
+
+Predicates are used to filter results from the hashtable,
+ensuring that string lookups only return the results they're
+meant to.
+
+This option should be a list of conses, where the car is a
+symbol, and the cdr is a lambda form which accepts the string key
+and a record, and returns t if the key is acceptable for
+returning that record."
+  :group 'ebdb-search
+  :package-version "0.2"
+  :type '(repeat (cons symbol functionp)))
+
+(defcustom ebdb-signal-program (executable-find "signal-cli")
+  "The name of the signal-cli program, if installed.
+
+This program must be present in order to send text messages
+through the Signal service."
+  :group 'ebdb-utilities-dialing
+  :type 'string)
+
 (defcustom ebdb-info-file nil
   "Location of the ebdb info file, if it's not in the standard place."
   :group 'ebdb
   :type '(choice (const :tag "Standard location" nil)
                  (file :tag "Nonstandard location")))
 
-(defvar ebdb-update-unchanged-records nil
-  "If non-nil update unchanged records in the database.
-Normally calls of `ebdb-change-hook' and updating of a record are suppressed,
-if an editing command did not really change the record.  Bind this to t
-if you want to call `ebdb-change-hook' and update the record unconditionally.")
-
-(defvar ebdb-street-list nil
-  "List of streets known to EBDB.")
+(defcustom ebdb-canonical-hosts
+  ;; Example
+  (regexp-opt '("cs.cmu.edu" "ri.cmu.edu"))
+  "Regexp matching the canonical part of the domain part of a mail address.
+If the domain part of a mail address matches this regexp, the domain
+is replaced by the substring that actually matched this address.
 
-(defvar ebdb-locality-list nil
-  "List of localities (towns or cities) known to EBDB.")
+Used by  `ebdb-canonicalize-mail-1'.  See also `ebdb-ignore-redundant-mails'."
+  :group 'ebdb-utilities
+  :type '(regexp :tag "Regexp matching sites"))
 
-(defvar ebdb-region-list nil
-  "List of regions (states or provinces) known to EBDB.")
+(defcustom ebdb-canonicalize-mail-function nil
+  "If non-nil, it should be a function of one arg: a mail address string.
+When EBDB is parsing mail addresses, the corresponding mail
+addresses are passed to this function first.  It acts as a kind
+of \"filter\" to transform the mail addresses before they are
+compared against or added to the database.  See
+`ebdb-canonicalize-mail-1' for a more complete example.  If this
+function returns nil, EBDB assumes that there is no mail address.
 
-(defvar ebdb-postcode-list nil
-  "List of post codes known to EBDB.")
+See also `ebdb-ignore-redundant-mails'."
+  :group 'ebdb-utilities
+  :type 'function)
 
-;;; Define some of our own errors. A few of these should never be
-;;; shown to the user, they're for internal flow control.
+(defcustom ebdb-message-clean-name-function 'ebdb-message-clean-name-default
+  "Function to clean up the name in the header of a message.
+It takes one argument, the name as extracted by
+`mail-extract-address-components'."
+  :group 'ebdb-utilities
+  :type 'function)
 
-;; Error parent
-(define-error 'ebdb-error "EBDB error")
+;;; Record editing
 
-(define-error 'ebdb-duplicate-uuid "Duplicate EBDB UUID" 'ebdb-error)
+;; The following two options should be obviated by ebdb-i18n.el
+;; See http://en.wikipedia.org/wiki/Postal_address
+;; 
http://www.upu.int/en/activities/addressing/postal-addressing-systems-in-member-countstateries.html
+(defcustom ebdb-address-format-list
+  '(((arg) "splrc" "@address@hidden, @%l@, address@hidden@" "@%l@")
+    ((aus) "slrpc" "@address@hidden@ %r@ address@hidden@" "@%l@")
+    ((aut due esp che)
+     "splrc" "@address@hidden @%l@ (%r)@\n%c@" "@%l@")
+    ((can) "slrcp" "@address@hidden@, address@hidden@ %p@" "@%l@")
+    ((chn) "slprc" "@address@hidden@\n%p@ address@hidden@" "@%l@") ; English 
format
+    ; (("China") "cprls" "@%c @address@hidden @%l@ %s@" "@%l@") ; Chinese 
format
+    ((ind) "slprc" "@address@hidden@ %p@ (%r)@\n%c@" "@%l@")
+    ((usa) "slrpc" "@address@hidden@, %r@ address@hidden@" "@%l@")
+    (t ebdb-edit-address-default ebdb-format-address-default "@%l@"))
+  "List of address editing and formatting rules for EBDB.
+Each rule is a list (IDENTIFIER EDIT FORMAT FORMAT).
+The first rule for which IDENTIFIER matches an address is used for editing
+and formatting the address.
 
-(define-error 'ebdb-unsynced-db "EBDB DB unsynced" 'ebdb-error)
+IDENTIFIER may be a list of countries.
+IDENTIFIER may also be a function that is called with one arg, the address
+to be used.  The rule applies if the function returns non-nil.
+See `ebdb-address-continental-p' for an example.
+If IDENTIFIER is t, this rule always applies.  Usually, this should be
+the last rule that becomes a fall-back (default).
 
-(define-error 'ebdb-disabled-db "EBDB DB disabled" 'ebdb-error)
+EDIT may be a function that is called with one argument, the address.
+See `ebdb-edit-address-default' for an example.
 
-(define-error 'ebdb-readonly-db "EBDB DB read-only" 'ebdb-error)
+EDIT may also be an editting format string.  It is a string containing
+the five letters s, c, p, S, and C that specify the order for editing
+the five elements of an address:
 
-(define-error 'ebdb-unacceptable-field "EBDB record cannot accept field" 
'ebdb-error)
+s  streets
+l  locality
+p  postcode
+r  region
+c  country
 
-(define-error 'ebdb-empty "Empty value" 'ebdb-error)
+The first FORMAT of each rule is used for multi-line layout, the second FORMAT
+is used for one-line layout.
 
-(define-error 'ebdb-unparseable "Unparseable value" 'ebdb-error)
+FORMAT may be a function that is called with one argument, the address.
+See `ebdb-format-address-default' for an example.
 
-;;; Utility functions and macros
+FORMAT may also be a format string.  It consists of formatting elements
+separated by a delimiter defined via the first (and last) character of FORMAT.
+Each formatting element may contain one of the following format specifiers:
 
-;;;###autoload
-(defsubst ebdb-records (&optional record-class child-p)
-  "Return a list of all EBDB records; load databases if necessary.
-This function also notices if databases are out of sync.
+%s  streets (used repeatedly for each street part)
+%l  locality
+%p  postcode
+%r  region
+%c  country
 
-If RECORD-CLASS is given, only return records of this class or,
-if CHILD-P is non-nil, one of its subclasses."
-  (unless ebdb-db-list
-    (ebdb-load))
-  (if record-class
-      (seq-filter
-       (lambda (r)
-        (if child-p
-            (object-of-class-p r record-class)
-          (same-class-p r record-class)))
-       ebdb-record-tracker)
-   ebdb-record-tracker))
+A formatting element will be applied only if the corresponding part
+of the address is a non-empty string.
 
-(defmacro ebdb-error-retry (&rest body)
-  "Repeatedly execute BODY ignoring errors till no error occurs."
-  `(catch '--ebdb-error-retry--
-     (while t
-       (condition-case --c--
-           (throw '--ebdb-error-retry-- (progn ,@body))
-         (ebdb-unparseable
-         (ding)
-         (message "Error: %s" (nth 1 --c--))
-         (sit-for 2))))))
+See also `ebdb-print-address-format-list'."
+  :group 'ebdb-record-edit
+  :type '(repeat (list (choice (const :tag "Default" t)
+                               (function :tag "Function")
+                               (repeat (string)))
+                       (choice (string)
+                               (function :tag "Function"))
+                       (choice (string)
+                               (function :tag "Function"))
+                       (choice (string)
+                               (function :tag "Function")))))
 
-(defmacro ebdb-with-exit (&rest body)
-  `(condition-case nil
-       ,@body
-     ((quit ebdb-empty)
-      nil)))
+(defcustom ebdb-continental-postcode-regexp
+  "^\\s *[A-Z][A-Z]?\\s *-\\s *[0-9][0-9][0-9]"
+  "Regexp matching continental postcodes.
+Used by address format identifier `ebdb-address-continental-p'.
+The regexp should match postcodes of the form CH-8052, NL-2300RA,
+and SE-132 54."
+  :group 'ebdb-record-edit
+  :type 'regexp)
 
-(defmacro ebdb-loop-with-exit (&rest body)
-  "Repeat BODY, accumulating the results in a list, until the
-user either hits C-g, or enters an empty field label."
-  `(let (acc)
-     (catch '--ebdb-loop-exit--
-       (condition-case nil
-          (while t
-            (push ,@body acc))
-        ((quit ebdb-empty)
-         (throw '--ebdb-loop-exit-- acc))))))
+(defcustom ebdb-legal-postcodes
+  '(;; empty string
+    "^$"
+    ;; Matches 1 to 6 digits.
+    "^[ \t\n]*[0-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[ \t\n]*$"
+    ;; Matches 5 digits and 3 or 4 digits.
+    "^[ \t\n]*\\([0-9][0-9][0-9][0-9][0-9]\\)[ \t\n]*-?[ 
\t\n]*\\([0-9][0-9][0-9][0-9]?\\)[ \t\n]*$"
+    ;; Match postcodes for Canada, UK, etc. (result is ("LL47" "U4B")).
+    "^[ \t\n]*\\([A-Za-z0-9]+\\)[ \t\n]+\\([A-Za-z0-9]+\\)[ \t\n]*$"
+    ;; Match postcodes for continental Europe.  Examples "CH-8057"
+    ;; or "F - 83320" (result is ("CH" "8057") or ("F" "83320")).
+    ;; Support for "NL-2300RA" added at request from Carsten Dominik
+    ;; <address@hidden>
+    "^[ \t\n]*\\([A-Z]+\\)[ \t\n]*-?[ \t\n]*\\([0-9]+ ?[A-Z]*\\)[ \t\n]*$"
+    ;; Match postcodes from Sweden where the five digits are grouped 3+2
+    ;; at the request from Mats Lofdahl <address@hidden>.
+    ;; (result is ("SE" (133 36)))
+    "^[ \t\n]*\\([A-Z]+\\)[ \t\n]*-?[ \t\n]*\\([0-9]+\\)[ \t\n]+\\([0-9]+\\)[ 
\t\n]*$")
+  "List of regexps that match legal postcodes.
+Whether this is used at all depends on the variable `ebdb-check-postcode'."
+  :group 'ebdb-record-edit
+  :type '(repeat regexp))
 
-(defmacro ebdb-debug (&rest body)
-  "Excecute BODY just like `progn' with debugging capability.
-Debugging is enabled if variable `ebdb-debug' is non-nil during compile.
-You really should not disable debugging.  But it will speed things up."
-  (declare (indent 0))
-  (if ebdb-debug ; compile-time switch
-      `(let ((debug-on-error t))
-         ,@body)))
+(defcustom ebdb-default-separator '("[,;]" ", ")
+  "The default field separator.  It is a list (SPLIT-RE JOIN).
+This is used for fields which do not have an entry in `ebdb-separator-alist'."
+  :group 'ebdb-record-edit
+  :type '(list regexp string))
 
-;;; Fields.
+(defcustom ebdb-separator-alist
+  '((record "\n\n" "\n\n") ; used by `ebdb-copy-fields-as-kill'
+    (name-first-last "[ ,;]" " ")
+    (name-last-first "[ ,;]" ", ")
+    (name-field ":\n" ":\n") ; used by `ebdb-copy-fields-as-kill'
+    (phone "[,;]" ", ")
+    (address ";\n" ";\n")
+    (organization "[,;]" ", ")
+    (affix "[,;]"  ", ")
+    (aka "[,;]" ", ")
+    (mail "[,;]" ", ")
+    (mail-alias "[,;]" ", ")
+    (vm-folder "[,;]" ", ")
+    (birthday "\n" "\n")
+    (wedding "\n" "\n")
+    (anniversary "\n" "\n")
+    (notes "\n" "\n"))
+  "Alist of field separators.
+Each element is of the form (FIELD SPLIT-RE JOIN).
+For fields lacking an entry here `ebdb-default-separator' is used instead."
+  :group 'ebdb-record-edit
+  :type '(repeat (list symbol regexp string)))
 
-(defclass ebdb-field ()
-  ((actions
-    :type (list-of cons)
-    :allocation :class
-    :initform nil
-    :documentation
-    "A list of actions which this field can perform.  Each list
-  element is a cons of string name and function name."))
-  :abstract t :documentation "Abstract class for EBDB fields.
-  Subclass this to produce real field types.")
+(defcustom ebdb-image-path nil
+  "List of directories to search for `ebdb-image'."
+  :group 'ebdb-record-edit
+  :type '(repeat (directory)))
 
-(cl-defgeneric ebdb-init-field (field record)
-  "Initialize FIELD.
+(defcustom ebdb-image-suffixes '(".png" ".jpg" ".gif" ".xpm")
+  "List of file name suffixes searched for `ebdb-image'."
+  :group 'ebdb-record-edit
+  :type '(repeat (string :tag "File suffix")))
 
-What this means is entirely dependent upon the field class in
-question.  Often it involves manipulating secondary data
-structures such as label lists.  If RECORD is given, it may also
-involve using FIELD as a hash value to get to RECORD.")
+(defcustom ebdb-read-name-articulate nil
+  "Specify how to read record names.
 
-(cl-defmethod ebdb-init-field (_field-value _record)
-  "Catch-all `ebdb-init-field' method for fields.
+If nil, read full names as single strings, and parse them
+accordingly.  If t, the user will be prompted separately for each
+field of the name.
 
-This method may also get called on field values that aren't
-actually `ebdb-field' instances -- for instance, plain strings.
-In those cases, assume we don't need to do anything."
-  t)
+If this option is nil, and the user enters a single string, the
+resulting name field will be an instance of
+`ebdb-field-name-simple'.  Even if this option is t, the user can
+still trigger the creation of a simple name field by entering a
+single string for the surname, and nothing else."
+  :group 'ebdb-record-edit
+  :type 'boolean)
 
-(cl-defmethod ebdb-field-readable-name ((field (subclass ebdb-field)))
-  "Return a human-readable string label for this class.
+(defcustom ebdb-lastname-prefixes
+ '("von" "de" "di")
+  "List of lastname prefixes recognized in name fields.
+Used to enhance dividing name strings into firstname and lastname parts.
+Case is ignored."
+  :group 'ebdb-record-edit
+  :type '(repeat string))
 
-Mostly used for allowing users to pick which field type they want
-to add to a record."
-  ;; Why is there no non-private access to this?  The `class-option'
-  ;; function is mentioned in the EIEIO manual, but doesn't exist.
-  (eieio--class-option (find-class field) :human-readable))
+(defcustom ebdb-lastname-re
+  (concat "[- \t]*\\(\\(?:\\<"
+          (regexp-opt ebdb-lastname-prefixes)
+          ;; multiple last names concatenated by `-'
+          "\\>[- \t]+\\)?\\(?:\\w+[ \t]*-[ \t]*\\)*\\w+\\)\\'")
+  "Regexp matching the last name of a full name.
+Its first parenthetical subexpression becomes the last name."
+  :group 'ebdb-record-edit
+  :type 'regexp)
 
-(cl-defmethod ebdb-field-readable-name ((field ebdb-field))
-  (ebdb-field-readable-name (eieio-object-class field)))
+(defcustom ebdb-lastname-suffixes
+ '("Jr" "Sr" "II" "III")
+  "List of lastname suffixes recognized in name fields.
+Used to dividing name strings into firstname and lastname parts.
+All suffixes are complemented by optional `.'.  Case is ignored."
+  :group 'ebdb-record-edit
+  :type '(repeat string))
 
-(cl-defmethod ebdb-field-readable-name ((_field (eql string)))
-  "Value")
+(defcustom ebdb-lastname-suffix-re
+  (concat "[-,. \t/\\]+\\("
+          (regexp-opt ebdb-lastname-suffixes)
+          ;; suffices are complemented by optional `.'.
+          "\\.?\\)\\W*\\'")
+  "Regexp matching the suffix of a last name.
+Its first parenthetical subexpression becomes the suffix."
+  :group 'ebdb-record-edit
+  :type 'regexp)
 
-(cl-defgeneric ebdb-parse (field-class str &optional slots)
-  "Attempt to construct an instance of FIELD-CLASS using STR.
+(defcustom ebdb-allow-duplicates nil
+  "When non-nil EBDB allows records with duplicate names and email addresses.
+In rare cases, this may lead to confusion with EBDB's MUA interface."
+  :group 'ebdb-record-edit
+  :type 'boolean)
 
-Implementations should extract information from STR and put it
-into SLOTS, provided that SLOTS does not already contain relevant
-values (ie, parsing should not override what's already in SLOTS).
-Then call `cl-call-next-method' with the new values.")
+(defcustom ebdb-address-label-list '("home" "work" "other")
+  "List of labels for Address field."
+  :group 'ebdb-record-edit
+  :type '(repeat string))
 
-(cl-defmethod ebdb-parse :around (_field-class _str &optional _slots)
-  (save-match-data
-    (cl-call-next-method)))
+(defcustom ebdb-phone-label-list '("home" "work" "cell" "fax" "other")
+  "List of labels for Phone field."
+  :group 'ebdb-record-edit
+  :type '(repeat string))
 
-(cl-defmethod ebdb-parse ((field-class (subclass ebdb-field)) _str &optional 
slots)
-  "Create the actual field instance."
-  (apply 'make-instance field-class slots))
+(defcustom ebdb-default-country "Emacs";; what do you mean, it's not a country?
+  "Default country to use if none is specified."
+  :group 'ebdb-record-edit
+  :type '(choice (const :tag "None" nil)
+                 (string :tag "Default Country")))
 
-(cl-defmethod ebdb-parse :before ((_field-class (subclass ebdb-field)) str 
&optional _slots)
-  (when (string-empty-p str)
-    (signal 'ebdb-empty (list "Empty string cannot be parsed"))))
+(defcustom ebdb-check-postcode t
+  "If non-nil, require legal postcodes when entering an address.
+The format of legal postcodes is determined by the variable
+`ebdb-legal-postcodes'."
+  :group 'ebdb-record-edit
+  :type 'boolean)
 
-;;; Errors
+(defcustom ebdb-default-user-field 'ebdb-field-notes
+  "Default field when editing EBDB records."
+  :group 'ebdb-record-edit
+  :type '(symbol :tag "Field"))
 
-;; I haven't figured this out quite yet.  What I want to do is avoid
-;; raising errors for *some* methods, with *some* classes; right now
-;; all errors are suppressed.  It doesn't seem very easy to specialize
-;; on methods and classes here: the GENERIC argument that's passed in
-;; to the methods below is the full struct of the generic itself.
-;; Presumably I'll have to look into that struct?  Or maybe I should
-;; just write bottom-level do-nothing methods for the cases where I
-;; don't want to raise an error.  I guess I'll do that for
-;; `ebdb-delete-field' and `ebdb-init-field', for the base
-;; `ebdb-field' class.
+(defcustom ebdb-url-valid-schemes '("http:" "https:" "irc:")
+  "A list of strings matching schemes acceptable to
+  `ebdb-field-url' instances.
 
-;; (cl-defmethod cl-no-applicable-method (_generic &rest _args)
-;;   "Don't raise errors for unimplemented methods."
-;;   (message "All no-applicable-method errors are swallowed."))
+Strings should not be regular expressions.  They should include
+the colon character."
 
-;; (cl-defmethod cl-no-next-method (_generic _method &rest _args)
-;;   "Don't raise errors for non-existent next methods."
-;;   (message "All no-next-method errors are swallowed."))
+  :group 'ebdb-record-edit
+  :type '(repeat string))
 
-;; There used to be a `destructor' method, but it's been marked
-;; obsolete as of 25.2.  There may be a `delete-instance' method, but
-;; then again there may not.  Handle it ourselves.
+(defcustom ebdb-mail-avoid-redundancy nil
+  "How to handle the name part of `ebdb-dwim-mail'.
 
-(cl-defgeneric ebdb-delete-field (field &optional record unload)
-  "Delete FIELD.
+If nil, always return both name and mail.  If value is mail-only
+never use full name.  Other non-nil values mean do not use full
+name in mail address when same as mail.
+"
+  :group 'ebdb-sendmail
+  :type '(choice (const :tag "Allow redundancy" nil)
+                 (const :tag "Never use full name" mail-only)
+                 (const :tag "Avoid redundancy" t)))
 
-Often involves un-hashing RECORD against the field value, or
-removing labels from label lists.
+(defcustom ebdb-complete-mail t
+  "If t MUA insinuation provides key binding for command `ebdb-complete-mail'."
+  :group 'ebdb-sendmail
+  :type 'boolean)
 
-If UNLOAD is true, it indicates that RECORD is only being
-unloaded, not actually deleted.")
+(defcustom ebdb-completion-list t
+  "Controls the behaviour of `ebdb-complete-mail'.
+If a list of symbols, it specifies which fields to complete.  Symbols include
+  name (= record's display name)
+  alt-names (= any other names the record has)
+  organization
+  mail (= all email addresses of each record)
+  primary (= first email address of each record)
+If t, completion is done for all of the above.
+If nil, no completion is offered."
+  ;; These symbols match the fields for which EBDB provides entries in
+  ;; `ebdb-hash-table'.
+  :group 'ebdb-sendmail
+  :type '(choice (const :tag "No Completion" nil)
+                 (const :tag "Complete across all fields" t)
+                 (repeat :tag "Field"
+                         (choice (const name)
+                                 (const alt-names)
+                                 (const organization)
+                                 (const primary)
+                                 (const mail)))))
 
-(cl-defmethod ebdb-delete-field ((field ebdb-field) &optional _record _unload)
-  "User-level deletion routine for FIELD.
+(defcustom ebdb-complete-mail-allow-cycling nil
+  "If non-nil cycle mail addresses when calling `ebdb-complete-mail'."
+  :group 'ebdb-sendmail
+  :type 'boolean)
 
-Override this to do any necessary cleanup work after FIELD is
-removed."
-  (delete-instance field))
+(defcustom ebdb-complete-mail-hook nil
+  "List of functions called after a sucessful completion."
+  :group 'ebdb-sendmail
+  :type 'hook)
 
-(cl-defmethod ebdb-delete-field ((_field string) &optional _record _unload)
-  t)
+(defcustom ebdb-mail-abbrev-expand-hook nil
+  ;; Replacement for function `mail-abbrev-expand-hook'.
+  "Function (not hook) run each time an alias is expanded.
+The function is called with two args the alias and the list
+of corresponding mail addresses."
+  :group 'ebdb-sendmail
+  :type 'function)
 
-(cl-defmethod delete-instance ((_field ebdb-field) &rest _args)
-  t)
+(defcustom ebdb-completion-display-record t
+  "If non-nil `ebdb-complete-mail' displays the EBDB record after completion."
+  :group 'ebdb-sendmail
+  :type '(choice (const :tag "Update the EBDB buffer" t)
+                 (const :tag "Do not update the EBDB buffer" nil)))
 
-(cl-defmethod ebdb-read ((class (subclass ebdb-field)) &optional slots _obj)
-  "Complete the read/object creation process for a field of CLASS.
+(defvar ebdb-update-unchanged-records nil
+  "If non-nil update unchanged records in the database.
+Normally calls of `ebdb-change-hook' and updating of a record are suppressed,
+if an editing command did not really change the record.  Bind this to t
+if you want to call `ebdb-change-hook' and update the record unconditionally.")
 
-Earlier subclasses of `ebdb-field' will have read all the
-necessary values into SLOTS; this base method is simply
-responsible for creating the field object.
+(defvar ebdb-street-list nil
+  "List of streets known to EBDB.")
 
-The OBJ argument is used when editing existing fields: OBJ is the
-old field.  By now we've sucked all the useful information out of
-it, and if this process is successful it will get deleted."
-  (apply 'make-instance class slots))
+(defvar ebdb-locality-list nil
+  "List of localities (towns or cities) known to EBDB.")
 
-;; Generics for fields.  Not all field classes will implement these
-;; methods.  `ebdb-action' should raise an error (to be caught and
-;; displayed at top level) when there is no applicable action method,
-;; so we don't actually define a base method. `ebdb-notice' shouldn't
-;; raise an error if it's not implemented, so we define a do-nothing
-;; base method.
+(defvar ebdb-region-list nil
+  "List of regions (states or provinces) known to EBDB.")
 
-(cl-defmethod ebdb-action ((field ebdb-field) record &optional idx)
-  "Do an \"action\" based on one of the functions listed in FIELD's action 
slot.
+(defvar ebdb-postcode-list nil
+  "List of post codes known to EBDB.")
 
-If IDX is provided, it is an index indicating which of the action
-functions to call. Otherwise, call the car of the list."
-  (let* ((actions (slot-value field 'actions))
-        (pair (when actions
-                (if idx (or (nth idx actions) (last actions)) (car actions)))))
-    (when pair
-      (funcall (cdr pair) record field))))
+;;; Define some of our own errors. A few of these should never be
+;;; shown to the user, they're for internal flow control.
 
-(cl-defgeneric ebdb-notice-field (field &optional type record)
-  "\"Notice\" FIELD.
+;; Error parent
+(define-error 'ebdb-error "EBDB error")
 
-This means that a message involving RECORD has been viewed, or
-that a MUA has otherwise decided that something significant to
-RECORD has taken place.  It is up to the class of FIELD to decide
+(define-error 'ebdb-duplicate-uuid "Duplicate EBDB UUID" 'ebdb-error)
+
+(define-error 'ebdb-unsynced-db "EBDB DB unsynced" 'ebdb-error)
+
+(define-error 'ebdb-disabled-db "EBDB DB disabled" 'ebdb-error)
+
+(define-error 'ebdb-readonly-db "EBDB DB read-only" 'ebdb-error)
+
+(define-error 'ebdb-unacceptable-field "EBDB record cannot accept field" 
'ebdb-error)
+
+(define-error 'ebdb-empty "Empty value" 'ebdb-error)
+
+(define-error 'ebdb-unparseable "Unparseable value" 'ebdb-error)
+
+;;; Utility functions and macros
+
+;;;###autoload
+(defsubst ebdb-records (&optional record-class child-p)
+  "Return a list of all EBDB records; load databases if necessary.
+This function also notices if databases are out of sync.
+
+If RECORD-CLASS is given, only return records of this class or,
+if CHILD-P is non-nil, one of its subclasses."
+  (unless ebdb-db-list
+    (ebdb-load))
+  (if record-class
+      (seq-filter
+       (lambda (r)
+        (if child-p
+            (object-of-class-p r record-class)
+          (same-class-p r record-class)))
+       ebdb-record-tracker)
+   ebdb-record-tracker))
+
+(defmacro ebdb-error-retry (&rest body)
+  "Repeatedly execute BODY ignoring errors till no error occurs."
+  `(catch '--ebdb-error-retry--
+     (while t
+       (condition-case --c--
+           (throw '--ebdb-error-retry-- (progn ,@body))
+         (ebdb-unparseable
+         (ding)
+         (message "Error: %s" (nth 1 --c--))
+         (sit-for 2))))))
+
+(defmacro ebdb-with-exit (&rest body)
+  `(condition-case nil
+       ,@body
+     ((quit ebdb-empty)
+      nil)))
+
+(defmacro ebdb-loop-with-exit (&rest body)
+  "Repeat BODY, accumulating the results in a list, until the
+user either hits C-g, or enters an empty field label."
+  `(let (acc)
+     (catch '--ebdb-loop-exit--
+       (condition-case nil
+          (while t
+            (push ,@body acc))
+        ((quit ebdb-empty)
+         (throw '--ebdb-loop-exit-- acc))))))
+
+(defmacro ebdb-debug (&rest body)
+  "Excecute BODY just like `progn' with debugging capability.
+Debugging is enabled if variable `ebdb-debug' is non-nil during compile.
+You really should not disable debugging.  But it will speed things up."
+  (declare (indent 0))
+  (if ebdb-debug ; compile-time switch
+      `(let ((debug-on-error t))
+         ,@body)))
+
+;;; Fields.
+
+(defclass ebdb-field ()
+  ((actions
+    :type (list-of cons)
+    :allocation :class
+    :initform nil
+    :documentation
+    "A list of actions which this field can perform.  Each list
+  element is a cons of string name and function name."))
+  :abstract t :documentation "Abstract class for EBDB fields.
+  Subclass this to produce real field types.")
+
+(cl-defgeneric ebdb-init-field (field record)
+  "Initialize FIELD.
+
+What this means is entirely dependent upon the field class in
+question.  Often it involves manipulating secondary data
+structures such as label lists.  If RECORD is given, it may also
+involve using FIELD as a hash value to get to RECORD.")
+
+(cl-defmethod ebdb-init-field (_field-value _record)
+  "Catch-all `ebdb-init-field' method for fields.
+
+This method may also get called on field values that aren't
+actually `ebdb-field' instances -- for instance, plain strings.
+In those cases, assume we don't need to do anything."
+  t)
+
+(cl-defmethod ebdb-field-readable-name ((field (subclass ebdb-field)))
+  "Return a human-readable string label for this class.
+
+Mostly used for allowing users to pick which field type they want
+to add to a record."
+  ;; Why is there no non-private access to this?  The `class-option'
+  ;; function is mentioned in the EIEIO manual, but doesn't exist.
+  (eieio--class-option (find-class field) :human-readable))
+
+(cl-defmethod ebdb-field-readable-name ((field ebdb-field))
+  (ebdb-field-readable-name (eieio-object-class field)))
+
+(cl-defmethod ebdb-field-readable-name ((_field (eql string)))
+  "Value")
+
+(cl-defgeneric ebdb-parse (field-class str &optional slots)
+  "Attempt to construct an instance of FIELD-CLASS using STR.
+
+Implementations should extract information from STR and put it
+into SLOTS, provided that SLOTS does not already contain relevant
+values (ie, parsing should not override what's already in SLOTS).
+Then call `cl-call-next-method' with the new values.")
+
+(cl-defmethod ebdb-parse :around (_field-class _str &optional _slots)
+  (save-match-data
+    (cl-call-next-method)))
+
+(cl-defmethod ebdb-parse ((field-class (subclass ebdb-field)) _str &optional 
slots)
+  "Create the actual field instance."
+  (apply 'make-instance field-class slots))
+
+(cl-defmethod ebdb-parse :before ((_field-class (subclass ebdb-field)) str 
&optional _slots)
+  (when (string-empty-p str)
+    (signal 'ebdb-empty (list "Empty string cannot be parsed"))))
+
+;;; Errors
+
+;; I haven't figured this out quite yet.  What I want to do is avoid
+;; raising errors for *some* methods, with *some* classes; right now
+;; all errors are suppressed.  It doesn't seem very easy to specialize
+;; on methods and classes here: the GENERIC argument that's passed in
+;; to the methods below is the full struct of the generic itself.
+;; Presumably I'll have to look into that struct?  Or maybe I should
+;; just write bottom-level do-nothing methods for the cases where I
+;; don't want to raise an error.  I guess I'll do that for
+;; `ebdb-delete-field' and `ebdb-init-field', for the base
+;; `ebdb-field' class.
+
+;; (cl-defmethod cl-no-applicable-method (_generic &rest _args)
+;;   "Don't raise errors for unimplemented methods."
+;;   (message "All no-applicable-method errors are swallowed."))
+
+;; (cl-defmethod cl-no-next-method (_generic _method &rest _args)
+;;   "Don't raise errors for non-existent next methods."
+;;   (message "All no-next-method errors are swallowed."))
+
+;; There used to be a `destructor' method, but it's been marked
+;; obsolete as of 25.2.  There may be a `delete-instance' method, but
+;; then again there may not.  Handle it ourselves.
+
+(cl-defgeneric ebdb-delete-field (field &optional record unload)
+  "Delete FIELD.
+
+Often involves un-hashing RECORD against the field value, or
+removing labels from label lists.
+
+If UNLOAD is true, it indicates that RECORD is only being
+unloaded, not actually deleted.")
+
+(cl-defmethod ebdb-delete-field ((field ebdb-field) &optional _record _unload)
+  "User-level deletion routine for FIELD.
+
+Override this to do any necessary cleanup work after FIELD is
+removed."
+  (delete-instance field))
+
+(cl-defmethod ebdb-delete-field ((_field string) &optional _record _unload)
+  t)
+
+(cl-defmethod delete-instance ((_field ebdb-field) &rest _args)
+  t)
+
+(cl-defmethod ebdb-read ((class (subclass ebdb-field)) &optional slots _obj)
+  "Complete the read/object creation process for a field of CLASS.
+
+Earlier subclasses of `ebdb-field' will have read all the
+necessary values into SLOTS; this base method is simply
+responsible for creating the field object.
+
+The OBJ argument is used when editing existing fields: OBJ is the
+old field.  By now we've sucked all the useful information out of
+it, and if this process is successful it will get deleted."
+  (apply 'make-instance class slots))
+
+;; Generics for fields.  Not all field classes will implement these
+;; methods.  `ebdb-action' should raise an error (to be caught and
+;; displayed at top level) when there is no applicable action method,
+;; so we don't actually define a base method. `ebdb-notice' shouldn't
+;; raise an error if it's not implemented, so we define a do-nothing
+;; base method.
+
+(cl-defmethod ebdb-action ((field ebdb-field) record &optional idx)
+  "Do an \"action\" based on one of the functions listed in FIELD's action 
slot.
+
+If IDX is provided, it is an index indicating which of the action
+functions to call. Otherwise, call the car of the list."
+  (let* ((actions (slot-value field 'actions))
+        (pair (when actions
+                (if idx (or (nth idx actions) (last actions)) (car actions)))))
+    (when pair
+      (funcall (cdr pair) record field))))
+
+(cl-defgeneric ebdb-notice-field (field &optional type record)
+  "\"Notice\" FIELD.
+
+This means that a message involving RECORD has been viewed, or
+that a MUA has otherwise decided that something significant to
+RECORD has taken place.  It is up to the class of FIELD to decide
 what, if anything, to do about this.
 
 TYPE is a further indicator of how RECORD was noticed: in normal
@@ -749,7 +1109,7 @@ process."
 (cl-defmethod eieio-object-name-string ((field ebdb-field-labeled))
   "Return a string which is FIELD's name."
   (or (slot-value field 'object-name)
-      (ebdb-field-readable-name (class-of field))))
+      (ebdb-field-readable-name (eieio-object-class field))))
 
 ;;; The obfuscated field type.  This is a little goofy, but might come
 ;;; in handy.
@@ -1722,16 +2082,6 @@ Eventually this method will go away."
 
 ;; URL field
 
-(defcustom ebdb-url-valid-schemes '("http:" "https:" "irc:")
-  "A list of strings matching schemes acceptable to
-  `ebdb-field-url' instances.
-
-Strings should not be regular expressions.  They should include
-the colon character."
-
-  :group 'ebdb-record-edit
-  :type '(repeat string))
-
 (defvar ebdb-url-label-list '("homepage")
   "List of known URL labels.")
 
@@ -1865,24 +2215,80 @@ record uuids.")
   (with-slots (country number) field
     (format "(%s) %s" country number)))
 
-;;; Records
+;;; The cache class
 
-;; The basic, abstract `ebdb-record' class should require no user
-;; interaction, and has no real user-facing fields (except for the
-;; "fields" bucket, of course).  It takes care of all the fundamental
-;; setup and housekeeping automatically.
+;; This probably bears some re-thinking.  It would be nice to make it
+;; behave as a "real" cache, in the sense that all the accessors are
+;; accessors on the records themselves -- the records don't need to be
+;; aware of the cache.  The (probably multiple) cache classes should
+;; be parent classes, not slots on the record (or rather, the cache
+;; slot on the record comes from the cache parent class).  We ask the
+;; record for information, and the cache method intercepts the call,
+;; returns the value if it has it, and if not then asks the record for
+;; the value then stores it.  Ie, a real cache.  Not all the cache
+;; slots would work that way, of course -- for instance. a record has
+;; no way of knowing its databases except via the cache.
 
-(defclass ebdb-record (eieio-instance-tracker)
-  ((uuid
-    :initarg :uuid
-    :type (or null ebdb-field-uuid)
-    :initform nil)
-   (tracking-symbol
-    :initform ebdb-record-tracker)
-   (creation-date
-    :initarg :creation-date
-    :type (or null ebdb-field-creation-date)
-    :initform nil)
+(defclass ebdb-cache ()
+  ((name-string
+    :initarg :name-string
+    :type string
+    :initform nil
+    :documentation "The \"canonical\" name for the record, as
+    displayed in the *EBDB* buffer.")
+   (alt-names
+    :initarg :alt-names
+    :type (list-of string)
+    :initform nil
+    :documentation "A list of strings representing all other
+    alternate names for this record.")
+   (organizations
+    :initarg :organizations
+    :type list
+    :initform nil
+    :documentation
+    "A list of strings representing the organizations this record
+    is associated with.")
+   (mail-aka
+    :initarg :mail-aka
+    :type list
+    :initform nil)
+   (mail-canon
+    :initarg :mail-canon
+    :type list
+    :initform nil)
+   (sortkey
+    :initarg :sortkey
+    :type string
+    :initform nil)
+   (database
+    :initarg :database
+    :type (list-of ebdb-db)
+    :initform nil
+    :documentation
+    "The database(s) this record belongs to."))
+  ;; I'm not sure if a marker slot is still going to be necessary in
+  ;; this setup.
+  :allow-nil-initform t)
+
+;;; Records
+
+;; The basic, abstract `ebdb-record' class should require no user
+;; interaction, and has no real user-facing fields (except for the
+;; "fields" bucket, of course).  It takes care of all the fundamental
+;; setup and housekeeping automatically.
+
+(defclass ebdb-record (eieio-instance-tracker)
+  ((uuid
+    :initarg :uuid
+    :type (or null ebdb-field-uuid)
+    :initform nil)
+   (tracking-symbol
+    :initform ebdb-record-tracker)
+   (creation-date
+    :initarg :creation-date
+    :type (or null ebdb-field-creation-date)
+    :initform nil)
    (timestamp
     :initarg :timestamp
     :type (or null ebdb-field-timestamp)
@@ -1911,7 +2317,7 @@ record uuids.")
     :initarg :cache
     :type (or null ebdb-cache)
     :initform nil
-    ;:accessor ebdb-record-cache
+                                       ;:accessor ebdb-record-cache
     ))
   :abstract t
   :allow-nil-initform t
@@ -2236,11 +2642,18 @@ or actual image data."
   ;; Unimplemented.
   nil)
 
+;; See http://www.ietf.org/rfc/rfc3966.txt
 (cl-defmethod ebdb-field-phone-dial ((_record ebdb-record)
                                     (phone ebdb-field-phone))
   "Make some attempt to call this PHONE number."
-  ;; This won't actually work.
-  (ebdb-dial-number (ebdb-string phone)))
+  (with-slots (country-code area-code number extension) phone
+    (browse-url
+     (concat
+      "tel:"
+      (when country-code (format "+%d" country-code))
+      (when area-code (number-to-string area-code))
+      number
+      (when extension (format ";ext=%d" extension))))))
 
 (cl-defmethod ebdb-field-url-browse ((_record ebdb-record)
                                     (field ebdb-field-url))
@@ -2388,6 +2801,9 @@ If FIELD doesn't specify a year, use the current year."
 (cl-defmethod ebdb-record-organizations ((_record ebdb-record-entity))
   nil)
 
+;; TODO: This is wrong, it will alter the database after the main body
+;; of `ebdb-record-insert-field' has run.  Can we simply switch it to
+;; :before?
 (cl-defmethod ebdb-record-insert-field :after ((record ebdb-record-entity)
                                               (_mail ebdb-field-mail)
                                               &optional _slot)
@@ -2396,6 +2812,10 @@ priority."
   (let ((sorted (ebdb-sort-mails (slot-value record 'mail))))
     (setf (slot-value record 'mail) sorted)))
 
+(defun ebdb-compose-mail (&rest args)
+  "Start composing a mail message to send."
+  (apply 'compose-mail args))
+
 (cl-defmethod ebdb-field-mail-compose ((record ebdb-record-entity)
                                       (mail ebdb-field-mail))
   (ebdb-compose-mail (ebdb-dwim-mail record mail)))
@@ -3307,62 +3727,6 @@ the persistent save, or allow them to propagate."
   (setf (slot-value db 'dirty) t)
   (cl-call-next-method))
 
-;;; The cache class
-
-;; This probably bears some re-thinking.  It would be nice to make it
-;; behave as a "real" cache, in the sense that all the accessors are
-;; accessors on the records themselves -- the records don't need to be
-;; aware of the cache.  The (probably multiple) cache classes should
-;; be parent classes, not slots on the record (or rather, the cache
-;; slot on the record comes from the cache parent class).  We ask the
-;; record for information, and the cache method intercepts the call,
-;; returns the value if it has it, and if not then asks the record for
-;; the value then stores it.  Ie, a real cache.  Not all the cache
-;; slots would work that way, of course -- for instance. a record has
-;; no way of knowing its databases except via the cache.
-
-(defclass ebdb-cache ()
-  ((name-string
-    :initarg :name-string
-    :type string
-    :initform nil
-    :documentation "The \"canonical\" name for the record, as
-    displayed in the *EBDB* buffer.")
-   (alt-names
-    :initarg :alt-names
-    :type (list-of string)
-    :initform nil
-    :documentation "A list of strings representing all other
-    alternate names for this record.")
-   (organizations
-    :initarg :organizations
-    :type list
-    :initform nil
-    :documentation
-    "A list of strings representing the organizations this record
-    is associated with.")
-   (mail-aka
-    :initarg :mail-aka
-    :type list
-    :initform nil)
-   (mail-canon
-    :initarg :mail-canon
-    :type list
-    :initform nil)
-   (sortkey
-    :initarg :sortkey
-    :type string
-    :initform nil)
-   (database
-    :initarg :database
-    :type (list-of ebdb-db)
-    :initform nil
-    :documentation
-    "The database(s) this record belongs to."))
-  ;; I'm not sure if a marker slot is still going to be necessary in
-  ;; this setup.
-  :allow-nil-initform t)
-
 ;;; Subclasses of `ebdb-db'.
 
 ;; File-based database, keeping its records in-file.
@@ -3541,411 +3905,102 @@ Returns a list of (\"label\" slot . field-class)."
       (unless (or (eq field 'ebdb-field-user-simple)
                  (eq field 'ebdb-field-creation-date)
                  (eq field 'ebdb-field-timestamp))
-       (push (list (ebdb-field-readable-name field) c) field-list)))
-    (dolist (l ebdb-user-label-list)
-      (push (list l (cons 'fields 'ebdb-field-user-simple)) field-list))
-    (setq choice
-         (completing-read
-          "Choose field type: "
-          field-list))
-    (or (assoc choice field-list)
-       (list choice (cons 'fields 'ebdb-field-user-simple)))))
-
-(defun ebdb-prompt-for-db (&optional db-list)
-  (unless (or db-list ebdb-db-list)
-    (ebdb-load))
-  (let* ((collection (or db-list ebdb-db-list))
-        (db-string
-         (ebdb-read-string "Choose a database: "
-                           nil
-                           (mapcar
-                            (lambda (d)
-                              (slot-value d 'object-name))
-                            collection)
-                           t)))
-    (object-assoc db-string 'object-name collection)))
-
-(defun ebdb-prompt-for-mail (record)
-  (let ((mail-alist (mapcar
-                    (lambda (m) (cons (ebdb-string m) m))
-                    (ebdb-record-mail record t))))
-    (cdr (if (= 1 (length mail-alist))
-            (car mail-alist)
-          (assoc (ebdb-read-string
-                  (format "Mail address for %s: " (ebdb-string record))
-                  nil mail-alist t)
-                 mail-alist)))))
-
-(defun ebdb-dirty-records (&optional records)
-  "Return all records with unsaved changes.
-
-If RECORDS are given, only search those records."
-  (seq-filter
-   (lambda (r)
-     (slot-value r 'dirty))
-   (or records ebdb-record-tracker)))
-
-;;; Getters
-
-;; The simplest of getters/setters are defined with an :accessor tag
-;; on the class slot definition itself.  Ie, `ebdb-record-user-fields'
-;; and `ebdb-record-cache'.
-
-(defun ebdb-record-cache (record)
-  (slot-value record 'cache))
-
-(defun ebdb-record-user-fields (record)
-  (slot-value record 'fields))
-
-(defun ebdb-record-user-field (record label)
-  (object-assoc (if (stringp label)
-                   label
-                 (symbol-name label))
-               'object-name (ebdb-record-user-fields record)))
-
-(defun ebdb-record-address (record &optional label)
-  (let ((addresses (slot-value record 'address)))
-    (if label
-       (object-assoc label 'object-name addresses)
-      addresses)))
-
-(defun ebdb-record-phone (record &optional label)
-  (let ((phones (slot-value record 'phone)))
-    (if label
-       (object-assoc label 'object-name phones)
-      phones)))
-
-(defun ebdb-record-mail (record &optional roles label defunct)
-  "Return a list of all RECORD's mail fields.
-
-If ROLES is non-nil, also consider mail fields from RECORD's
-roles.  If LABEL is a string, return the mail with that label.
-If DEFUNCT is non-nil, also consider RECORD's defunct mail
-addresses."
-  (let ((mails (slot-value record 'mail)))
-    (when (and roles (slot-exists-p record 'organizations))
-      (dolist (r (slot-value record 'organizations))
-       (when (and (slot-value r 'mail)
-                  (or defunct
-                      (null (slot-value r 'defunct))))
-         (push (slot-value r 'mail) mails))))
-    (unless defunct
-      (setq mails
-           (seq-filter (lambda (m)
-                         (null (eq (slot-value m 'priority) 'defunct)))
-                       mails)))
-    (if label
-       (object-assoc label 'object-name mails)
-      mails)))
-
-
-;;; Record editing
-
-;; The following two options should be obviated by ebdb-i18n.el
-;; See http://en.wikipedia.org/wiki/Postal_address
-;; 
http://www.upu.int/en/activities/addressing/postal-addressing-systems-in-member-countstateries.html
-(defcustom ebdb-address-format-list
-  '(((arg) "splrc" "@address@hidden, @%l@, address@hidden@" "@%l@")
-    ((aus) "slrpc" "@address@hidden@ %r@ address@hidden@" "@%l@")
-    ((aut due esp che)
-     "splrc" "@address@hidden @%l@ (%r)@\n%c@" "@%l@")
-    ((can) "slrcp" "@address@hidden@, address@hidden@ %p@" "@%l@")
-    ((chn) "slprc" "@address@hidden@\n%p@ address@hidden@" "@%l@") ; English 
format
-    ; (("China") "cprls" "@%c @address@hidden @%l@ %s@" "@%l@") ; Chinese 
format
-    ((ind) "slprc" "@address@hidden@ %p@ (%r)@\n%c@" "@%l@")
-    ((usa) "slrpc" "@address@hidden@, %r@ address@hidden@" "@%l@")
-    (t ebdb-edit-address-default ebdb-format-address-default "@%l@"))
-  "List of address editing and formatting rules for EBDB.
-Each rule is a list (IDENTIFIER EDIT FORMAT FORMAT).
-The first rule for which IDENTIFIER matches an address is used for editing
-and formatting the address.
-
-IDENTIFIER may be a list of countries.
-IDENTIFIER may also be a function that is called with one arg, the address
-to be used.  The rule applies if the function returns non-nil.
-See `ebdb-address-continental-p' for an example.
-If IDENTIFIER is t, this rule always applies.  Usually, this should be
-the last rule that becomes a fall-back (default).
-
-EDIT may be a function that is called with one argument, the address.
-See `ebdb-edit-address-default' for an example.
-
-EDIT may also be an editting format string.  It is a string containing
-the five letters s, c, p, S, and C that specify the order for editing
-the five elements of an address:
-
-s  streets
-l  locality
-p  postcode
-r  region
-c  country
-
-The first FORMAT of each rule is used for multi-line layout, the second FORMAT
-is used for one-line layout.
-
-FORMAT may be a function that is called with one argument, the address.
-See `ebdb-format-address-default' for an example.
-
-FORMAT may also be a format string.  It consists of formatting elements
-separated by a delimiter defined via the first (and last) character of FORMAT.
-Each formatting element may contain one of the following format specifiers:
-
-%s  streets (used repeatedly for each street part)
-%l  locality
-%p  postcode
-%r  region
-%c  country
-
-A formatting element will be applied only if the corresponding part
-of the address is a non-empty string.
-
-See also `ebdb-print-address-format-list'."
-  :group 'ebdb-record-edit
-  :type '(repeat (list (choice (const :tag "Default" t)
-                               (function :tag "Function")
-                               (repeat (string)))
-                       (choice (string)
-                               (function :tag "Function"))
-                       (choice (string)
-                               (function :tag "Function"))
-                       (choice (string)
-                               (function :tag "Function")))))
-
-(defcustom ebdb-continental-postcode-regexp
-  "^\\s *[A-Z][A-Z]?\\s *-\\s *[0-9][0-9][0-9]"
-  "Regexp matching continental postcodes.
-Used by address format identifier `ebdb-address-continental-p'.
-The regexp should match postcodes of the form CH-8052, NL-2300RA,
-and SE-132 54."
-  :group 'ebdb-record-edit
-  :type 'regexp)
-
-(defcustom ebdb-default-separator '("[,;]" ", ")
-  "The default field separator.  It is a list (SPLIT-RE JOIN).
-This is used for fields which do not have an entry in `ebdb-separator-alist'."
-  :group 'ebdb-record-edit
-  :type '(list regexp string))
-
-(defcustom ebdb-separator-alist
-  '((record "\n\n" "\n\n") ; used by `ebdb-copy-fields-as-kill'
-    (name-first-last "[ ,;]" " ")
-    (name-last-first "[ ,;]" ", ")
-    (name-field ":\n" ":\n") ; used by `ebdb-copy-fields-as-kill'
-    (phone "[,;]" ", ")
-    (address ";\n" ";\n")
-    (organization "[,;]" ", ")
-    (affix "[,;]"  ", ")
-    (aka "[,;]" ", ")
-    (mail "[,;]" ", ")
-    (mail-alias "[,;]" ", ")
-    (vm-folder "[,;]" ", ")
-    (birthday "\n" "\n")
-    (wedding "\n" "\n")
-    (anniversary "\n" "\n")
-    (notes "\n" "\n"))
-  "Alist of field separators.
-Each element is of the form (FIELD SPLIT-RE JOIN).
-For fields lacking an entry here `ebdb-default-separator' is used instead."
-  :group 'ebdb-record-edit
-  :type '(repeat (list symbol regexp string)))
-
-(defcustom ebdb-image-path nil
-  "List of directories to search for `ebdb-image'."
-  :group 'ebdb-record-edit
-  :type '(repeat (directory)))
-
-(defcustom ebdb-image-suffixes '(".png" ".jpg" ".gif" ".xpm")
-  "List of file name suffixes searched for `ebdb-image'."
-  :group 'ebdb-record-edit
-  :type '(repeat (string :tag "File suffix")))
-
-(defcustom ebdb-read-name-articulate nil
-  "Specify how to read record names.
-
-If nil, read full names as single strings, and parse them
-accordingly.  If t, the user will be prompted separately for each
-field of the name.
-
-If this option is nil, and the user enters a single string, the
-resulting name field will be an instance of
-`ebdb-field-name-simple'.  Even if this option is t, the user can
-still trigger the creation of a simple name field by entering a
-single string for the surname, and nothing else."
-  :group 'ebdb-record-edit
-  :type 'boolean)
-
-(defcustom ebdb-lastname-prefixes
- '("von" "de" "di")
-  "List of lastname prefixes recognized in name fields.
-Used to enhance dividing name strings into firstname and lastname parts.
-Case is ignored."
-  :group 'ebdb-record-edit
-  :type '(repeat string))
-
-(defcustom ebdb-lastname-re
-  (concat "[- \t]*\\(\\(?:\\<"
-          (regexp-opt ebdb-lastname-prefixes)
-          ;; multiple last names concatenated by `-'
-          "\\>[- \t]+\\)?\\(?:\\w+[ \t]*-[ \t]*\\)*\\w+\\)\\'")
-  "Regexp matching the last name of a full name.
-Its first parenthetical subexpression becomes the last name."
-  :group 'ebdb-record-edit
-  :type 'regexp)
-
-(defcustom ebdb-lastname-suffixes
- '("Jr" "Sr" "II" "III")
-  "List of lastname suffixes recognized in name fields.
-Used to dividing name strings into firstname and lastname parts.
-All suffixes are complemented by optional `.'.  Case is ignored."
-  :group 'ebdb-record-edit
-  :type '(repeat string))
-
-(defcustom ebdb-lastname-suffix-re
-  (concat "[-,. \t/\\]+\\("
-          (regexp-opt ebdb-lastname-suffixes)
-          ;; suffices are complemented by optional `.'.
-          "\\.?\\)\\W*\\'")
-  "Regexp matching the suffix of a last name.
-Its first parenthetical subexpression becomes the suffix."
-  :group 'ebdb-record-edit
-  :type 'regexp)
-
-(defcustom ebdb-default-domain nil
-  "Default domain to append when reading a new mail address.
-If a mail address does not contain address@hidden', append 
@`ebdb-default-domain' to it.
-
-The address is not altered if `ebdb-default-domain' is nil
-or if a prefix argument is given to the command `ebdb-insert-field'."
-  :group 'ebdb-record-edit
-  :type '(choice (const :tag "none" nil)
-                 (string :tag "Default Domain")))
-
-(defcustom ebdb-allow-duplicates nil
-  "When non-nil EBDB allows records with duplicate names and email addresses.
-In rare cases, this may lead to confusion with EBDB's MUA interface."
-  :group 'ebdb-record-edit
-  :type 'boolean)
-
-(defcustom ebdb-default-label-list '("home" "work" "other")
-  "Default list of labels for Address and Phone fields."
-  :group 'ebdb-record-edit
-  :type '(repeat string))
-
-(defcustom ebdb-address-label-list ebdb-default-label-list
-  "List of labels for Address field."
-  :group 'ebdb-record-edit
-  :type '(repeat string))
+       (push (list (ebdb-field-readable-name field) c) field-list)))
+    (dolist (l ebdb-user-label-list)
+      (push (list l (cons 'fields 'ebdb-field-user-simple)) field-list))
+    (setq choice
+         (completing-read
+          "Choose field type: "
+          field-list))
+    (or (assoc choice field-list)
+       (list choice (cons 'fields 'ebdb-field-user-simple)))))
 
-(defcustom ebdb-phone-label-list '("home" "work" "cell" "fax" "other")
-  "List of labels for Phone field."
-  :group 'ebdb-record-edit
-  :type '(repeat string))
+(defun ebdb-prompt-for-db (&optional db-list)
+  (unless (or db-list ebdb-db-list)
+    (ebdb-load))
+  (let* ((collection (or db-list ebdb-db-list))
+        (db-string
+         (ebdb-read-string "Choose a database: "
+                           nil
+                           (mapcar
+                            (lambda (d)
+                              (slot-value d 'object-name))
+                            collection)
+                           t)))
+    (object-assoc db-string 'object-name collection)))
 
-(defcustom ebdb-default-country "Emacs";; what do you mean, it's not a country?
-  "Default country to use if none is specified."
-  :group 'ebdb-record-edit
-  :type '(choice (const :tag "None" nil)
-                 (string :tag "Default Country")))
+(defun ebdb-prompt-for-mail (record)
+  (let ((mail-alist (mapcar
+                    (lambda (m) (cons (ebdb-string m) m))
+                    (ebdb-record-mail record t))))
+    (cdr (if (= 1 (length mail-alist))
+            (car mail-alist)
+          (assoc (ebdb-read-string
+                  (format "Mail address for %s: " (ebdb-string record))
+                  nil mail-alist t)
+                 mail-alist)))))
 
-(defcustom ebdb-check-postcode t
-  "If non-nil, require legal postcodes when entering an address.
-The format of legal postcodes is determined by the variable
-`ebdb-legal-postcodes'."
-  :group 'ebdb-record-edit
-  :type 'boolean)
+(defun ebdb-dirty-records (&optional records)
+  "Return all records with unsaved changes.
 
-(defcustom ebdb-legal-postcodes
-  '(;; empty string
-    "^$"
-    ;; Matches 1 to 6 digits.
-    "^[ \t\n]*[0-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[ \t\n]*$"
-    ;; Matches 5 digits and 3 or 4 digits.
-    "^[ \t\n]*\\([0-9][0-9][0-9][0-9][0-9]\\)[ \t\n]*-?[ 
\t\n]*\\([0-9][0-9][0-9][0-9]?\\)[ \t\n]*$"
-    ;; Match postcodes for Canada, UK, etc. (result is ("LL47" "U4B")).
-    "^[ \t\n]*\\([A-Za-z0-9]+\\)[ \t\n]+\\([A-Za-z0-9]+\\)[ \t\n]*$"
-    ;; Match postcodes for continental Europe.  Examples "CH-8057"
-    ;; or "F - 83320" (result is ("CH" "8057") or ("F" "83320")).
-    ;; Support for "NL-2300RA" added at request from Carsten Dominik
-    ;; <address@hidden>
-    "^[ \t\n]*\\([A-Z]+\\)[ \t\n]*-?[ \t\n]*\\([0-9]+ ?[A-Z]*\\)[ \t\n]*$"
-    ;; Match postcodes from Sweden where the five digits are grouped 3+2
-    ;; at the request from Mats Lofdahl <address@hidden>.
-    ;; (result is ("SE" (133 36)))
-    "^[ \t\n]*\\([A-Z]+\\)[ \t\n]*-?[ \t\n]*\\([0-9]+\\)[ \t\n]+\\([0-9]+\\)[ 
\t\n]*$")
-  "List of regexps that match legal postcodes.
-Whether this is used at all depends on the variable `ebdb-check-postcode'."
-  :group 'ebdb-record-edit
-  :type '(repeat regexp))
+If RECORDS are given, only search those records."
+  (seq-filter
+   (lambda (r)
+     (slot-value r 'dirty))
+   (or records ebdb-record-tracker)))
 
-(defcustom ebdb-default-user-field 'ebdb-field-notes
-  "Default field when editing EBDB records."
-  :group 'ebdb-record-edit
-  :type '(symbol :tag "Field"))
+;;; Getters
 
-
+;; TODO: Use :accessor tags for the simple cases.
 
-(defcustom ebdb-mail-avoid-redundancy nil
-  "How to handle the name part of `ebdb-dwim-mail'.
+(defun ebdb-record-cache (record)
+  (slot-value record 'cache))
 
-If nil, always return both name and mail.  If value is mail-only
-never use full name.  Other non-nil values mean do not use full
-name in mail address when same as mail.
-"
-  :group 'ebdb-sendmail
-  :type '(choice (const :tag "Allow redundancy" nil)
-                 (const :tag "Never use full name" mail-only)
-                 (const :tag "Avoid redundancy" t)))
+(defun ebdb-record-user-fields (record)
+  (slot-value record 'fields))
 
-(defcustom ebdb-complete-mail t
-  "If t MUA insinuation provides key binding for command `ebdb-complete-mail'."
-  :group 'ebdb-sendmail
-  :type 'boolean)
+(defun ebdb-record-user-field (record label)
+  (object-assoc (if (stringp label)
+                   label
+                 (symbol-name label))
+               'object-name (ebdb-record-user-fields record)))
 
-(defcustom ebdb-completion-list t
-  "Controls the behaviour of `ebdb-complete-mail'.
-If a list of symbols, it specifies which fields to complete.  Symbols include
-  name (= record's display name)
-  alt-names (= any other names the record has)
-  organization
-  mail (= all email addresses of each record)
-  primary (= first email address of each record)
-If t, completion is done for all of the above.
-If nil, no completion is offered."
-  ;; These symbols match the fields for which EBDB provides entries in
-  ;; `ebdb-hash-table'.
-  :group 'ebdb-sendmail
-  :type '(choice (const :tag "No Completion" nil)
-                 (const :tag "Complete across all fields" t)
-                 (repeat :tag "Field"
-                         (choice (const name)
-                                 (const alt-names)
-                                 (const organization)
-                                 (const primary)
-                                 (const mail)))))
+(defun ebdb-record-address (record &optional label)
+  (let ((addresses (slot-value record 'address)))
+    (if label
+       (object-assoc label 'object-name addresses)
+      addresses)))
 
-(defcustom ebdb-complete-mail-allow-cycling nil
-  "If non-nil cycle mail addresses when calling `ebdb-complete-mail'."
-  :group 'ebdb-sendmail
-  :type 'boolean)
+(defun ebdb-record-phone (record &optional label)
+  (let ((phones (slot-value record 'phone)))
+    (if label
+       (object-assoc label 'object-name phones)
+      phones)))
 
-(defcustom ebdb-complete-mail-hook nil
-  "List of functions called after a sucessful completion."
-  :group 'ebdb-sendmail
-  :type 'hook)
+(defun ebdb-record-mail (record &optional roles label defunct)
+  "Return a list of all RECORD's mail fields.
 
-(defcustom ebdb-mail-abbrev-expand-hook nil
-  ;; Replacement for function `mail-abbrev-expand-hook'.
-  "Function (not hook) run each time an alias is expanded.
-The function is called with two args the alias and the list
-of corresponding mail addresses."
-  :group 'ebdb-sendmail
-  :type 'function)
+If ROLES is non-nil, also consider mail fields from RECORD's
+roles.  If LABEL is a string, return the mail with that label.
+If DEFUNCT is non-nil, also consider RECORD's defunct mail
+addresses."
+  (let ((mails (slot-value record 'mail)))
+    (when (and roles (slot-exists-p record 'organizations))
+      (dolist (r (slot-value record 'organizations))
+       (when (and (slot-value r 'mail)
+                  (or defunct
+                      (null (slot-value r 'defunct))))
+         (push (slot-value r 'mail) mails))))
+    (unless defunct
+      (setq mails
+           (seq-filter (lambda (m)
+                         (null (eq (slot-value m 'priority) 'defunct)))
+                       mails)))
+    (if label
+       (object-assoc label 'object-name mails)
+      mails)))
 
-(defcustom ebdb-completion-display-record t
-  "If non-nil `ebdb-complete-mail' displays the EBDB record after completion."
-  :group 'ebdb-sendmail
-  :type '(choice (const :tag "Update the EBDB buffer" t)
-                 (const :tag "Do not update the EBDB buffer" nil)))
+
 
 (defun ebdb-dwim-mail (record &optional mail)
   ;; Do What I Mean!
@@ -3997,60 +4052,6 @@ RECORD.  If MAIL is nil use RECORD's primary mail 
address."
 
 ;;; Dialing and texting.
 
-(defcustom ebdb-dial-local-prefix-alist
-  '(((if (integerp ebdb-default-area-code)
-         (format "(%03d)" ebdb-default-area-code)
-       (or ebdb-default-area-code ""))
-     . ""))
-  "Mapping to remove local prefixes from numbers.
-If this is non-nil, it should be an alist of
-\(PREFIX . REPLACEMENT) elements. The first part of a phone number
-matching the regexp returned by evaluating PREFIX will be replaced by
-the corresponding REPLACEMENT when dialing."
-  :group 'ebdb-utilities-dialing
-  :type 'sexp)
-
-(defcustom ebdb-dial-local-prefix nil
-  "Local prefix digits.
-If this is non-nil, it should be a string of digits which your phone
-system requires before making local calls (for example, if your phone system
-requires you to dial 9 before making outside calls.) In EBDB's
-opinion, you're dialing a local number if it starts with a 0 after
-processing `ebdb-dial-local-prefix-alist'."
-  :group 'ebdb-utilities-dialing
-  :type '(choice (const :tag "No digits required" nil)
-                 (string :tag "Dial this first" "9")))
-
-(defcustom ebdb-dial-long-distance-prefix nil
-  "Long distance prefix digits.
-If this is non-nil, it should be a string of digits which your phone
-system requires before making a long distance call (one not in your local
-area code).  For example, in some areas you must dial 1 before an area
-code. Note that this is used to replace the + sign in phone numbers
-when dialling (international dialing prefix.)"
-  :group 'ebdb-utilities-dialing
-  :type '(choice (const :tag "No digits required" nil)
-                 (string :tag "Dial this first" "1")))
-
-(defcustom ebdb-dial-function nil
-  "If non-nil this should be a function used for dialing phone numbers.
-This function is used by `ebdb-dial-number'.  It requires one
-argument which is a string for the number that is dialed.
-If nil then `ebdb-dial-number' uses the tel URI syntax passed to `browse-url'
-to make the call."
-  :group 'ebdb-utilities-dialing
-  :type 'function)
-
-;; Signal integration.
-
-(defcustom ebdb-signal-program (executable-find "signal-cli")
-  "The name of the signal-cli program, if installed.
-
-This program must be present in order to send text messages
-through the Signal service."
-  :group 'ebdb-utilities-dialing
-  :type 'string)
-
 (defun ebdb-signal-get-number (record &optional no-prompt)
   "Extract a usable Signal number from RECORD.
 
@@ -4113,37 +4114,6 @@ command's docstring for more details."
        (ebdb--signal-text sender message recipients attachments)
       (message "Please set `ebdb-signal-program'"))))
 
-(defun ebdb-signal-text (sender records message attachments)
-  "Compose and send a text message using the Signal protocol.
-
-SENDER should be a phone number (with leading \"+\") to send
-from.  If `ebdb-record-self' is set, this record will be used as
-the sender, while RECORDS will be used as the list of recipients.
-In both cases, `ebdb-signal-get-number' will be used to find a
-usable number from the record.
-
-MESSAGE is the string to send as the body of the text message.
-ATTACHMENTS is a list of filenames to send as attachments on the
-message."
-  (interactive
-   (list (or (and ebdb-record-self
-                 (ebdb-signal-get-number
-                  (ebdb-gethash ebdb-record-self 'uuid)
-                  t))
-            (ebdb-read-string
-             "Number to send from (or set `ebdb-record-self'): "))
-        (ebdb-do-records)
-        (ebdb-read-string "Message contents: ")
-        (ebdb-loop-with-exit
-         (expand-file-name
-          (read-file-name "Attach file (C-g when done): "
-                          nil nil nil)))))
-  (let ((recipients
-        (delq nil (mapcar #'ebdb-signal-get-number records))))
-    (if ebdb-signal-program
-       (ebdb--signal-text sender message recipients attachments)
-      (message "Please set `ebdb-signal-program'"))))
-
 (defun ebdb--signal-text (sender message recipients &optional attachments)
   "Internal function for actually sending the SMS."
   (let ((command
@@ -4160,12 +4130,6 @@ message."
 
 ;;; Helper functions
 
-(defun ebdb-warn (&rest args)
-  "Display a message at the bottom of the screen.
-ARGS are passed to `message'."
-  (ding t)
-  (apply 'message args))
-
 (defun ebdb-string-trim (string &optional null)
   "Remove leading and trailing whitespace and all properties from STRING.
 If STRING is nil return an empty string unless NULL is non-nil."
@@ -4346,38 +4310,6 @@ and canonical addresses in the mail field of EBDB 
records."
 
 ;;; Massage of mail addresses
 
-(defcustom ebdb-canonical-hosts
-  ;; Example
-  (regexp-opt '("cs.cmu.edu" "ri.cmu.edu"))
-  "Regexp matching the canonical part of the domain part of a mail address.
-If the domain part of a mail address matches this regexp, the domain
-is replaced by the substring that actually matched this address.
-
-Used by  `ebdb-canonicalize-mail-1'.  See also `ebdb-ignore-redundant-mails'."
-  :group 'ebdb-utilities
-  :type '(regexp :tag "Regexp matching sites"))
-
-
-(defcustom ebdb-canonicalize-mail-function nil
-  "If non-nil, it should be a function of one arg: a mail address string.
-When EBDB is parsing mail addresses, the corresponding mail
-addresses are passed to this function first.  It acts as a kind
-of \"filter\" to transform the mail addresses before they are
-compared against or added to the database.  See
-`ebdb-canonicalize-mail-1' for a more complete example.  If this
-function returns nil, EBDB assumes that there is no mail address.
-
-See also `ebdb-ignore-redundant-mails'."
-  :group 'ebdb-utilities
-  :type 'function)
-
-(defcustom ebdb-message-clean-name-function 'ebdb-message-clean-name-default
-  "Function to clean up the name in the header of a message.
-It takes one argument, the name as extracted by
-`mail-extract-address-components'."
-  :group 'ebdb-utilities
-  :type 'function)
-
 (defun ebdb-canonicalize-mail-1 (address)
   "Example of `ebdb-canonicalize-mail-function'.
 However, this function is too specific to be useful for the general user.
@@ -4489,21 +4421,6 @@ This strips garbage from the user full NAME string."
 ;; `ebdb-hash-record' for each record.  This function is also called
 ;; when new records are added to the database.
 
-(defcustom ebdb-hash-extra-predicates nil
-  "Extra predicates when looking up entries in the EBDB hashtable.
-
-Predicates are used to filter results from the hashtable,
-ensuring that string lookups only return the results they're
-meant to.
-
-This option should be a list of conses, where the car is a
-symbol, and the cdr is a lambda form which accepts the string key
-and a record, and returns t if the key is acceptable for
-returning that record."
-  :group 'ebdb-search
-  :package-version "0.2"
-  :type '(repeat (cons symbol functionp)))
-
 (defun ebdb-puthash (key record)
   "Associate RECORD with KEY in `ebdb-hashtable'.
 KEY must be a string or nil.  Empty strings and nil are ignored."
@@ -4880,7 +4797,7 @@ The formatting rules are defined in 
`ebdb-address-format-list'."
 (defun ebdb-cite-records (&optional records arg)
   (interactive (list (ebdb-prompt-for-record)
                     current-prefix-arg))
-  (let ((recs (ebdb-record-list records))
+  (let ((recs (if (listp records) records (list records)))
        (style (if arg 'list 'inline))
        usable)
     (dolist (r recs)
@@ -4899,7 +4816,7 @@ differently by different major modes.
 This is a generic function that dispatches on the value of
 `major-mode'.  It only inserts names and mail addresses.")
 
-(cl-defmethod ebdb-records-cite (style records)
+(cl-defmethod ebdb-records-cite (_style records)
   "The fallback catch-all method."
   (when records
     (mapcar (lambda (pair)
@@ -4909,8 +4826,8 @@ This is a generic function that dispatches on the value of
            records)))
 
 (cl-defmethod ebdb-records-cite :around ((_style (eql list))
-                                             (_records list)
-                                             &context (major-mode org-mode))
+                                        (_records list)
+                                        &context (major-mode org-mode))
   (let ((list (cl-call-next-method)))
     (mapconcat (lambda (elt)
                 (format "- %s" elt))
@@ -4926,15 +4843,15 @@ This is a generic function that dispatches on the value 
of
          records))
 
 (cl-defmethod ebdb-records-cite :around ((_style (eql list))
-                                             (_records list)
-                                             &context (major-mode html-mode))
+                                        (_records list)
+                                        &context (major-mode html-mode))
   (let ((list (cl-call-next-method)))
     (mapconcat (lambda (l)
                 (format "<li>%s</li>" l))
               list "\n")))
 
 (cl-defmethod ebdb-records-cite :around ((_style (eql inline))
-                                             (_records list))
+                                        (_records list))
   (let ((list (cl-call-next-method)))
     (mapconcat #'identity list " ")))
 
@@ -4979,6 +4896,9 @@ With prefix ARG, insert string at point."
 
 ;;; Searching EBDB
 
+(defvar ebdb-search-invert nil
+  "Bind this variable to t in order to invert the result of `ebdb-search'.")
+
 (defun ebdb-message-search (name mail)
   "Return list of EBDB records matching NAME and/or MAIL.
 First try to find a record matching both NAME and MAIL.
@@ -5051,7 +4971,7 @@ interpreted as t, ie the record passes."
   FIELD.")
 
 (cl-defgeneric ebdb-record-search (record type criterion)
-  "Return t if CRITERION matches RECORD, given STYLE.")
+  "Return t if CRITERION matches RECORD, given TYPE.")
 
 (cl-defmethod ebdb-field-search ((field ebdb-field) (regex string))
   (condition-case nil
diff --git a/helm-ebdb.el b/helm-ebdb.el
index ae1fbbc..53f4199 100644
--- a/helm-ebdb.el
+++ b/helm-ebdb.el
@@ -63,7 +63,7 @@
 (defun helm-ebdb-cite-records (candidate)
   "Insert Name <email> string for CANDIDATE or marked candidate."
   (let ((recs (or (helm-marked-candidates) (list candidate))))
-    (ebdb-cite-records-mail recs)))
+    (ebdb-cite-records recs)))
 
 (defvar helm-source-ebdb
   '((name . "EBDB")



reply via email to

[Prev in Thread] Current Thread [Next in Thread]