6091 comments
2357 subscribers
6239 on Twitter
Subscribe! Feed reader E-mail

Emacs BBDB: Filtering tags with the power of lambda expressions

What 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))

On Technorati: , , , ,

Short URL: http://sachachua.com/blog/p/3896

On This Day...

  • 2012: Sketchnotes from WordCamp Toronto Day 1: Marketing, giving back, multilingual sites, security, SEO & analytics, e-commerce — Click on the images for larger version. Please feel free to share these! You can credit it as © 2012 [...]
  • 2011: Transcript: Blogging (Part 5): Getting started — Hat-tip to Holly Tse for organizing this interview! At the end of the blog series, I’ll put them all together [...]
  • 2010: Book: Thank You for Arguing — (c) 2009 Mark Robinson – Creative Commons Attribution 2.0 Licence Thank You for Arguing: What Aristotle, Lincoln [...]
  • 2009: Personal connection and a trip to the dentist — A personal connection can make going to the dentist a lot of fun. I like going to my dentist. Part of [...]
  • 2009: Dinner on the periodic table — Geekiness makes my heart flutter. Last night, we had dinner with W-’s family. Conversation topic? The periodic table, would you believe [...]
  • 2008: Weekly review: Week ending Sept 28 — Accomplishments: I prepared and gave a presentation on Gen Y and how work is changing to an IBM team that [...]
  • 2006: Geek dinners and networking ideas — Kevin McIntosh’s presentation on networking reminded me of Geek Dinner. I’ve been meaning to organize one of these for ages. You [...]
  • 2006: The power of sales — Simon just finished a sales call that came in through a referral. He’s got a pretty nifty voice messaging system (for [...]
  • 2006: Networking is about being memorable; the art of the deep bump — Kevin McIntosh made a couple of great points at last night’s Newpath Network workshop. One of my favorite ideas from [...]
  • 2006: Networking story: Being in the right place at the right time — At last night’s New Path Network workshop, Alex Sirota told us a story about how Fernando Morales networked his way [...]
  • 2006: Emacs BBDB: Prioritize exact matches — I often include people’s names in my notes on other people, such as when I’m tracking who introduced me to whom. [...]
  • 2006: Notes from KMD2004 meeting — Attendance: Dave Kemp, MJ Suhonos, Sacha Chua Next action Work on individual ANT maps, due Oct 5 Next meeting Oct 5 2:00 PM at [...]
  • 2006: Stories from the NY trip — The e-ticket from the bus company said that people should arrive twenty minutes early in order to keep their reservation priority. [...]
  • 2006: Stories from New York: Making things happen — I was thrilled when the Greater IBM Initiative invited me to be one of the Core Connectors. Thrilled, yes, and more [...]
  • 2006: Networking: Create value with your business cards — Business cards are not only a good way to leave your contact information with people, but they can be a great [...]
  • 2006: Hospitality — Someday I want to be able to make people feel as welcome as Luis Suarez felt in Cincinnati: But on [...]
  • 2006: Taking the Terror out of Talk — Does the thought of speaking in public make you anxious? Want some tips on how to deal with the butterflies in [...]
  • 2005: Synchronicity — Life happens when you open yourself to unexpected things. Today I talked to James, Greenpeace volunteer in front of Graduate House. I [...]
  • 2005: Not joining the Rotary Club — Frank Adamo suggested joining the Rotary Club or the Rotaract Club, so I filled out the online application form for the [...]
  • 2005: More about lectures — Today’s class in Engineering Psychology and Human Performance was a lot more engaging. Prof. Milgram walked us through a typical problem, and [...]
  • 2004: find-buffer-visiting — Now there’s a useful Emacs function. I might be able to use that for planner-tasks-file-behavior…
  • 2004: Mysterious font-locking problems — It appears that newly-opened files don’t have buffer-file-names until they’re saved. I may have to do something fancy.
  • 2004: Letter for Marcelle — Happy birthday, Marcell! I’ll unencrypt this if the postcard gets lost. -----BEGIN PGP MESSAGE----- Version: GnuPG v1.2.5 (GNU/Linux) hQEOAzQ6c9jHW5SMEAQAvrfUEFGHbeuvc66Zg4isWG+IU3n/ezUJCgPBh3Eoqx0t otsMX+eq7av1bsrLHWkvRPblcb42bEvZjXdCSVUJkgxLoYc6C16r+2UxGaHWnZkL 57XBsKl7sMPtp5V3xPNpevuU5qeqj/Bp14jIq0I63Zyw3YHvag27y6Q7y/f9snwE AJjql9Rs/EESr1ULq9Y5vqsefT1BZfryt4EhFz3cRT1I8yxhODqp1T3Plk6p4k5P MVD+jJY3SQXJdKS0jHJXI3hbz9JAlypy984GPYG8j8dhTQP+ltCzr7Gaxack4U9O +oTGm6PcQeZon46Q/y1rjZdw6zO/c7bWC3b7+2A7xl/o0sC7AU8qiEUHb5oU+dML 79zTUOgCu6Zn4rRCPbeipkY/LRrZay+jd5Hb8J0ATkLPa6LRLjVB+pEN5PIp6Zav /YW2Su9gEeN3V1kMlZauv+vwQfsOC065PJ51koe+C4G1wTzTOlc0Wh/wpwfAlaAS 95Gz8RmupeaHiZcY/zKGKpHZ0Gq0AAy5nRYJTC/wqjlP0lLTFxyb4QjZWATrI3cM Bd8o8oYTfmSYgDdjRgvlyurVLoQlLQ7uknvTGT5J5c74htzdGLPq0msmKFr3f7fz 01yXQFvNOUMQ8ghVjDl5p7Hm5tIDztB+bhbodwomIOAmj4d+uMqIayb9AtV3+e6d Od4xuwOq59Zr9JynGjvQjdQzs6SvTLhJdT5S24rZGsnGwGxusQESbqVdnvd9dCjX /bM1MgT2VaeNBSyD9T5jKwXngg6hpBJNCqyGLb6E81VYja8OP9kCOdncI+nThyVI i0MXOlN/2LuYMMMv4hqE6em56haCpsa6G8ezH8HGTg== =rruK -----END PGP MESSAGE-----
  • 2003: SMIT Ed — Review of the minutes Review of the textbooks The group met last Friday. They have decided to look at both the elementary and [...]
  • 2003: TagBoard — I should integrate this into the site someday. netscape_support(); Powered by TagBoard Message Board Name URL or Email Messages(smilies) rememberme()
  • 2003: Elisp snippet for mailing feedback — (defun sacha/mail-cs21a-feedback (id) (interactive "MID:") (save-excursion (insert "\n-------------------------------------------------------------------\n" [...]
  • 2003: Upcoming ACM regionals — http://icpc.baylor.edu/icpc/regionals/UpcomingRegionals.html

Get the highlights as a PDF!

Stories from my Twenties: Highlights from a Decade of Blogging

Free sample!