Emacs BBDB: Filtering tags with the power of lambda expressions
| bbdb, connecting, emacsWhat do you do when you're into both Emacs geeks and social
networking? Well, you build a really really weird contact management
tool, that's what!
One of the things I often need to do is filter my contacts for a
particular set of interests. I would have no idea how to do this in
Microsoft Outlook and other proprietary contact management systems.
Because Emacs is infinitely programmable, though, I can just hack it
in.
You'd expect the intersection of the set “emacs geek” and the set
“social networker” to be a null set or a singleton (me!). As it turns
out, there's at least one other geek in this space – hooray!
Paul Lussier's been bouncing all sorts of
crazy ideas off me, which explains all the weird
porridge-and-toe-nails posts of Emacs Lisp code on my blog lately.
He's responsible for my puttting together yesterday's LinkedIn
importer. Today, he wrote:
Then found your sacha/bbdb-search-tags stuff which
totally, completely rocks. I just wish I had the first inkling as to
how it worked :) Now, the question I have is: How can I use
sacha/bbdb-search-tags to search for entries which are tagged with one
label, but NOT with another? For example, I want to search on: (and
(taq eq “planner”) (not (tag eq “muse”)))
I'd completely forgotten about sacha/bbdb-search-tags! Anyway, I'd
been meaning to write a fancy alias management thing for a while now,
and this code does a reasonable job for me. I can now filter my
displayed records by arbitrary Lisp expressions, bringing me closer to
insane contact relationship management. I mean, c'mon…
;; M-x sacha/bbdb-filter-by-alias-function RET ;; (lambda (aliases) (and (member "planner" aliases) ;; (not (member "muse" aliases))))) RET
If I do this often enough, I might make up an easier syntax, but
lisp expressions work fine for me.
MWAHAHAHAHA!
Here's the code:
;;;_+ Mail aliases ;; Code for working with aliases ;; You can use "a" (bbdb-add-or-remove-mail-alias) in BBDB buffers to add ;; a mail alias to the current entry, or "* a" to add a mail alias to ;; all displayed entries. ;; Goal: Be able to specify ALIAS and ALIAS ;; M-x sacha/bbdb-filter-displayed-records-by-alias RET alias alias ;; Goal: Be able to specify ALIAS or ALIAS ;; C-u M-x sacha/bbdb-filter-displayed-records-by-alias RET alias alias ;; Goal: Be able to specify not ... ;; M-x sacha/bbdb-omit-displayed-records-by-alias RET alias alias ;; C-u M-x sacha/bbdb-omit-displayed-records-by-alias RET alias alias (defun sacha/bbdb-filter-by-alias-match-all (query-aliases record-aliases) "Return non-nil if all QUERY-ALIASES are in RECORD-ALIASES." (let ((result t)) (while query-aliases (unless (member (car query-aliases) record-aliases) (setq query-aliases nil result nil)) (setq query-aliases (cdr query-aliases))) result)) (defun sacha/bbdb-filter-by-alias-match-any (query-aliases record-aliases) "Return non-nil if any in QUERY-ALIASES can be found in RECORD-ALIASES." (let (result) (while query-aliases (when (member (car query-aliases) record-aliases) (setq query-aliases nil result t)) (setq query-aliases (cdr query-aliases))) result)) ;; Moved this to a convenience function so that we don't ;; have to deal with invert and property splitting. (defun sacha/bbdb-filter-by-alias (bbdb-records alias-filter-function query &optional invert) "Return only the BBDB-RECORDS that match ALIAS-FILTER-FUNCTION. ALIAS-FILTER-FUNCTION should accept two arguments: - QUERY, a list of keywords to search for - aliases, a list of keywords from the record If INVERT is non-nil, return only the records that do not match." (delq nil (mapcar (lambda (rec) (if (funcall alias-filter-function query (split-string (or (bbdb-record-getprop (if (vectorp rec) rec (car rec)) propsym) "") "[ \n\t,]+")) (when (null invert) rec) (when invert rec))) bbdb-records))) ;; Splitting this into two functions because of interactive calling. (defun sacha/bbdb-filter-displayed-records-by-alias (query &optional any) "Display only records whose mail-aliases match QUERY. If ANY is non-nil, match if any of the keywords in QUERY are present. See also `sacha/bbdb-omit-displayed-records-by-alias'." (interactive (list (let ((crm-separator " ")) (completing-read-multiple "Mail aliases: " (bbdb-get-mail-aliases))) current-prefix-arg)) (when (stringp query) (setq query (split-string query "[ \n\t,]+"))) (bbdb-display-records (sacha/bbdb-filter-by-alias-by-function (or bbdb-records (bbdb-records)) (if any 'sacha/bbdb-filter-by-alias-match-any 'sacha/bbdb-filter-by-alias-match-all) query))) ;; Splitting this into two functions because of interactive calling. (defun sacha/bbdb-omit-displayed-records-by-alias (query &optional any) "Display only records whose mail-aliases do not match QUERY. If ANY is non-nil, match if any of the keywords in QUERY are present. See also `sacha/bbdb-filter-displayed-records-by-alias'." (interactive (list (let ((crm-separator " ")) (completing-read-multiple "Mail aliases: " (bbdb-get-mail-aliases)) current-prefix-arg))) (when (stringp query) (setq query (split-string query "[ \n\t,]+"))) (bbdb-display-records (sacha/bbdb-filter-by-alias-by-function (or bbdb-records (bbdb-records)) (if any 'sacha/bbdb-filter-by-alias-match-any 'sacha/bbdb-filter-by-alias-match-all) query t))) ;;;_+ Advanced mail alias queries ;; Goal: Use complicated lambda expressions to filter displayed records ;; M-x sacha/bbdb-filter-by-alias-function RET ;; (lambda (aliases) (and (member "planner" aliases) ;; (not (member "muse" aliases))))) RET ;; Thanks to Paul Lussier for the suggestion! (defun sacha/bbdb-filter-by-alias-function (bbdb-records alias-filter-function) "Return only the BBDB-RECORDS that match ALIAS-FILTER-FUNCTION. ALIAS-FILTER-FUNCTION should accept one argument: - aliases, a list of keywords from the record." (interactive (list (or bbdb-records (bbdb-records)) (read t))) (let (records) (setq records (delq nil (mapcar (lambda (rec) (when (funcall alias-filter-function (split-string (or (bbdb-record-getprop (if (vectorp rec) rec (car rec)) propsym) "") "[ \n\t,]+")) rec)) bbdb-records))) (if (interactive-p) (bbdb-display-records records)) records))