appscript

Using NSAppleScript

AppleScripts may be executed within Cocoa processes via the NSAppleScript class or (in 10.8+) in a subprocess via NSUserAppleScriptTask. Both classes serve much the purpose, providing Cocoa apps with the basic ability to load and call/execute user-supplied AppleScripts. The main differences are that NSAppleScript can persist a loaded script's state across multiple calls, while NSUserAppleScriptTask lacks persistence support but executes scripts outside the main process's sandbox so doesn't require entitlements to interact with other apps.

Usage

Cocoa's NSAppleScript class can be used to load existing AppleScript files or compile AppleScript code from scratch. The resulting script object is retained for the lifetime of the NSAppleScript instance, allowing its handlers to be invoked any number of times and values stored in its properties to persist.

In basic usage, -executeAndReturnError: may be used to invoke the script's implicit/explicit run handler without parameters. The return value is an NSAppleEventDescriptor containing the script's return value, if any, or nil if an error occurred. Additional error information may be obtained via the optional error argument.

In order to pass parameters or invoke other handlers, the -executeAppleEvent:error: method should be used. (While it is possible to dynamically generate AppleScript code and execute it, that approach is subject to all the usual dangers of arbitrary code evaluation, including poor performance and security holes.) The handler name may be a dictionary-defined keyword (e.g. open) or a user-defined identifier (e.g. doSomething). Where keywords are used, the Apple event should be constructed using the appropriate four-character codes (OSTypes) for the event class and id, and for any positional arguments. Where user-defined identifiers are used, standard event class and id codes are used, and the handler name and any arguments are passed as standard parameters.

All input values must be packed as NSAppleEventDescriptors. All return values must be unpacked. This is somewhat tedious, but could easily be abstracted away into a separate library or class for repeated use.

As of 10.6, user-defined handler names are case-sensitive. (In 10.5 and earlier, the name was always treated as all-lowercase except when enclosed in pipes.)

Python and Ruby scripts can use NSAppleScript via the PyObjC and RubyCocoa modules respectively. MacRuby can access NSAppleScript directly.

Example

The following Objective-C code compiles a simple AppleScript from source, then calls its addTo handler (where addTo is a user-defined identifier):

#import 
#import 

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSAppleEventDescriptor *params, *event, *resultDesc;
    
        // load AppleScript code
        // (tip: to read directly from .scpt file, use -[NSAppleScript initWithContentsOfURL:error:])
        NSAppleScript *script = [[NSAppleScript alloc] initWithSource: @" on addTo(a, b) \n"
                                                                       @"     return a + b \n"
                                                                       @" end addTo "];
        
        // pack positional parameters
        params = [NSAppleEventDescriptor listDescriptor];
        [params insertDescriptor: [NSAppleEventDescriptor descriptorWithInt32: 2] atIndex: 1];
        [params insertDescriptor: [NSAppleEventDescriptor descriptorWithInt32: 3] atIndex: 2];
        
        // build Apple event to invoke user-defined handler in script
        event = [NSAppleEventDescriptor appleEventWithEventClass: kASAppleScriptSuite
                                                         eventID: kASSubroutineEvent
                                                targetDescriptor: NSAppleEventDescriptor.nullDescriptor
                                                        returnID: kAutoGenerateReturnID
                                                   transactionID: kAnyTransactionID];
        [event setDescriptor: params forKeyword: keyDirectObject];
        [event setDescriptor: [NSAppleEventDescriptor descriptorWithString: @"addTo"]
                  forKeyword: keyASSubroutineName];
        
        // invoke handler
        NSDictionary *errorInfo;
        resultDesc = [script executeAppleEvent: event error: &errorInfo];
        if (!resultDesc) {
            NSLog(@"ERROR: %@", errorInfo);
        } else {
            NSLog(@"RESULT: %i", resultDesc.int32Value); // unpack return value
        }
    
    }
    return 0;
}

Here is the same example implemented in Python, using the PyObjC bridge:

#!/usr/bin/python

import struct
from Cocoa import *

def fourcharcode(s): # convert a four-char code string to an unsigned int
    return struct.unpack('>I', s)[0]

# load AppleScript code
scpt = NSAppleScript.alloc().initWithSource_(''' on addTo(a, b)
                                                     return a + b
                                                 end addTo ''')

# pack positional parameters
params = NSAppleEventDescriptor.listDescriptor()
params.insertDescriptor_atIndex_(NSAppleEventDescriptor.descriptorWithInt32_(2), 1)
params.insertDescriptor_atIndex_(NSAppleEventDescriptor.descriptorWithInt32_(3), 2)

# build Apple event to invoke user-defined handler in script
event = NSAppleEventDescriptor.appleEventWithEventClass_eventID_targetDescriptor_returnID_transactionID_(
        fourcharcode('ascr'), fourcharcode('psbr'), NSAppleEventDescriptor.nullDescriptor(), 0, 0)
event.setDescriptor_forKeyword_(params, fourcharcode('----'))
event.setDescriptor_forKeyword_(NSAppleEventDescriptor.descriptorWithString_('addTo'), fourcharcode('snam'))

# invoke handler
errordesc, errorinfo = scpt.executeAppleEvent_error_(event, None)
if out is None:
    print 'ERROR:', errorinfo
else:
    print errordesc.int32Value() # unpack return value