AppleScript’s run script command is an extremely useful tool that allows the user to programatically run an AppleScript script in the form of a text string, text file, compiled script file, compiled script bundle, or application bundle. A potential downside of the run script command is the execution overhead time it incurs, typically between a couple of hundredths of a second in the best of cases to several tenths of a second in the worst. Although this time cost may not be important in many circumstances, it can become a prohibitive obstacle to the command’s use, for example, in a large repeat loop.
The following handler, which utilizes Cocoa’s NSAppleScript class to execute the AppleScript script, reproduces the behavior of the run script command but runs 10 to 15 times faster in my timing tests. Typical run times for very simple scripts have ranged from 1 to 2 thousandths of a second, which appears to be its execution overhead time. The handler accepts forms of script input similar to that of run script: a text string, or a reference to a text file, compiled script file, compiled script bundle, or application bundle. The reference to a script file or bundle may be in the form of a POSIX path, HFS path, or AppleScript alias. Error checking is performed at various steps, and an error is thrown to the calling program if one occurs. The result of script execution is returned to the calling program as a native AppleScript value by coercing the NSAppleEventDescriptor object returned by the NSAppleScript object execution.
Here are examples of handler usage:
-- Example of a text string script returning native AppleScript values
set scriptAsText to "return {1, 2.2, \"three\", true, current date, path to library folder from local domain, {1, 2, 3}, {aa:1, bb:2, cc:3}}"
return runScript(scriptAsText) --> the AppleScript record {1, 2.2, "three", true, date "Friday, November 1, 2019 at 3:35:36 PM", alias "Macintosh HD:Library:", {1, 2, 3}, {aa:1, bb:2, cc:3}}
-- Example of a script saved as a text file returning native AppleScript values
set scriptPath to ((path to desktop as text) & "SampleScript.applescript")'s POSIX path
do shell script "echo " & scriptAsText's quoted form & " >" & scriptPath's quoted form
return runScript(scriptPath) --> the AppleScript record {1, 2.2, "three", true, date "Friday, November 1, 2019 at 3:35:36 PM", alias "Macintosh HD:Library:", {1, 2, 3}, {aa:1, bb:2, cc:3}}
-- Example of a text string script returning a record of application object values
set scriptAsText to "tell application \"Finder\" to return (get properties of file 1 of desktop)"
return runScript(scriptAsText) --> the Finder record {name:..., index:..., displayed name:..., name extension:... }
Here is the handler:
use framework "Foundation"
use scripting additions
on runScript(theScript)
-- The handler input argument is a reference to an AppleScript script in any of the following forms: text string, text file, compiled script file, compiled script bundle, or application bundle
-- The handler return value is the AppleScript value returned by execution of the input script
script util
on processError(errorDict)
set errorNumber to missing value
try
set errorNumber to (errorDict's NSAppleScriptErrorNumber) as integer
end try
if errorNumber ≠ missing value then
set errorMessage to "missing value"
try
set errorMessage to (errorDict's NSAppleScriptErrorMessage) as text
end try
if errorMessage = "missing value" then set errorMessage to "[No error message]"
error ("Problem with handler runScript:" & return & return & errorMessage) number errorNumber
end if
end processError
end script
-- Test if the input argument is a file reference
set scriptAlias to missing value
try
set scriptAlias to theScript as alias
on error
try
set scriptAlias to theScript as POSIX file as alias
end try
end try
-- Create an NSAppleScript object from the input argument
if scriptAlias ≠ missing value then
-- If the input argument is a file reference, try to create an NSAppleScript object from the file, or throw an error if one occurs
set scriptURL to current application's |NSURL|'s fileURLWithPath:(scriptAlias's POSIX path)
set {scriptObj, errorDict} to current application's NSAppleScript's alloc()'s initWithContentsOfURL:(scriptURL) |error|:(reference)
if errorDict ≠ missing value then util's processError(errorDict as record)
else
-- If the input argument is not a file reference, try to create an NSAppleScript object directly from its text content, or throw an error if one occurs
set scriptObj to current application's NSAppleScript's alloc()'s initWithSource:(theScript)
if scriptObj = missing value then util's processError({NSAppleScriptErrorMessage:"The input argument neither is a reference to an existing file nor can it be formed into an executable AppleScript object.", NSAppleScriptErrorNumber:-2700})
end if
-- If an NSAppleScript object was successfully created, try to execute the script, or throw an error if one occurs
set {returnValue, errorDict} to (scriptObj's executeAndReturnError:(reference)) as list
if errorDict ≠ missing value then util's processError(errorDict as record)
-- If the script executed successfully, coerce the returned NSAppleEventDescriptor object into its equivalent AppleScript value, and return the AppleScript value to the calling program
tell returnValue's descriptorType() to set {isList, isRecord, isNothing} to {it = 1.818850164E+9, it = 1.919247215E+9, it = 1.853189228E+9}
if isList then
return returnValue as list
else if isRecord then
return returnValue as record
else if isNothing then
return
else
return (returnValue as list)'s first item
end if
end runScript
Note: To those who know more about NSAppleEventDescriptor-to-Applescript value coercion than I, may I ask if there is a better way to test for descriptor type than the numerical testing I’m using? I tried but failed to test against descriptor type enums, for instance typeAEList for a list, which for some reason weren’t recognized during compilation. More broadly speaking, is there a better way to perform the coercion?