Some Scriptable Applications

As part of the default Tiger installation, Apple ships a number of applications with varying degrees of useful scriptability . Some of these are in places where you might think to look, but a number of them are not, so here's a quick survey of the scriptable applications you'll find on your computer. (Third-party applications, which are often the most important scriptable applications in a workflow, are not discussed here.)

Of the applications in your /Applications directory, quite a number are usefully scriptable. iTunes is probably the most popular target; it is, perhaps, the only one of the iApps whose powers emerge to their fullest extent only in the presence of AppleScript. Address Book and iCal are essentially databases, and provide scriptable access to their data. Mail is scriptable, though there are many bugs and functionality holes.

Safari , aside from control over its preferences and interface, responds to just one important command, do JavaScript, but this command should not be underestimated, as it opens the door to some powerful capabilities. System Profiler is another one-trick pony: you can retrieve an XML version of its report, from which you can retrieve any desired information. Script Editor is scriptable enough to allow insertion of templated control structures through its contextual menu. If you see the Internet via modem or PPPoE, Internet Connect is a good way to query and manipulate your connection; you can connect and disconnect, and learn whether you are connected. iChat is scriptable enough to let you get buddy list information and send a message. TextEdit's scriptability is substandard. Other applications that are scriptable in a rudimentary way are Automator, DVD Player, Font Book, iPhoto, iSync, and QuickTime Player.

The Finder, located in /System/Library/CoreServices, is the favorite target application for examples in this book—and with good reason. For all its faults, Finder scripting is a solid way to interact with the hierarchy of files and folders on your hard disk. It's very good at such things as renaming files, copying files, deleting files, creating folders and aliases, and describing the folder hierarchy. The Finder is also one scriptable application that is almost certain to be running (so that, for example, you have somewhere to start when targeting a remote machine).

In Mac OS 9 and before, the Finder was the locus of scriptability for a lot of system functionality that had nothing to do with files and folders, such as what applications were running. This was somewhat irrational, as the Finder wasn't involved in such matters; it was being used as a kind of stand-in for the system itself. In Mac OS X, scripting of system functionality of this kind has been moved to a faceless background application called System Events (located in the CoreServices folder, along with the Finder). Here are some of the things you can do with System Events:

Here we examine some features of an audio file:

tell application "Finder" to set f to (get file 1 as string)
tell application "System Events"
    try
        set f to audio file f
    on error
        error "Not an audio file."
    end try
    set k to kind of f
    tell (get contents of f)
        set t to (its duration) / (its time scale)
        set min to t div 60
        set sec to round (t mod 60) rounding as taught in school
    end tell
    return k & ", " & min & ":" & sec -- MP3 Audio File, 2:43
end tell

Here's an illustration of System Events's support for parsing XML. This is very crude: we have to know the schema of the XML beforehand, and all we can do is cycle through elements and attributes looking for a desired value. (We cannot, for example, extract data through an XPath query.) In this example, I cycle down into the System Profiler report to find out what kind of computer I've got:

tell application "System Profiler"
    set s to (XML text of document 1)
end tell
on findElementWithValue(e, v)
    tell application "System Events"
        repeat with i from 1 to (count XML elements of e)
            if value of XML element i of e is v then
                return i
            end if
        end repeat
    end tell
    error "not found"
end findElementWithValue
tell application "System Events"
    set x to make new XML data with data s
    set e to XML element 1 of XML element 1 of x
    set e to first XML element of e where ¬
        value of XML element 2 of it is "SPHardwareDataType"
    set i to my findElementWithValue(e, "_items")
    set e to XML element 1 of XML element (i + 1) of e
    set i to my findElementWithValue(e, "machine_name")
    return value of XML element (i + 1) of e -- iMacG5
end tell

In this particular case the XML in question is actually a property list, and we can do a much more elegant job of retrieving the desired data by treating it as such:

tell application "System Profiler"
    set s to (XML text of document 1)
end tell
tell application "System Events"
    set p to make new property list item with data s
    set p to property list item 1 of p
    set p to property list item "_items" of p
    set p to property list item 1 of p
    return value of property list item "machine_name" of p -- iMacG5
end tell

System Events is also responsible for GUI scripting (see Chapter 24) and for folder actions (see Chapter 27). Plus, it is System Events that actually runs menu items chosen in Apple's global Script Menu (see Chapter 2), which operates by passing the chosen script to System Events as the parameter of the do script command. System Events is thus a very important application, and its dictionary deserves study. Another example of scripting System Events appeared in Chapter 1.

This application, hidden away in the Carbon framework, is the scriptable front end for the system's built-in speech recognition functionality. For an example, see "Noises" in Chapter 21.

