You’re looking at a draft of a chapter from a work in progress, tentatively titled Scripting Mac Applications With Ruby: An AppleScript Alternative, by Matt Neuburg.
Covers rb-appscript 0.6.1. Last revised Sep 06, 2012. All content ©2012 by the author, all rights reserved.
Introduction
Back in 1993, Apple Computer introduced a truly clever innovation: a way for ordinary users to write little programs (scripts) that would tell applications what to do. Tasks that would be repetitive, boring, calculation-intensive, error-prone, or virtually impossible if performed by hand suddenly became available through a single quick and accurate step — namely, running a script.
For example, suppose we want to create a text file listing the names of all the files on the Desktop. It would be tedious to copy (or type!) the name of each file, one at a time. Instead, we just run the following script and presto, there’s our file.
tell application "Finder"
set L to get name of every file
set f to choose file name default name "filelist.txt" default location (desktop as alias)
open for access f with write permission
set eof f to 0
repeat with aName in L
write aName & return to f
end repeat
close access f
end tell
Or suppose we want to assign all the tracks in an iTunes playlist to a single album and number the tracks incrementally. Track numbering is important; yet there is no Number Tracks command built into iTunes. It would be tedious (and error-prone, and boring) to fill in the track numbers one at a time. Instead, we just run the following script and presto, our tracks are numbered:
tell application "iTunes"
display dialog "Album Name:" default answer "My Album"
set albumName to text returned of result
tell view of browser window 1
set total to count file tracks
repeat with i from 1 to total
tell file track i
set album to albumName
set track count to total
set track number to i
end tell
end repeat
end tell
end tell
All of this is possible because the Finder and iTunes, like many other Mac applications, are scriptable. Not every Mac application is scriptable; but many are, and when an application is scriptable, it can be harnessed programmatically, induced to perform actions automatically through the running of a script.
Scriptability is a wonderful and powerful thing. It makes life better for you, the user. The messaging mechanism that underlies this type of scriptability, Apple events, is quite clever, allowing a single message packet to express such relatively complex concepts as name of every file
or album of file track 1 of view of browser window 1
. But the scripting language used to write these example scripts, AppleScript, has been a source of confusion, frustration and annoyance ever since it was invented.
This is not the place for a full discussion of all that I think is wrong with AppleScript. For that, you can consult my book AppleScript: The Definitive Guide. Yes, I wrote a huge and detailed book about AppleScript even though I dislike AppleScript! In fact, my dislike of AppleScript was the reason I wrote that book; I wanted to understand and explain precisely the many linguistic behaviors that I found so confusing, frustrating, and annoying. Here’s a brief list of some of AppleScript’s primary shortcomings:
A script file must be compiled in order to be executed or saved and must be decompiled in order to be opened or read. Decompilation requires the presence of all the resources that were present at compile time, so a script written on one machine often cannot be opened or read on another. Furthermore, compilation is to byte-code, and decompilation is from byte-code, but decompilation from the byte-code tokens might not match the original code that was compiled; in other words, the very act of compilation can alter a script.
Script files can come in various formats, but not every application that runs scripts can deal with all of them. Also, compiled script files can contain persistent data that can change between runs. This data is invisible and difficult to learn about; it can alter the behavior of a script, depending on what application runs it, and can even make a script impossible to save. Libraries (scripts incorporated into other scripts at runtime) are difficult to manage.
The dictionary mechanism, along with the injection of script addition terminology, causes the global namespace to become polluted. Terminology conflicts are common. Attempts to use a certain word as a variable name can mysteriously fail, because that name has been invisibly injected into the global namespace.
The rules for scoping of variables and parameter passing are obscure, complicated, and prone to cause user error. The rules for how messages are passed to (pseudo-) objects differ depending on whether you’re getting / setting a variable (property) or calling a handler. Determining what application or other object is being targeted can be tricky.
AppleScript modifies certain expressions behind the scenes, such as turning a reference into the direct parameter of an implicitly supplied get
command, or dereferencing the reference, in ways that seem surprising and inconsistent.
The AppleScript equivalent of an array, a list, is not an array. Access becomes slow as a list becomes large, and many fundamental array operations are not implemented. Furthermore, resource limits, such as stack depth, are very readily encountered; so, although AppleScript appears to encourage a LISP-like recursive treatment of lists, such recursion is not really practicable. Similarly, the AppleScript equivalent of a hash, a record, is not a hash. Specification of arbitrary key names based on a variable value is not implemented, and introspection is completely absent.
String handling is crude; many fundamental string operations are not implemented and there are no regular expressions. File IO is primitive.
There is an irony associated with many of these problems, in that they arise from good intentions. The idea, when AppleScript was being designed, seems to have been to make life easier for the novice AppleScript programmer. But in the end, I believe, AppleScript has emerged a quirky, troublesome language, with (in my view) a quality of being unfinished, “sent into this breathing world scarce half made up.”
All of these problems, and more, are solved through the approach taken in this book — the use of Ruby, along with Hamish Sanderson’s rb-appscript library, instead of AppleScript.
Ruby is an elegant, powerful language, created with an eye to the programmer’s convenience. It comes with superb handling of numbers, strings, arrays, hashes, and files, and is backed by numerous built-in and third-party libraries for performing tasks common and uncommon, such as parsing and building XML, communicating over a network, transforming image files, and talking to databases. A Ruby script is “just text”, so it can always be read and edited. With rb-appscript the difference between the mere formation of a reference and the actual sending of a communication to a scriptable application is crystal clear, there is never a doubt as to what scriptable application (or scripting addition) you’re talking to, namespaces remain clear and separate, and there is no such thing as a terminology conflict.
Once upon a time, my favorite way of scripting Macintosh applications was through a different alternative to AppleScript, namely UserTalk, the programming language of UserLand Frontier, about which I also wrote a book (http://sbc.apeth.com/frontierDef/ch00.html). But that was a long time ago; since then, Frontier’s abilities to send and receive Apple events have not kept pace with developments in the Mac OS X world, such as the advent of Intel-based Macintosh hardware, and various recent changes in the AppleScript language.
With the emergence of rb-appscript as a way of sending Apple events (which is remarkably similar, linguistically, to UserTalk’s mode of expression), my happiness is complete. I started using rb-appscript not long after the second edition of my AppleScript book was published, and became so excited that I wrote an article about it (http://www.oreillynet.com/pub/a/mac/2007/02/27/replacing-applescript-with-ruby.html). The decisive moment came when I was asked by the publishers of the Take Control ebooks to write some scripts automating parts of their publishing workflow, which used Microsoft Word. Things that were difficult to express linguistically or algorithmically in AppleScript suddenly became easy when I switched to Ruby and rb-appscript, and maintenance and development, which had been a nightmare, became a breeze. Since then, I’ve used rb-appscript for all my Macintosh application scripting; I virtually never use AppleScript any more.
Not only does rb-appscript let you communicate with Macintosh applications as an alternative to AppleScript, but also the fact that you’re using Ruby means you don’t need to communicate with Macintosh applications as much. AppleScript has few native abilities, so you generally need to be targeting some scriptable application in order to get anything done; but Ruby is extremely capable on its own. As a simple example, there is much less need to target the Finder with rb-appscript than with AppleScript, because Ruby already has excellent file-handling capabilities. Similarly, to perform image file transformations, you probably wouldn’t bother launching GraphicConverter or Photoshop and figuring out how to script them; you might instead access ImageMagick through the RMagick library.
Another advantage of Ruby over AppleScript is its general acceptance in the wider world. AppleScript is a specialized, Mac-only language, with a flavor of not being a “real” scripting language. Use of AppleScript locks you into a dependency on Apple’s whims; AppleScript might be incapable of something you need, it might be buggy, it might change its behavior from one system version to the next in incompatible ways. Indeed, the nature and extent of Apple’s own future support for AppleScript is an open question; for example, Apple’s own AppleScript Studio appears to have received no internal support in many years, Apple has introduced its own “bridges”, similar to rb-appscript, as alternatives to AppleScript (see the Appendix), and promised improvements to the AppleScript language have never materialized. Ruby, on the other hand, is a mature, cross-platform, thoroughly modern scripting language; it is open source, and different versions can be installed on the same machine. Ruby programmers are in wide demand, including in the spheres of enterprise and Web applications (in large part thanks to the example set by Ruby on Rails, a widely used Web application framework). Thus, many companies already have an extensive Ruby-based workflow, and rb-appscript can be effortlessly slotted in.
Ultimately, though, I simply find Ruby with rb-appscript, quite apart from the immeasurably greater power of Ruby, to be linguistically easier, clearer, cleaner, and more reliable than AppleScript. That’s the main reason why this book is so much shorter than my AppleScript book! Much of the AppleScript book was consumed with discussion of the quirks and pitfalls of AppleScript as a language; with Ruby and rb-appscript, there’s none of that, so there’s much less to say.
For whom is this book written? I would imagine readers to fall roughly into three categories: refugees from AppleScript (like myself), either deliberately fleeing its quirks and inconveniences or tentatively seeking some plausible alternative; experienced Ruby programmers who want to incorporate Macintosh application scriptability into their toolbox; and programming neophytes with little or no prior experience of scripting Macintosh applications, who just want to get started. This book will probably be found most useful for those in the first two categories, but I hope it will prove helpful for the third as well.
I have tried to avoid using this book as a polemical soapbox for comparisons, invidious or otherwise. This Introduction does contain a little AppleScript-bashing, by way of justifying and explaining the book as a whole; but I promise that the rest of the book does not. It is true that I am enthusiastic about my subject, but it is not my intention to attempt to persuade AppleScript users to switch to rb-appscript, even though I think they might be much happier if they did. Habit is a great comfort, and straying beyond one’s comfort zone is uncomfortable; to some people, the English-like verbosity of a line of AppleScript code like this:
tell application "Finder" to set all_names to the name of every file
seem more congenial than the more mathematically dry and compact equivalent rb-appscript expression:
all_names = Appscript.app("Finder").files.name.get
And this apparent congeniality, together with the investment of years already spent in learning to navigate AppleScript’s treacherous shoals, will be enough for many long-time AppleScript users to continue piloting their familiar waters. Nevertheless, I am convinced that the future cannot be like the past, and that for a user wishing to communicate with scriptable applications on Mac OS X, there needs to be a way forward from AppleScript. Apple might surprise us all by modernizing AppleScript and endowing it with the proper powers and abilities of a full-fledged scripting language; but until then, rb-appscript shows us what such a way can look like. I find it a pleasure to use and I hope you will too.
Here’s an outline of the structure of this book:
You’re looking at a draft of a chapter from a work in progress, tentatively titled Scripting Mac Applications With Ruby: An AppleScript Alternative, by Matt Neuburg.
Covers rb-appscript 0.6.1. Last revised Sep 06, 2012. All content ©2012 by the author, all rights reserved.
This book took time and effort to write, and no traditional publisher would accept it. If it has been useful to you, please consider a small donation to my PayPal account (matt at tidbits dot com). Thanks!