Friday, January 4, 2008

What Role Should the Script Editor Play in Writing AppleScript?

I think it is safe to say that Script Editor is the most widely used application for writing AppleScripts. But, should it be? I'm coming to the conclusion that it brings so many limitations along with it that it can only play a limited role.

Before going any further with this line of thought, I'd like to make clear that I'm not making any recommendation for or against the various commercial AppleScript development environments. Looking at the SEEing LaTeX series, you might get the impression that I use AppleScript a fair amount. I don't. I use it as little as possible, which is why in that series I do most of the real work not in AppleScript, but in shell scripts called from AppleScript. Paying for a commercial environment may make sense if you want to or have to write a lot of AppleScript, but would be a waste of money for me.

However, our choices are not limited to Script Editor and the commercial development environments. We have an excellent development environment that comes for free with Mac OS X: it's called Unix. We can use make to manage a project, of which AppleScript can play a useful part. Unfortunately, Script Editor is tied too strongly to AppleScript, which means we're more or less stuck with AppleScript's mechanisms for modularity. Since modularity goes hand in hand with reuse, this is a key point.

Within AppleScript, the usual approach is to put common functionality in a script library and use load script from the StandardAdditions. This has a number of problems. First, there isn't a standard search path from which to load the script library, so you need to introduce a new convention. Second, if a library is changed, there is no automated mechanism to update scripts that depend on it. You can load the script library, store it in a property, and just check to see if the library has been updated whenever the script is called; this roughly emulates how, e.g., import works in Python. There are enough steps to this process that you're looking at a lot of copy and paste programming. Also, you depend on whatever convention you've established to load the script library.

Consider now using make. You can use osacompile to compile your text source into a compiled script. You could also use various shell methods to manipulate your script to define the search path for the script libraries, but how is the Script Editor to work with that? You can also just forget about reloading the script libraries when they change, since make will take care of that for us. We just load the script library once, and forget about it. Since we're just loading the script library once, why not just forget about the search path while we're at it? Just compile the library source into your working directory, and have the script load it from that same directory.

At this point, we still can use Script Editor to edit the script sources. However, we still have a lot of boilerplate to write for little gain. We need to set up the scripts correctly so that make can run them once to load the library. We need to add extra steps to make in order to load the libraries. We need to refer to the script libraries by using the property in which we stored them, whenever we want to use a handler in the library.

Alternatively, instead of emulating how Python loads modules, let's take a look at how something like C would do things. In C, you just use the preprocessor to include the various files. We can do that for AppleScript using, e.g., m4. Assuming you're working in a script called demo.applescript, and want to use some handlers you've written for manipulating text. Just include the relevant library source with something like
include(`StringTools.applescript')
and compile with
m4 demo.applescript | osacompile -o demo.scpt


Just like in C, this approach includes everything into the namespace of the script. And, just like in C, this could lead to namespace conflicts. So far, I've only used this in a few scripts, all of which are relatively small. I've not encountered any difficulties. But, should trouble arise, there is a solution. Instead of including the text at the top level of the script, include it inside of a script object:
script StringTools
    include(`StringTools.applescript')
end script
This could be further automated by writing an m4 macro, such as:
define(`import', `script `$2'
include(`$1')
end script
')dnl


I'm not at all sure that this approach would scale to larger scripts well. You'd really like to be able to have scripts import what they need, and not worry about whether they will themselves be imported into others. Since I'm not planning to write any large AppleScripts, I'm not going to worry about that until it becomes a problem.

Where does this leave the Script Editor? It's no longer useful for scripts of any length, since it can't handle the include statements. We do want it in order to look at scripting dictionaries, that much is clear, but is it needed for any editing at all? It is a reasonable environment in which to try out little code snippets, but we can even dispose of that, since Script Editor offers that functionality through the services menu.

No comments: