ScriptingBridge
Originally published on macresearch.org, around 2007. Reproduced from the author's archive; some links may no longer resolve.
Scripting Apps Without AppleScript
Author: Drew McCormack
Web Site: www.maccoremac.com
I’ve already written (link no longer available) here about a great new scripting technology in Leopard called BridgeSupport (link no longer available), which allows you to write first class Mac OS X apps in languages other than Objective-C. This language-independence is also a theme of another great scripting advancement in Leopard: the Scripting Bridge. Where BridgeSupport allows you to program the core APIs of Mac OS X in a variety of languages, the Scripting Bridge allows you to script applications in languages other than AppleScript.
I’m not an AppleScript expert, but I have worked with it enough to know it is not for me, and I think there are many others in the same boat — you either love the English-like verbosity, or you hate it. In my experience, non-programmers tend to like it, and programmers used to languages like C and Fortran tend to dislike it. And while there were third-party application scripting options available before Leopard for those of us that didn’t warm to AppleScript, if you wanted an Apple-sanctioned solution, it was the AppleScript-way or the highway.
The Scripting Bridge has changed all of that. Now you can access the same functionality as AppleScript from Objective-C, Python, and Ruby straight out of the box. In this short tutorial, I’m going to show you how you can script the application ChemDraw using Python.
The Scripting Definition
I’m going to take an AppleScript written by fellow MacResearcher Chris Swain, and convert it into Python, to give you a feel for how the Scripting Bridge works. Chris’ script (link no longer available) adds chemical metadata to a PNG file. Here is the AppleScript version
set theFile to (choose file with prompt "Select the PNG file to add metadata:") as alias
set the_posix_file to POSIX path of theFile
tell application "CS ChemBioDraw Ultra"
if not (enabled of menu item "copy") then
do menu item "Select All" of menu "Edit"
set the_SMILES to SMILES of selection
else
set the_SMILES to SMILES of selection
end if
end tell
--use openbabel to get canonical SMILES
set the_ob_script to "echo '" & the_SMILES & "' | /usr/local/bin/babel -ismi -osmi"
try
set ob_smiles to (do shell script the_ob_script)
end try
--display dialog ob_smiles
set theScript to "exiftool -SMILES=\"" & ob_smiles & "\" " & the_posix_file
do shell script theScript
This script uses the CS ChemBioDraw Ultra (link no longer available) application to get the SMILES string of a file, processes the string with OpenBabel (link no longer available), and then uses a command line tool called exiftool (link no longer available) to insert the SMILES string into a PNG file.
The first problem you face when writing a script for the Scripting Bridge is simply knowing what objects and methods are available to call. Luckily, there are command line tools to help you generate the interfaces you can work with. We can use sdef and sdp to produce a C header file like this:
sdef "/Applications/CS ChemOffice 2008/CS ChemDraw/CS ChemBioDraw Ultra.app" | \
sdp -fh --basename ChemDraw --bundleid com.cambridgesoft.ChemDraw
The first part of the command, which uses sdef, writes out the scripting definition for the CS ChemBioDraw Ultra application. The output is piped to the sdp command, which produces the header file ChemDraw.h. This file documents the interface you have to work with, and you can download it here (link no longer available).
Scripting in Python
With a scripting definition available, you can proceed to write the script. Generally, you start with a root application object, and use that as the starting point to access other objects, which represent documents, menus, etc. Here is the Python script that I came up with to carry out the same operations as the AppleScript above:
#!/usr/bin/env python
import sys
from Foundation import *
from ScriptingBridge import *
from subprocess import *
chemDraw = SBApplication.applicationWithBundleIdentifier_("com.cambridgesoft.ChemDraw")
# If no selection, select all
if not chemDraw.menuItems().objectWithName_("copy").enabled():
chemDraw.menus().objectWithName_("Edit").menuItems().objectWithName_("Select All").do()
# Get SMILES
theSmiles = chemDraw.selection().SMILES()
# Use openbabel to get canonical SMILES
cmd = "echo '%s' | /usr/local/bin/babel -ismi -osmi" % (theSmiles)
subproc = Popen(cmd, shell=True, stdout=PIPE)
openbabelOutput = subproc.stdout.read()
# Add metadata to PNG
filepath = sys.argv[1]
cmd = "exiftool -SMILES='%s' '%s'" % (openbabelOutput, filepath)
call(cmd, shell=True)
Whether or not you find this preferable to the AppleScript will largely depend on your taste, but it does demonstrate how easy it is to access the scripting functionality from a language other than AppleScript. All you need to do is import the ScriptingBridge module, and the standard Cocoa Foundation module. The rest is standard Python.
To access an application object, you use the SBApplication class, and an initializer like applicationWithBundleIdentifier_. Once you have the application object, you can delve down into the object graph to retrieve other objects in the application, such as menus and menu items.
You run this script — unlike the original AppleScript — from the command line. Simply pass a PNG file as argument, and make sure the right molecule is open in ChemDraw. For more information, see Chris’ original tutorial (link no longer available).
Where to Now?
That’s a lightening look at the Scripting Bridge. Using the sdef and sdp tools as shown above, you should be able to figure out how to access just about any scriptable application. For more information on the Scripting Bridge, and more examples, there is an article at Apple (link no longer available) to get you started, as well as documentation (link no longer available) for the classes that make up the bridge.