MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


Write Your Own Automator Actions

by Matt Neuburg
05/03/2005

Mac OS X 10.4 (Tiger) introduces Automator, which lets users string together preinstalled script steps (Actions) into a sequence (Workflow) that can be run and saved. The sequence is a rudimentary data flow, or pipe: typically, each step outputs some data, which becomes the input to the next step. The advantage for the user is that there's no need to know a scripting language in order to write and run a custom sequence.

Figure 1. A simple Automator workflow; it writes the pathnames of all files in a given folder into a new TextEdit document Figure 1. A simple Automator workflow; it writes the pathnames of all files in a given folder into a new TextEdit document

Besides the more than 200 Actions installed by default in /System/Library/Automator, we may expect to see many third-party Actions in the near future, which the user will install in /Library/Automator or ~/Library/Automator. You, the developer, can participate in this movement by writing and distributing your own Actions.

This prospect is particularly attractive to AppleScript programmers. (It is also possible to write an Action in Objective-C, but AppleScript is usually more appropriate because typically an Action communicates with some scriptable application.)

An Action is a convenient way to distribute a script. The script is bundled inside the Action and cannot be read. The script is in the form of a handler, accepting a certain type of data and outputting a certain type of data, and Automator enforces your declarations of these data types so the end user can't misuse it. Thus, thanks to Automator's data flow architecture, the end user can easily take advantage of your script's functionality to operate on and generate data as part of a custom sequence without ever coming in direct contact with the code, and with no need to grapple with the mysteries of a scripting language.

Four-Step Program

An Automator Action is extremely easy to write. Documentation on how to do so is copious and helpful; take a look in the ADC Reference Library on your hard disk, under ...documentation/AppleApplications/Conceptual/AutomatorConcepts/Automator.html. Still, all that documentation may seem overwhelming at first, so this article presents a simple hands-on example to get you started down the road to writing your own Actions.

We'll write our Action in AppleScript, but it will be a very simple and silly example and won't do any interapplication communication; it merely takes the text provided as input and returns every nth word, where n is to be provided by the user in Automator's interface. Hey, it's only an example!

There are four steps to writing an Action:

  1. In Interface Builder, draw the interface.

  2. In Interface Builder, bind the interface widgets to the object controller.

  3. In Xcode, write the Action's code.

  4. In Property List Editor, edit the Action's Info.plist file.

You can then run and test the Action within Automator.

Create the Project, Draw the Interface

Start up Xcode 2.0, choose File -> New Project, and select AppleScript Automator Action. The name you give the project will be used as the name of the Action, so make it meaningful; let's call it Every Nth Word.

When the project appears, open main.nib. You're now in Interface Builder. In the main window, you'll see a View instance; open it. This is where you'll design the interface that the user will see for this Action within Automator.

Figure 2. The default Action nib, ready for you to draw the interface Figure 2. The default Action nib, ready for you to draw the interface

Select the existing label in the view ("UI elements go here") and delete it. Now create the new interface within the view. In our case, we just need an NSTextField where the user can enter a number, and a label explaining what to do with it. As a crude way of enforcing the requirement that the user enter a number in the NSTextField, I've made the NSTextField noneditable, and I've added an NSStepper, on which the user must click to change the number in the NSTextField.

Figure 3. Our Action's interface Figure 3. Our Action's interface

(The View is displayed at a minimum width, which you should not change. You may, however, change its height to accommodate more interface or to eliminate empty white space. In real life, you might also want to add "springs and struts" to the interface widgets in case the user widens the Automator window.)

And in the Darkness Bind Them

When our Action runs, we will need to know the user's settings in its interface. The mechanism by which we will acquire this information involves Cocoa bindings. That is what the NSObjectController object called Parameters is doing in the main.nib main window (see Figure 2). For every value in the interface that we want to hear about, it is up to us to give this NSObjectController a key, and to bind the interface widget to that key.

In this case, there is just one value we need to know about--the number selected by the user, using the NSStepper, that appears in the NSTextField. Let's call the key for that number n. Select the Parameters instance in the main.nib window and open the inspector window to the Attributes pane. Click on the Add button, double-click on the NewKey entry, and type n (and hit the Tab key). We have created the n key.

 Figure 4: Creating the n key for the Parameters NSObjectController Figure 4. Creating the n key for the Parameters NSObjectController

Now we must bind our interface to the n key. In this case, we must bind both the NSTextField and the NSStepper to the n key, so that their values stay synchronized. (When the user changes the NSStepper value, the NSTextField should change to reflect the new value.) Select the NSTextField in the View, switch the inspector window to the Bindings pane, and bind its Value to n. Do the same with the NSStepper. Save the nib and quit Interface Builder; we're done with it.

Figure 5. Binding the NSTextField to the n key (binding the NSStepper is similar) Figure 5. Binding the NSTextField to the n key (binding the NSStepper is similar)
AppleScript: The Definitive Guide

Related Reading

AppleScript: The Definitive Guide
By Matt Neuburg

Write the Code

Back in Xcode, open main.applescript. This is the script that will run when our Action runs. It has been filled out with a skeleton handler, like this:

on run {input, parameters}
    return input
end run

The variable input is basically the output from the previous Action in the Workflow, the data on which we are to operate. We do not have to return the same value, and what we return does not have to be a variable named input, but we should return something to function as our Action's output. In this case, we'll be expecting text as our input, and we'll be returning text as our output.

The variable parameters is an AppleScript record containing, among other things, the bound values from the interface. You'll recall that we have just one such bound value, called n; our first task should therefore be to fetch it:


on run {input, parameters}
    set whatN to (get n of parameters)

Then we simply generate a string consisting of every nth word, and return it.

