TextExpander snippet prefixes again

Last week I wrote a couple of small scripts for transforming TextExpander snippet abbreviations. Today I added another script to the repository on GitHub. This one, called teprefix, changes the abbreviation prefixes in a TextExpander library file (one with a .textexpander extension).

What’s an abbreviation prefix? Many TextExpander users put a 1-2 character “code” at the beginning of their snippets so they don’t get inadvertent snippet expansion when writing or programming. For example, I always start my snippets with a semicolon because it’s easy to type (right there on the home row) and there are no circumstances in which a semicolon should be immediately followed by an alphanumeric character. By designing my snippets this way, I don’t have to worry if my snippets use actual words. My ;furl snippet—which inserts the frontmost URL in my browser—will never be accidentally expanded when I’m writing about flag etiquette or sailing (it could happen).

It’s nice to be able to exchange .textexpander files with other TE users, but it’s a pain to edit the snippets if the person you get a file from uses a different abbreviation prefix. That’s where teprefix comes in. It reads a .textexpander file with someone else’s prefix and writes out a new .textexpander file with your prefix. So if you use a pair of commas as your prefix, you could take my URLs.textexpander file and make it your own by running

teprefix -o ';' -n ',,' URLs.textexpander

to get a URLs-new.textexpander file.

Here’s the source code:

 1:  #!/usr/bin/python
 2:  # encoding: utf-8
 4:  """
 5:  Read a .textexpander snippet file and generate a new one with a different abbreviation prefix.
 6:  """
 8:  import plistlib
 9:  import sys
10:  import os.path
11:  import getopt
13:  usage = '''Usage: teprefix [options] tefile
15:  Create a new TextExpander file with different abbreviation prefixes.
17:  Options:
18:    -o <old prefix>       The old prefix, i.e., the one that's used in
19:    --old=<old prefix>    the existing TextExpander file. It's best
20:                          to enclose the prefix in single quotes to 
21:                          avoid interpretation by the shell. Default is
22:                          the empty string.
24:    -n <new prefix>       The new prefix, i.e., the one you want to use.
25:    --n=<new prefix>      Again, it's best to enclose the prefix in single
26:                          quotes to avoid interpretation by the shell.
27:                          Default is the empty string.
29:    -h, --help            Show this usage message.
30:  '''
32:  # Get the arguments from the command line.
33:  try:
34:    optlist, args = getopt.getopt(sys.argv[1:], 'o:n:h', ['old=', 'new=', 'help'])
35:  except getopt.GetoptError, err:
36:    print str(err)
37:    print usage
38:    sys.exit(2)
40:  # Process the options.
41:  oldprefix = '';       # default
42:  newprefix = '';       # default
43:  for o, a in optlist:
44:    if o in ('-o', '--old'):
45:      oldprefix = a
46:    elif o in ('-n', '--new'):
47:      newprefix = a
48:    else:
49:      print usage
50:      sys.exit()
52:  # Extract folder and filename.
53:  try:
54:    infile = args[0]
55:    basename, extension = os.path.splitext(infile)
56:  except IndexError:
57:    print "No input filename."
58:    print usage
59:    sys.exit()
61:  # Make sure it's a TextExpander file.
62:  if extension != '.textexpander':
63:      print("%s is not a TextExpander file." % infile)
64:      sys.exit(-1)
66:  # Generate the new filename.
67:  outfile = basename + '-new.textexpander'
69:  # Parse the snippet file
70:  try:
71:      te = plistlib.readPlist(infile)
72:  except IOError:
73:      print("Couldn't open %s to read from." % infile)
74:      sys.exit(-1)
76:  # Go through the snippets, replacing each oldprefix with newprefix.
77:  for i in range(len(te['snippetsTE2'])):
78:      prelength = len(oldprefix)
79:      if te['snippetsTE2'][i]['abbreviation'][0:prelength] == oldprefix:
80:          te['snippetsTE2'][i]['abbreviation'] = newprefix + \
81:          te['snippetsTE2'][i]['abbreviation'][prelength:]
83:  # Write out the new .textexpander file.
84:  try:
85:      plistlib.writePlist(te, outfile)
86:      print "Wrote:", outfile
87:  except IOError:
88:      print "Couldn't write new textexpander file", outfile

The code is, I think, mostly self-explanatory. I took several ideas from urschrei’s improvements to the other scripts in the repository.

One thing you may be wondering about is my choice of getopt as the library for parsing the command line options; Python has two other libraries, optparse and argparse, that are supposed to be improvements on getopt. The problem is that

  1. optparse is deprecated as of Python 2.7; and
  2. argparse isn’t part of the Python 2.6 distribution, which is the distribution that comes with OS X.

Getopt is the only library that’s guaranteed to work now and for the foreseeable future. I’d be happy to switch to argparse, but not until Apple puts 2.6 behind it. I’ve read that the Lion preview comes with 2.7, which is encouraging.

With the three scripts in the tedist repository and Brett Terpstra’s soon-to-be-open-to-the-public TE-Tool webapp, transforming snippets between users should be relatively simple.