Addresses, labels, and scripts

A few days ago I added a new script to my plabels repository at GitHub. This is the repository that contains my scripts for printing on Avery address labels, although the previous two scripts in the repository weren’t for printing addresses. This one is.

The script is called palabels, and it’s set up to print on Avery 5164 labels and any clones that match the 5164’s 3⅓×4 size and layout. I use it for labels on boxes and large envelopes.

The input consists of plain text, formatted like this:

John Smith
ABC Company
123 Main Street
Anytown, NY 10000

Jane Eyre
Mack, Book & Eyre LLP
1212 First Avenue
Ploddington, NH 00012

Blank lines separate the addresses. When run through palabels, the result looks like this:

Sample of palabels

The return address is an EPS file named logoaddr.eps kept in the ~/graphics directory. The graphic will be scaled to fit in a box 3″ long by ½″ high. You can change the name and location of the EPS file by editing this line near the top of palabels:

$img = "$ENV{'HOME'}/graphics/logoaddr.eps";

The labels don’t have to start in the upper left corner of the sheet. Both pflabels and ptlabels take two options: -r m and -c n, where m and n give the row and column numbers of the first label to be printed. This allows you to print part of a sheet with one command, and then pick up where you left off the next time.

If the input specifies more labels than can fit on a single sheet, the scripts will continue at the top left corner of a new sheet.

You might be wondering why I use script to print address labels instead of the printing features built into the Mac’s Address Book. There is, after all, a setting for printing to Avery 5164s. Three reasons come to mind.

  1. I first wrote this script about a dozen years ago, back when I was using Linux and didn’t have a GUI address label printing program. When I returned to the Mac in 2005, I was used to using the script and didn’t see any reason to change my work habits.
  2. The Address Book doesn’t have a good way to format the label the way I want, especially the return address, which I like having at the top of the label, separated from the “To” address by a line.
  3. The Address Book doesn’t have a good way—or any way, as far as I can tell—to print a single address in any spot other than the top left position on the label sheet. Unless you always print address labels in multiples of six, you’ll end up with unused labels.1

Under Linux, my “address book” was just a plain text file with a list of names, addresses, telephone number, and email addresses. Entries were kept in alphabetical order and separated by lines of hyphens. It was easy to copy a name and address from that file and use it as the input to palabels. That isn’t the case with Address Book on the Mac.

Although I don’t dislike Address Book—even the new one in Lion—as much as some do, I’ve always been frustrated with the inability to simply copy the name and address from it in a single action. You can click on the “work” label and choose Copy Mailing Label from the popup menu, but that adds the country field (my Address Book entries have a country field because several of my clients are based outside the US), which I don’t want to include in a domestic mailing. So I have to either delete the country almost every time I paste it or…

Or write some scripts. Here’s an AppleScript called Copy Mailing Label Without Country that I have saved in my ~/Library/Address Book Plugins folder.

 1:  using terms from application "Address Book"
 2:    on action property
 3:      return "address"
 4:    end action property
 6:    on should enable action for thePerson with theEntry
 7:      if theEntry is not missing value then
 8:        return true
 9:      else
10:        return false
11:      end if
12:    end should enable action
14:    on action title for thePerson with theEntry
15:      return "Copy Mailing Label Without Country"
16:    end action title
18:    on perform action for thePerson with theEntry
19:      set theName to name of thePerson
20:      set theAddress to formatted address of theEntry
21:      set theLabel to theName & return & theAddress
22:      if last paragraph of theLabel is in {"US", "USA", "United States"} then
23:        set lineList to paragraphs of theLabel
24:        set newLineList to reverse of rest of reverse of lineList
25:        set tid to AppleScript's text item delimiters
26:        set AppleScript's text item delimiters to return
27:        set theLabel to newLineList as text
28:        set AppleScript's text item delimiters to tid
29:      end if
30:      set the clipboard to theLabel
31:    end perform action
33:  end using terms from

This puts a new item at the end of the popup menu that appears when I click on the little label next to an address in Address Book.

Address Book popup with new item

The name of the menu item isn’t quite right. Although the clipboard won’t include the country line if the country is “US,” “USA,” or “United States,” it will include it for any other countries. Which is just what I want for a mailing label.

I don’t actually use the popup very often. I prefer another script called plainaddress, which is invoked from the command line like this:

plainaddress John Smith

Assuming I have a “John Smith” in my Address Book, it will return his name and address, formatted like this:

John Smith
ABC Company
123 Main Street
Anytown, NY 10000

Since I often include middle initials in my Address Book entries but can’t remember them when I want to look up a client, the script is clever enough to recognize “John A. Smith” when I ask for “John Smith.” Here it is:

 1:  #!/usr/bin/env python
 3:  from appscript import *
 4:  import sys
 6:  titles = ("Mr.", "Ms.", "Miss", "Mrs.", "Dr.")
 7:  us = ('USA', 'US', 'United States', 'United States of America')
 9:  def plainAddress(strings):
10:      'Return the name and address of the Address Book contact that matches.'
12:      # Get the first Address Book contact whose name is a match.
13:      contacts = app(u'/Applications/Address').people[[0])].get()
14:      while len(strings) > 1:
15:          del strings[0]
16:          for i,c in enumerate(contacts):
17:              if strings[0].lower() not in
18:                  contacts[i] = ''
19:          contacts = [ c for c in contacts if c != '']
21:      a = contacts[0]
23:      # Collect all the address lines in here.
24:      lines = []
26:      # Get the full name and drop the title, if present.    
27:      nameWords =
28:      if nameWords[0] in titles:
29:          del nameWords[0]
30:      name = ' '.join(nameWords)
31:      lines.append(name)
33:      # Get the company name, if present.
34:      if a.organization.get() != '':
35:        lines.append(a.organization.get())
37:      # Get the address, deleting the "left-to-right mark" characters.
38:      addr = a.addresses()[0].formatted_address().replace(u'\u200e', u'')
39:      lines.extend(addr.split('\n'))
41:      # Delete the country if it's the US.
42:      if lines[-1] in us:
43:          del lines[-1]
45:      # Return as a multiline string.
46:      plain = "\n".join(lines)
47:      return plain
49:  if __name__ == '__main__':
50:      print plainAddress(sys.argv[1:]).encode('utf8')

It uses the third party Appscript library to access Address Book in Line 13. The rest is pure Python. You’ll note that plainaddress is also smart enough to leave off the country if the addressee is in the US.

If I need to print an address label for Mr. Smith at row 2 and column 2 on the sheet, I combine plainaddress and palabels:

plainaddress John Smith | palabels -r2 -c2

If you’re wondering why I gave plainaddress a long and unwieldy name, it’s because it was one of a pair of scripts for looking up addresses. The other, texaddress, returned the same information but had double backslashes at the end of each line for insertion into LaTeX documents.

John Smith\\
ABC Company\\
123 Main Street\\
Anytown, NY 10000

I don’t use texaddress very often anymore because I seldom write directly in LaTeX, but I’m so used to the two script names that I haven’t bothered to shorten plainaddress’s name.

  1. Yes, you can turn the sheet end-for-end to make what was the bottom right position the top left, but if you tend to print labels one at a time, as I do, you’ll still end up with unused labels.