on run {input, parameters}
    set whatN to (get n of parameters)
    set L to (get words of input)
    set output to {}
    repeat with ix from 1 to (count L)
        if ix mod whatN is 0 then
            set end of output to item ix of L
        end if
    end repeat
    set text item delimiters to " "
    return (output as string)
end run

So much for the script. Save the script and close its window.

Edit the Plist

When our Action is loaded into Automator, Automator will fetch a lot of information about it from its Info.plist file. So our next task is to edit the project's Info.plist. You might be tempted to do this by double-clicking on Info.plist in the project window and editing the Info.plist as a text file, but I strongly advise against that, because if you make just one mistake in the XML of this file, your Action won't load and you won't know why. Instead, to ensure that your XML is valid, edit the file with Property List Editor. The simplest way to do this from within Xcode is to Control-click on Info.plist and choose Open With Finder from the contextual menu.

Editing the plist is rather tricky and extremely important. If we get this wrong, our Action will behave oddly or won't appear properly within Automator. Some entries in the plist are required; others are optional. For this example, I recommend deleting the optional entries. Some entries have default values you can simply accept; others must be edited if your Action is to work properly. You can learn more about the meaning of the plist entries from the file ...documentation/AppleApplications/Conceptual/AutomatorConcepts/Articles/AutomatorPropRef.html on your hard disk. For now, just imitate the settings shown in Figure 6.

Figure 6. Editing the plist Figure 6. Editing the plist

Here are comments on some of the settings in the plist:

Once you've edited and saved the Info.plist file, you must also edit the localized strings file. If you don't, your Action will not appear correctly within Automator. (This fact is poorly documented; when my first Action didn't show up properly in Automator, I had quite a hard time tracking down the problem.) In this case, since my default language is English, this file is listed in Xcode under the name InfoPlist.strings (English). You can reduce this file to a minimum, as shown here, but you must not empty it completely:

/* Localized versions of Info.plist keys */

CFBundleName = "Every Nth Word";
NSHumanReadableCopyright = 
"Copyright 2005 __MyCompanyName__.";

AMName = "Every Nth Word";

Testing

Testing your Action is very easy and extremely cool. Simply click on the Build and Run button in Xcode, and presto, Automator starts up--and, if all has gone properly, it contains your Action! When you select your Action, Automator displays the information for it, including its name, its icon, the AMDSummary, and the input and output data types.

Figure 7. The information for our Action, as displayed within Automator Figure 7. The information for our Action, as displayed within Automator

Now create a Workflow that actually uses your Action. When you have dragged your Action into the right side of the Automator window, its interface will appear; check that this looks as you expect, and that you can interact with it properly. Finally, run the Workflow, to see whether your Action behaves correctly.

>Figure 8. Our Action doing its thing in an actual Workflow (with apologies to Sir Arthur Conan Doyle--see The "Gloria Scott")
Figure 8. Our Action doing its thing in an actual Workflow (with apologies to Sir Arthur Conan Doyle--see The "Gloria Scott")

When you're testing your Action within Automator, Automator itself is a subprocess launched by Xcode. This means that if you want to make a change in your code (or any other aspect of your Action), you must quit Automator in order to retest. This may sound very painful, as you will lose the Workflow that you're using as a test sequence and will have to re-create it manually when you test your Action again. However, that's not entirely so. Simply save your Workflow as a file from within Automator before quitting it. Then, when Automator starts up again, choose your Workflow from the File -> Open Recent menu. You will immediately be able to run the very same Workflow--using the revised version of your Action.

For debugging, you can use the standard repertory of devices. The display dialog command works from within Automator. Uncaught errors are reported from within Automator. Values written out with the log command appear in the Xcode console. You cannot, however, set a break point and then use Build and Debug to test your Action. (Well, you can, but the Action won't pause at your break point.)

When your Action is finished, choose Project -> Set Active Build Style -> Deployment, clean the target, and build one last time. You now have an Action file that can be distributed to other users, who can install it in ~/Library/Automator in order to use it the next time they run Automator.

Onward and Upward

Once you've created your first Action, the documentation will make much more sense to you, and you can start exploring it to discover some of the advanced things you can make an Action do. There are two main areas you'll want to explore.

  1. You might want to make your Action's interface more lively and interactive. To do so, you supply a secondary script, with which your interface communicates through event messages of a sort familiar from AppleScript Studio. (You can see these listed in the Interface Builder inspector, in the AppleScript pane, under Automator Action View.) You can even bypass the use of Cocoa bindings and script the linkage between the interface and the values to be handed off to your main script. (Study the Get Process Information example on your hard disk.)

  2. Through settings in the plist, your Action can be made to perform a number of interesting tricks. Particularly intriguing is the use of AMCanShowWhenRun, which in our example we set to No. When this value is set to Yes, your Action's interface in Automator is displayed with an Options disclosure triangle that reveals a Show When Run check box; if the user checks this, then when the Workflow actually runs, it pauses just before it comes to your Action, and presents your Action's interface in a separate window so that the user can interact with it in real time before proceeding.

Also, don't neglect to study the included example Actions. And, as I mentioned earlier, it can be useful to study the Info.plist file inside an existing Action in order to understand how its values affect the Action's behavior.

As you've seen, it's easy for an AppleScript programmer to create an Action for Automator. An Action is a great way to package functionality so that end users can take advantage of it without knowing any AppleScript. Actions should prove to be great way for you to distribute scripts for others to use.

Matt Neuburg is the author of O'Reilly's "AppleScript: The Definitive Guide," "REALbasic: The Definitive Guide," and "Frontier: The Definitive Guide," and is a former editor of MacTech magazine.


Return to MacDevCenter.com.

Copyright © 2009 O'Reilly Media, Inc.