Whose in AppleScript

For several years, I’ve had a little utility program called addr that looks up a name in my Contacts (née Address Book) and returns the name and address formatted as if it were to be printed on an envelope or mailing label:

Brian Kernighan
Unix, Inc.
321 Troff Lane
Murray Hill, NJ 07974

The program was written in Python and used the appscript library to communicate with Contacts. It continued to work, despite the trouble other appscript-powered scripts had,1 so I never converted it to my applescript library. I’ve been thinking, though, about generalizing the script and adding options to it. I don’t want to do that to a script using a deprecated and orphaned library, so the first order of business is to get rid of the appscript dependency.

The script gets called this way:

addr kern brian

The arguments can be any part of any name in any order—whatever’s necessary to find the contact I want. In AppleScript, this means using a whose clause with multiple parameters, like

get people whose name contains "kern" and name contains "brian"

I learned the multiple whose syntax from this MacScripter page. The same basic structure works for a mixture of properties, too:

get people whose name contains "kern" and organization contains "unix"

(Interestingly, if you Google for “applescript whose,” you’re likely to see this decade-old Daring Fireball post at or near the top of the search results. Remember when Gruber used to post code? Those were the days.)

So with that syntax in mind, here’s the new version of addr:

python:
 1:  #!/usr/bin/python
 2:  
 3:  from applescript import asrun
 4:  import sys
 5:  
 6:  # Titles and countries that we're going to remove.
 7:  titles = ("Mr.", "Ms.", "Miss", "Mrs.", "Dr.")
 8:  us = ('USA', 'US', 'United States', 'United States of America')
 9:  
10:  nameclause = 'name contains "' + \
11:               '" and name contains "'.join(sys.argv[1:]) + '"'
12:  
13:  cmd = '''tell application "Contacts"
14:    set match to first item of (people whose {})
15:    set fullname to name of match
16:    set addr to formatted address of first item of (addresses of match)
17:    set org to organization of match
18:    if org is not missing value then
19:      set addr to org & linefeed & addr
20:    end if
21:    get fullname & linefeed & addr
22:  end tell'''.format(nameclause)
23:  
24:  # Get the address as a list of lines Remove trailing whitespace
25:  # and left-to-right Unicode characters.
26:  addrlines = asrun(cmd).rstrip().replace('\xe2\x80\x8e', '').split('\n')
27:  
28:  # Get rid of the title and the country if it's the US.
29:  nameparts = addrlines[0].split()
30:  if nameparts[0] in titles:
31:    addrlines[0] = ' '.join(nameparts[1:])
32:  if addrlines[-1] in us:
33:    addrlines = addrlines[:-1]
34:  
35:  print '\n'.join(addrlines)

The goal of my applescript library is to allow me to write the stuff AppleScript is good at in AppleScript and the stuff Python is good at in Python, and you can see that in operation here.

Lines 10–11 use Python to get the script arguments and turn them into the name contains... and name contains... part of the whose clause. This clause is then interpolated into the cmd string in Lines 13–22. This chunk of AppleScript is then run in Line 26, and the output is collected for further processing.

Before discussing that processing, I want to point out a couple of things about the AppleScript itself. First, it’s written in a sort of “just shut up and do it” style. If more than one contact matches the search terms, it doesn’t report the ambiguity, it returns the first one in the list. Similarly if the contact has more than one address. Although this approach (which the appscript version also used) has served me well, it’s one of things I want to change when I improve the script.

Second, most AppleScript examples that use whose would write it with every:

get every person whose name contains "brian"

I find the plural construction more pleasing:

get people whose name contains "brian"

They both work, so use what you find most comfortable.

One of the problems with the formatted address construct is that it puts weird characters in the city, state, zip code line. If you use a hexdump utility like xxd to investigate the output, you’ll see that it’s the three-byte sequence E2 80 8E, which is the UTF-8 left-to-right mark. Why Apple puts it in the formatted address, I have no idea—it’s meant to signal the beginning of a section of left-to-right text in a bidirectional corpus. Whatever the reason it’s there, I want it out, which is what the replace in Line 26 is for.

Most of my Contacts entries have a courtesy title, which I don’t want in the output, so Lines 30–31 look for the titles defined in Line 8 and strip them out. Similarly, I don’t want to include the country if it’s a domestic address, so Lines 32–33 remove the various ways “United States” is given among my contacts.2

I’ll probably spare you the tedium of any future improvements I make to addr. They’ll be pretty rote things like adding options for which address to print and printing a warning when there’s an ambiguity. Mostly, I posted this code so I could start with the “Whose in AppleScript” title and end with this video.

 


  1. The comments on that post by Hamish Sanderson, Clark Goble, and Matt Neuburg are one of the reasons I kept comments here for as long as I did. If you follow the link and start reading, you’ll never come back. 

  2. Why do I bother with a country entry for the US? And if I include it, why isn’t it uniform? Well, many of my Contacts entries are imported from vCards, and I get whatever comes along. I do periodically run a script that tries to clean up the country entries, but I often go months between cleanings.