LaunchBar and LaTeX

When I send a proposal for new work to a client, I do it in the form of a letter addressed to the client and emailed to them as a PDF attachment.

1 I write the letter in LaTeX, using TextExpander for the boilerplate and then filling in the job-specific parts. One of the job-specific parts is the name and address of the client, which, in a LaTeX letter, is the second argument to the \begin block:

\begin{letter}{John Appleseed\\
Apple Inc.\\
1 Infinite Loop\\
Cupertino, CA 95014}

In LaTeX, linebreaks in multiline blocks of text have to be preceded by a pair of backslashes. Also, because the ampersand is a special character in LaTeX, it has to be escaped with a backslash. Thus, the firm of Dewey, Cheatham & Howe has to be written in the LaTeX source as Dewey, Cheatham \& Howe.

Over the years, I’ve written several little scripts to format addresses for LaTeX. They’ve all done the job, but they’ve all forced me to either

Neither is the way I want to work.

When writing on the Mac, I use LaunchBar to look up contacts. I tap ⌘-Space, start typing the contact’s name, and up it pops.

Appleseed in LaunchBar

What I want is to be able to run a command at this point that gets the address, puts it in LaTeX form, and pastes it into the letter where the cursor is sitting. This past week, I finally got around to writing a script that does just that.

It uses LaunchBar’s “sending” feature to pass the contact to a script that assembles, formats, and pastes the address—all without leaving LaunchBar. The idea is that after getting the contact as above, I can tap the Tab key to bring up a list of things I can do with it:

Sending contact to LaTeXAddress AppleScript

Choosing the LaTeXAddress script sends the contact to that script, which then inserts the formatted name and address. No switching out of and back into my text editor, and no messing around with the clipboard.

While I was at it, I decided the script should also handle an address sent to it. That would happen if I select the contact’s address instead of the contact itself:

Selecting address of contact

Will I ever use it this way? It adds an extra step, so probably not. But it was fairly easy to add this extra capability, and it’s usually better to finish off a project when it’s fresh in your mind.

Here’s the LateXAddress script:

  1  use AppleScript version "2.4" -- Yosemite (10.10) or later
  2  use scripting additions
  3  
  4  (* This script is to be used within LaunchBar. It is sent either a
  5  contact or the address of a contact. When sent a contact, the argument
  6  of handle_string is the full name of the contact as a single line of
  7  text. When sent an address,the argument of handle_string is the street
  8  address as a multi-line block of text with an attn: line that contains
  9  the name.*)
 10  
 11  -- The US may show up as any of these names in the country field.
 12  property usNames : {"USA", "US", "United States", "United States of America"}
 13  
 14  -- Handle string passed in from LaunchBar
 15  on handle_string(s)
 16    set sList to paragraphs of s
 17    
 18    -- Get the plain name and address as a list of text lines.
 19    -- The method called depends on whether a single-line name
 20    -- or multi-line address (with attn:) is passed in.
 21    if (count of sList) is 1 then
 22      set plainList to addressFromName(s)
 23    else
 24      set plainList to addressFromAddress(sList)
 25    end if
 26    
 27    -- Convert from plain name and address to LaTeX
 28    set blockText to latexify(plainList)
 29    
 30    -- Insert it at the cursor
 31    tell application "LaunchBar" to paste in frontmost application blockText
 32  end handle_string
 33  
 34  
 35  -- Strip the US line part of the address if it's there
 36  on stripUS(aList)
 37    if last item of aList is in usNames then
 38      return items 1 thru -2 of aList
 39    end if
 40  end stripUS
 41  
 42  
 43  -- Convert from list of lines of plain address to text block
 44  -- formatted according to LaTeX rules
 45  on latexify(aList)
 46    set oldDelims to AppleScript's text item delimiters
 47    set AppleScript's text item delimiters to linefeed
 48    
 49    -- Escape the ampersands and end each line except the last with two backslashes
 50    set cmd to "echo " & quoted form of (aList as text) & ¬
 51      "| sed 's/&/\\\\&/g' " & ¬
 52      "| sed '$!s/$/\\\\\\\\/' "
 53    set newBlock to do shell script cmd
 54    return newBlock
 55    
 56    set AppleScript's text item delimiters to oldDelims
 57  end latexify
 58  
 59  
 60  -- Handle single-line name
 61  on addressFromName(fullname)
 62    tell application "Contacts"
 63      --Find the contact with that name
 64      set match to first item of (people whose name is fullname)
 65      
 66      -- Get the address and strip the country if US
 67      set addr to formatted address of first item of (addresses of match)
 68      set addrList to paragraphs of addr
 69      set addrList to my stripUS(addrList)
 70      
 71      -- Add the company to the top of the list of lines
 72      set org to organization of match
 73      if org is not missing value then
 74        set beginning of addrList to org
 75      end if
 76      
 77      -- Add the name to top of the list of lines
 78      set beginning of addrList to fullname
 79      
 80    end tell
 81    
 82    return addrList
 83  end addressFromName
 84  
 85  
 86  -- Handle multi-line address
 87  on addressFromAddress(aList)
 88    -- Strip the country if US
 89    set aList to stripUS(aList)
 90    
 91    -- Loop through and find the addressee via the attn: line
 92    -- Keep track of the attn: line number and save in aLine
 93    set lCount to 1
 94    repeat with theLine in aList
 95      if (word 1 of theLine) is "attn" then
 96        set aLength to length of theLine
 97        set addressee to text 7 thru aLength of theLine
 98        set aLine to lCount
 99      end if
