Dated and well-named web receipts

A while ago, I read this post at Mac OS X Hints on altering the PDF workflow called “Save PDF to Web Receipts Folder” to add a date and time stamp to the saved PDF. Of course, I couldn’t just copy the hint as given, I had to customize it to better fit my needs.

First, let’s remind ourselves of what PDF workflows are. They’re the actions that appear in the standard Print sheet that springs out of the titlebar when you choose the Print… command.

“Save as PDF…” is probably the one you’ve used the most, but the others can be just as valuable.

Out of the box, “Save PDF to Web Receipts Folder” makes a PDF of whatever document you’re looking at and saves it in the Web Receipts folder in your Documents folder. It’s intended, I think, to be used mostly with Safari at the end of an online purchase. Most stores show you a receipt at the end of the process and tell you to print a copy for your records. “Save PDF to Web Receipts Folder” does just that, but without inking up a sheet or two of paper, and files it away in the Web Receipts folder for future reference.

Unfortunately, “Save PDF to Web Receipts Folder” has a flaw, a flaw identified in the Mac OS X Hints article: the name of the PDF file is taken from the title of the web page, which is usually either long and cryptic (“Amazon.com - Order 123-1234567-1234567”) or hopelessly generic (“Thank You!”). By following the hint, you can make the workflow prepend the date to the title, changing it to something a bit more useful (“2009-2-12 Thank you!”) but still probably not what you’d really like.

With my changes, you get the prepended date stamp--which makes ASCII ordering the same as chronological ordering--and a title that means something to you because you write it yourself on the spot. I call this workflow “Save Dated PDF to Web Receipts Folder.” When you invoke it, it pops up a window that looks like this

The editable title field starts out with the title of the web page. You can leave it as is, edit it, or retype it entirely. Whatever you choose, clicking OK will create a new PDF in the Web Receipts folder with that title prepended by today’s date. An example title for today might be “20090213-Amazon-harddisk.pdf.” (If you prefer dashes between the year, month, and day, it’s easy to modify the program to put them in.)

If you click the Cancel button or the red close button, the workflow is stopped and no PDF appears in your Web Receipts folder.

I call this workflow “Save Dated PDF to Web Receipts Folder.” Here’s how to implement it.

Step 1. Install Pashua application and library

Pashua is an application and a set of libraries written by Carsten Blüm that provide easy GUI features to shell, Perl, and Python scripts1. As we’ll see later, the guts of the PDF workflow is written in Python, so we’ll use the Pashua Python library to give the workflow the input window shown above.

Download the Pashua dmg. Put the Pashua application in the Applications folder and the Pashua.py Python library in /Library/Python/2.5/site-packages, so Python will find it. I recommend one change to Pashua.py: Down near the bottom is a loop that looks like this:

for Line in Result:
    print Line
    Parm, Value = Line.split('=')
    ResultDict[Parm] = Value.rstrip()

I think a print command in a library utility that is supposed to read lines is just plain wrong, so I’ve commented out the print Line, and I suggest you do the same.

If you’re a Perl programmer, you’ll probably want to install the Pashua.pm module, too, but it won’t be used in this project.

Step 2. Copy the original PDF workflow

“Save PDF to Web Receipts Folder” is kept in the system-wide /Library/PDF Services/ folder. Instead of altering the original--which can screw you up if you make a mistake in the alteration--make a copy of it, put it in your ~/Library/PDF Services/ folder (you may have to make that folder). Rename it “Save Dated PDF to Web Receipts Folder.”

Step 3. Change the Info plist

Control-click on the new “Save Dated PDF to Web Receipts Folder” icon and choose Show Package Contents from the popup menu. You’ll open a new window containing a folder named “Contents.” Open that folder to see all the files that make up the PDF workflow. We’ll be changing a few of these files.

“Info” is a plist (property list) file, an XML file with a nested set of information about the workflow. Since this is a copy, it contains information about the original workflow, so let’s edit it to reflect the fact that it’s different. There are two ways to do this:

If you have the Developer Tools installed, you can open it in the Property List Editor, and you’ll get a spreadsheet-like view of the data:

If you just open it in a text editor, you’ll see the underlying XML in all its glory:

 1:  <?xml version="1.0" encoding="UTF-8"?>
 2:  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 3:  <plist version="1.0">
 4:  <dict>
 5:    <key>CFBundleDevelopmentRegion</key>
 6:    <string>English</string>
 7:    <key>CFBundleIdentifier</key>
 8:    <string>com.apple.pdfworkflow.savedatedpdftowebreceipts</string>
 9:    <key>CFBundleInfoDictionaryVersion</key>
10:    <string>6.0</string>
11:    <key>CFBundleName</key>
12:    <string>Save Dated PDF to Web Receipts Folder</string>
13:    <key>CFBundlePackageType</key>
14:    <string>BNDL</string>
15:    <key>CFBundleShortVersionString</key>
16:    <string>5.5.3</string>
17:    <key>CFBundleSignature</key>
18:    <string>????</string>
19:    <key>CFBundleVersion</key>
20:    <string>237.5</string>
21:  </dict>
22:  </plist>

