Friday, April 10, 2009

Experimenting with Implementing Automator Actions

I've recently experimented with implementing actions for Automator using AppleScript. Overall, it was an interesting experience, and one I'd definitely recommend trying yourself.

Creating an action is generally quite simple, consisting of three main steps:

  1. Laying out controls in Interface Builder and establishing bindings to an NSObjectController,

  2. Defining the properties of the action by filling out some values in plists,

  3. Writing an AppleScript to actually implement the behavior.


I found Apple's documentation and an article by Matt Neuburg from the now-defunct O'Reilly Mac developer center. The latter article is a nice tutorial, but a little dated in spots; some caution is warranted.

As a specific example, I decided to take some of the AppleScript handlers for SubEthaEdit I already had on hand and convert them into actions. After reviewing the possibilities, I decided that there were just two actions that looked sensible: getting text from the front SubEthaEdit document and setting text in the front SubEthaEdit document. These are quite similar to existing actions for TextEdit. Additionally, I decided to expand them a little bit to also allow getting or setting the selection or selected lines in the document, not just the full contents of the document.

The first step was pretty simple. With well-defined, compact actions like the ones I considered, the interface requires little thought and little effort. In my case, it was just a matter of populating the menu entries for a pop-up button and establishing a binding.

To my surprise, I spent the most time and had the most difficulty with the second step. I'd expected this to be easy, but found that there were quite a few details that weren't made especially clear in the description of the keys for the property lists. The analogous actions for TextEdit proved very helpful, here: in several cases, I determined the correct choices by examining the plists from the bundles for the TextEdit actions. I'd expect this step to get easier with practice.

Actually implementing the behavior was especially easy, in this case. As I already had working handlers, I was able to just focus on connecting the action to the handlers. The structure for this is quite pleasant: a record of parameters is injected into the run handler, containing values obtained from the bindings to the NSObjectController. The resulting structure for my actions was then just using if-then statements to interpret the parameters, calling out to different handlers.

Although implementation of the behavior was easy, I did find testing to be quite tedious. It was helpful to save a workflow for testing, as suggested by Neuburg. More importantly than that, I'd strongly recommend that you have working handlers before trying to make them part of your action—test out the handlers in, e.g., Script Editor, and test that your Automator action dispatches to the correct handler.

The actions I created are available, as are the sources.