100      set lCount to lCount + 1
101    end repeat
102    
103    -- Build a list with everything except the attn: line
104    set addrList to items 1 thru (aLine - 1) of aList & items (aLine + 1) thru -1 of aList
105    set beginning of addrList to addressee
106    
107    return addrList
108  end addressFromAddress

The handle_string handler, Lines 15–32, is required and is the entry point for the script. Depending on how LaTeXAddress is called, it will get passed either the name of the selected contact—e.g., John Appleseed—or the selected address, which will be a block of text in this form:

Apple, Inc.
attn: John Appleseed
1 Infinite Loop
Cupertino CA 95014
USA

The handle_string function splits its argument into a list of lines (in AppleScript parlance, paragraphs are separated by linefeeds). If there’s only one line, we know that the argument is the full name of the contact, and the addressFromName handler is called. If there’s more than one line, we know the argument is an address block, and the addressFromAddress handler is called. Both of these functions return a list of address lines in the variable plainList, which is then reformatted on Line 28 by the latexify function and then pasted at the cursor position of the current document by the tell application "LaunchBar"… command on Line 31.

The addressFromName function finds the contact from their name (Line 64) and gets the first address listed for that contact (Line 67). It then splits this block of text into lines, stripping off the last line if it’s “USA” or any of the other names in the usNames property defined in Line 12.

The idea here is that the country name is unnecessary for an address in the US. It’s only my Canadian clients that need the country included in the address. The stripping is done by the function stripUS, defined on Lines 36–40.

Lines 72–75 add the company name (if there is one) to the top of the list of lines. Line 78 adds the contact’s name to the top of the list. This list of lines is what addressFromName returns.

The addressFromAddress handler is just a text manipulation function. Line 89 calls stripUS to get rid of the superfluous country name, if present. The rest of the function looks for an attn: line, removes it, and adds the addressee from that line to the top of the list.

The latexify function takes a list of address lines and returns a block of text properly formatted for a LaTeX document. The bulk of the work is done by the pipeline of three sed commands on Lines 51–52. The first one,

sed 's/&/\\\\&/g'

escapes all the ampersands. There are four backslashes in the replacement because this command is processed first by AppleScript, which eats half of the backslashes, and then by the shell, which also eats half of the backslashes. So AppleScript turns four backslashes into two, and then the shell turns two into one. And that’s what we want, one backslash in front of every ampersand. Because there’s no prefix before the s (substitute) command, the command is applied to each line. The g (global) flag at the end of the command tells sed to apply the substitution for every ampersand it finds on a given line.

The second sed command,

sed '$!s/$/\\\\\\\\/'

adds the backslashes required at the ends of the lines. Once again, both AppleScript and the shell consume half of the backslashes, so we need to start with eight to end up with two. The lines to which this substitution is applied are defined by the $! prefix that comes before the s. This is a two-part definition. The $ says to select only the last line, and the ! says to negate (or invert) that selection. The inverse of the last line is every line except the last line, which, if you look at the example up at the top of the post, is exactly what we want.

This system for inserting LaTeX-formatted addresses is more efficient than anything I’ve previously made. It’s possible I can squeeze out another keystroke by building a LaunchBar Action based on this script, but I’m not sure that’s worth the effort. I’ll live with what I have for a while.


  1. This may seem old-fashioned to you, but it’s what my clients prefer.