This is a background-only application in the ScriptingAdditions folder. When it was invented (back in Mac OS 8.5, I believe), it was a good thing, because it provided a way to download and upload via HTTP and FTP across the Internet without the overhead a full-featured client. However, I find it undependable, and as we're now in the Unix-based world of Mac OS X, I recommend do shell script and curl instead (see Chapter 25).

This is another background-only application in the ScriptingAdditions folder, and acts as a scriptable front end to the user's keychain, where passwords are stored. This is analogous to the functionality accessed through the Keychain Access utility. In order to access a password, however, Keychain Access itself must be launched by a different process from your script, which calls for a certain amount of extra song-and-dance (see http://docs.info.apple.com/article.html?artnum=301858):

set f to (path to application "Keychain Scripting")
tell application "Keychain Scripting" to quit
tell application "Finder" to open f
delay 5 -- that ought to do it!
tell application "Keychain Scripting"
    tell (get current keychain)
        set k to (get first generic key whose name is "mattneub")
        get {account, description, service, password} of k
        -- {"mattneub", "application password", "iTools", "teehee"}
    end tell
end tell

When I run that example, the Confirm Access to Keychain dialog appears twice (once for the Keychain Scripting application and once for the host application in which the script is running), because this is a password whose access control is set to require confirmation. Thus, this script could not run successfully without human intervention, which is a good thing. As a further security measure, you cannot access the access control rules for a key through Keychain Scripting.

This is another background-only application living in the CoreServices folder along with the Finder and System Events. It is a front end to sips (type sips —help in the Terminal for more information), which performs some basic manipulations on image files, such as scaling, rotating, and manipulating color profiles, as well as supplying information about monitors. For example:

tell application "Image Events"
    set f to (path to desktop as string) & "bigImage.tiff"
    set f2 to POSIX path of (path to desktop) & "/smallerImage.tiff"
    set im to open file f
    scale im by factor 0.5
    save im in f2
end tell

Database Events, yet another background-only application located in CoreServices, is for manipulating "databases." Here we create and populate a tiny database:

property fieldnames : {"firstname", "lastname", "book"}
property theData : {{"Matt", "Neuburg", "AppleScript"}, ¬
    {"Charles", "Dickens", "Pickwick"}, ¬
    {"William", "Shakespeare", "Hamlet"}}
tell application "Database Events"
    set d to (make new database with properties {name:"people"})
    tell d
        repeat with i from 1 to (count theData)
            set r to (make new record with properties {name:""})
            repeat with j from 1 to (count fieldnames)
                tell r to make new field with properties ¬
                    {name:item j of fieldnames, value:item j of item i of theData}
            end repeat
        end repeat
        save
    end tell
    delete every database -- actually, just closes the database
end tell

Observe that we do not try to tell Database Events where to save the database; this is because every database, rather annoyingly, is automatically saved in and retrieved from your ~/Documents/Databases folder. Now we'll query the database:

tell application "Database Events"
    tell database "~/Documents/Databases/people.dbev"
        get value of field "book" of ¬
            (every record where value of field "firstname" is "Matt")
         -- "AppleScript"
    end tell
end tell

The syntax illustrated here is extraordinarily clumsy and touchy, even for Apple, and the object model's approach to databases is unhelpful and nonstandard; you cannot execute an SQL command, nor does Database Events understand that a true database consists of tables (so you could not use it to make a relational database, and even the claim that it makes "databases" seems dubious). Furthermore, files created by Database Events cannot be opened by SQLite (the excellent lightweight open-source database engine included with Mac OS X starting in Tiger; see http://www.sqlite.org), which makes it hard to credit Apple's boast that Tiger includes a "Data Events suite" that "lets you create and parse SQLite databases." In sum, Database Events is nowhere near as powerful, coherent, and complete as the sqlite3 command-line tool. So it's best to disregard Database Events as a bad job, and use the command line instead. Here we create and populate the database:

set d to space & "~/Desktop/people.db" & space
set s to "sqlite3" & d & quote
set s to s & "create table people(firstname, lastname, book); "
set s to s & "insert into people values('matt','neuburg','AppleScript'); "
set s to s & "insert into people values('charles','dickens','Pickwick'); "
set s to s & "insert into people values('william', 'shakespeare', 'Hamlet');"
set s to s & quote
do shell script s

And here we query it:

set d to space & "~/Desktop/people.db" & space
set s to "sqlite3 -list" & d & quote
set s to s & "select book from people where firstname = 'matt';"
set s to s & quote
do shell script s -- "AppleScript"