initial version
authorIan Kelling <ian@iankelling.org>
Tue, 8 Apr 2014 13:53:10 +0000 (06:53 -0700)
committerIan Kelling <ian@iankelling.org>
Tue, 8 Apr 2014 13:53:10 +0000 (06:53 -0700)
bbdb3-csv-import.el [new file with mode: 0644]

diff --git a/bbdb3-csv-import.el b/bbdb3-csv-import.el
new file mode 100644 (file)
index 0000000..da817cf
--- /dev/null
@@ -0,0 +1,182 @@
+;;; bbdb3-csv-import.el  --- import csv to bbdb version 3+ -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 by Ian Kelling
+
+;; Author: Ian Kelling <ian@iankelling.org>
+;; Created: 1 Apr 2014
+;; Version: 1.0
+;; Keywords: csv, util, bbdb
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Some tools such as Thunderbird and Outlook allow for exporting contact data as
+;; CSV (Comma Separated Value) files.  This package, `bbdb3-csv-import.el', allows
+;; for importing such files into Emacs's bbdb database, version 3+.
+
+;;; Installation:
+;;
+;; dependencies. Available in marmalade/melpa or via the internet
+;; pcsv.el, dash.el, bbdb
+;; 
+;; (load-file FILENAME-OF-THIS-FILE)
+;; or
+;; (add-to-list 'load-path DIRECTORY-CONTAINING-THIS-FILE)
+;; (require 'bbdb3-csv-import)
+
+;;; Usage:
+;;
+;; Backup or rename ~/.bbdb while testing that the import works correctly
+;;
+;; Simply call `bbdb3-csv-import-buffer' or `bbdb3-csv-import-file'. Interactively
+;; they prompt for file/buffer. Use non-interactively for no prompts.
+;;
+;; Thunderbird csv data works out of the box. Otherwise you will need to create
+;; a mapping table to suit your data and assign it to
+;; bbdb3-csv-import-mapping-table. Please send any new mapping tables upstream so
+;; I can add it to this file for other's benefit. Ian Kelling is willing to help
+;; if any issues arise.
+;;
+
+(require 'pcsv)
+(require 'bbdb)
+(require 'dash)
+
+
+(defvar bbdb3-csv-import-mapping-table bbdb3-csv-import-thunderbird
+  "The table which maps bbdb3 fields to csv fields.
+Use the default as an example to map non-thunderbird data.
+Name used is firstname + lastname or name.
+After the car, all names should map to whatever csv
+field names are used in the first row of csv data.
+Many fields are optional. If you aren't sure if one is,
+best to just try it. The doc string for `bbdb-create-internal'
+may be useful for determining which fields are required.")
+
+(defconst bbdb3-csv-import-thunderbird
+      '(("firstname" "First Name")
+        ("lastname" "Last Name")
+        ("name" "Display Name")
+        ("aka" "Nickname")
+        ("mail" "Primary Email" "Secondary Email")
+        ("phone" "Work Phone" "Home Phone" "Fax Number" "Pager Number" "Mobile Number")
+        ("address"
+         ("home address" (("Home Address"
+                           "Home Address 2")
+                          "Home City"
+                          "Home State"
+                          "Home ZipCode"
+                          "Home Country"))
+         ("work address" (("Work Address"
+                           "Work Address 2")
+                          "Work City"
+                          "Work State"
+                          "Work ZipCode"
+                          "Work Country")))
+        ("organization" ("Organization"))
+        ("xfields" "Web Page 1" "Web Page 2" "Birth Year" "Birth Month"
+         "Birth Day" "Department" "Custom 1" "Custom 2" "Custom 3"
+         "Custom 4" "Notes" "Job Title")))
+
+
+;;;###autoload
+(defun bbdb3-csv-import-file (filename)
+  "Parse and import csv file FILENAME to bbdb3."
+  (interactive "fCSV file containg contact data: ")
+  (bbdb3-csv-import-buffer (find-file-noselect filename)))
+
+
+;;;###autoload
+(defun bbdb3-csv-import-buffer (&optional buffer-or-name) 
+  "Parse and import csv BUFFER-OR-NAME to bbdb3.
+Argument is a buffer or name of a buffer.
+Defaults to current buffer."
+  (interactive "bBuffer containing CSV contact data: ")
+  (let* ((csv-fields (pcsv-parse-buffer (get-buffer (or buffer-or-name (current-buffer)))))
+         (csv-contents (cdr csv-fields))
+         (csv-fields (car csv-fields))
+         (initial-duplicate-value bbdb-allow-duplicates)
+         csv-record)
+    ;; Easier to allow duplicates and handle them post import vs failing as
+    ;; soon as we find one.
+    (setq bbdb-allow-duplicates t)
+    (while (setq csv-record (map 'list 'cons csv-fields (pop csv-contents)))
+      (cl-flet* 
+          ((rd-assoc (list) (rd (lambda (elem) (assoc-plus elem csv-record)) list))
+           (mapcar-assoc (list) (mapcar (lambda (elem) (cdr (assoc elem csv-record))) list))
+           (field-map (field) (cdr (assoc field bbdb3-csv-import-mapping-table)))
+           (map-assoc (field) (assoc-plus (car (field-map field)) csv-record))
+           ;; EPA compliance (Emacs pollution agency)
+           (rd bbdb3-csv-import-reduce)
+           (assoc-plus bbdb3-csv-import-assoc-plus))
+        (let ((name (let ((first (map-assoc "firstname"))
+                          (last (map-assoc "lastname"))
+                          (name (map-assoc "name")))
+                      (if (and first last)
+                          (cons first last)
+                        (or name first last ""))))
+              (phone (rd (lambda (elem)
+                           (let ((data (assoc-plus elem csv-record)))
+                             (if data (vconcat (list elem data)))))
+                         (field-map "phone")))
+              (xfields (rd (lambda (field)
+                             (let ((value (assoc-plus field csv-record)))
+                               (when value
+                                 (while (string-match " " field)
+                                   ;; turn csv field names into symbols for extra fields
+                                   (setq field (replace-match "" nil nil field)))
+                                 (cons (make-symbol (downcase field)) value))))
+                           (field-map "xfields")))
+              (address (rd (lambda (x)
+                             (let ((address-lines (mapcar-assoc (caadr x)))
+                                   (address-data (mapcar-assoc (cdadr x))))
+                               ;; determine if non-nil and put together the  minimum set
+                               (when (or (not (-all? '(lambda (arg) (zerop (length arg))) address-data))
+                                         (not (-all? '(lambda (arg) (zerop (length arg))) address-lines)))
+                                 (when (> 2 (length address-lines))
+                                   (setcdr (max 2 (nthcdr (-find-last-index (lambda (x) (not (null x)))
+                                                                            address-lines)
+                                                          address-lines)) nil))
+                                 (vconcat (list (car x)) (list address-lines) address-data))))
+                           (cdr (assoc "address" bbdb3-csv-import-mapping-table))))
+              (mail (rd-assoc (field-map "mail")))
+              (organization (rd-assoc (field-map "organization")))
+              (affix (map-assoc "affix"))
+              (aka (rd-assoc (field-map "aka"))))
+          (bbdb-create-internal name affix aka organization mail
+                                phone address xfields t)))))
+  (setq bbdb-allow-duplicates initial-duplicate-value))
+
+
+;;;###autoload
+(defun bbdb3-csv-import-reduce (func list)
+  "like mapcar but don't build nil results into the resulting list"
+  (-reduce-from (lambda (acc elem)
+                  (let ((funcreturn (funcall func elem)))
+                    (if funcreturn
+                        (cons funcreturn acc)
+                      acc)))
+                nil list))
+
+;;;###autoload
+(defun bbdb3-csv-import-assoc-plus (key list)
+  "Like `assoc' but turn an empty string result to nil."
+  (let ((result (cdr (assoc key list))))
+    (when (not (string= "" result))
+      result)))
+
+(provide 'bbdb3-csv-import)
+
+;;; bbdb3-csv-import.el ends here