Either way, you want to change the Bundle identifier to “com.apple.pdfworkflow.savedatedpdftowebreceipts” and the Bundle name to “Save Dated PDF to Web Receipts Folder.” Which is what I’ve already done in the screenshot and on Lines 8 and 12 above.

I don’t think this step is strictly necessary, and that the PDF workflow will run just fine without these changes; but it makes me feel like a real Mac programmer to mess with a plist.

Step 4. Edit the localized strings

I’m pretty sure I didn’t have to do this on one of my computers, but I did on the other, so I’ll include it here. Go into the “Resources” folder and the “English.lproj” folder inside it. Open the “InfoPlist.strings” file in your text editor and change it to look like this:

    /* Localized versions of Info.plist keys */

    CFBundleName = "Save Dated PDF to Web Receipts Folder";
    NSHumanReadableCopyright = "Copyright (c) 2006, Apple Computer, Inc.";

Basically, you’re just adding the word “Dated” to the CFBundleName string.

Obviously, if your Mac is localized to a language other than English, you’ll make an analogous change to the strings file in the folder for your language.

Step 5. Edit the tool

The “tool” file has an imposing icon--it looks like all the Unix binary utilities stored in /bin: cp, mv, df, and the like. But it’s just a text file containing a Python script for naming the PDF and saving it to the Web Receipts folder. Open it up in any plain text editor and change it to this:

     1:  #!/usr/bin/python
     2:  #
     3:  # webreceipts job-id user title copies options pdf-file 
     4:  #
     5:  
     6:  import Pashua
     7:  import os
     8:  import shutil
     9:  import sys
    10:  from time import strftime
    11:  
    12:  def safeFilename(filename):
    13:      filename = filename.lstrip('.')
    14:      filename = filename.replace(os.path.sep, '-')
    15:      if len(filename) == 0:
    16:          filename = "Untitled"
    17:      elif len(filename) > 245:
    18:          filename = filename[0:245]
    19:      return filename
    20:  
    21:  def getTitle(title):
    22:      # Dialog box configuration
    23:      conf = '''
    24:      # Window properties
    25:      *.title = Dated Web Receipt
    26:  
    27:      # File name text field properties
    28:      fn.type = textfield
    29:      fn.default = %s
    30:      fn.width = 250
    31:      fn.x = 75
    32:      fn.y = 40
    33:      fnl.type = text
    34:      fnl.default = Receipt for:
    35:      fnl.x = 0
    36:      fnl.y = 42
    37:  
    38:      # Default button
    39:      db.type = defaultbutton
    40:      db.label = Save
    41:  
    42:      # Cancel button
    43:      cb.type = cancelbutton
    44:      ''' % title
    45:  
    46:      # Open the dialog box and get the input.
    47:      dialog = Pashua.run(conf)
    48:      if dialog['cb'] == '1':
    49:          sys.exit()
    50:      else:
    51:          return dialog['fn']
    52:  
    53:  
    54:  def main(argv):
    55:  
    56:   (title, options, pdfFile) = argv[0:3]
    57:   title = getTitle(safeFilename(title))
    58:   title = "%s-%s" % (strftime("%Y%m%d"), title)
    59:   destDirectory = os.path.expanduser("~/Documents/Web Receipts/")
    60:  
    61:   # Create the Web Receipts folder if necessary.
    62:   try:
    63:     os.makedirs(destDirectory)
    64:   except:
    65:     pass
    66:     
    67:   # Build a file path pointing into the web receipts folder
    68:   # using the document's title and a PDF extension.
    69:   title = safeFilename(title) 
    70:   destFile = title + ".pdf"
    71:   destPath = os.path.join(destDirectory, destFile)
    72:   
    73:   # If the filename we want is already in use then start
    74:   # appending numbers until we get a unique name.
    75:   i = 2
    76:   while (os.path.exists(destPath)):
    77:     destFile = title + "." + str(i) +".pdf"
    78:     destPath = os.path.join(destDirectory, destFile)
    79:     i = i + 1
    80:  
    81:   # Move the file if possible otherwise copy it.
    82:   shutil.move(pdfFile, destPath)
    83:  
    84:      
    85:  if __name__ == "__main__":
    86:      main(sys.argv[1:])

The changes from the original are:

The getTitle function defines and handles the window that we saw earlier. It returns whatever text is in the editable field when the user clicks the OK button. (If the user clicks the Cancel button instead of OK, the “tool” script quits and the PDF never gets saved to Web Receipts. See Lines 48-49.)

getTitle is called on Line 57 of the main function. With the receipt’s base title now chosen, Line 58 prepends today’s date in a compact, eight-character form and a hyphen. This is the line you’ll want to change if you’d like a different format for the date. For example, changing it to

58:   title = "%s-%s" % (strftime("%Y-%m-%d"), title)

will put hyphens between the three parts of the date as well as between the date and the descriptive text. I strongly suggest that you don’t put colons (:) or slashes (/) in your file name; these are the traditional directory separators for the Mac and Unix and I foresee nothing but trouble if you try to put them in a file name. Backslashes (\) are a bad idea, too.

Step 6. Enjoy your new workflow

It should keep your web receipts nicely organized.

Update
Spelling error (“except after c, except after c”) in the source code corrected, thanks to Matt McVickar.

Tags:


  1. I used Pashua for the GUI in my screenshot utility