Retrieving Handler Names From Script File

Experimenting with the OSA APIs—old and new—I wondered if I would be able to do something like this:

use framework "Carbon"
use framework "OSAKit"

property this : a reference to the current application
property _0 : a reference to missing value
property _1 : a reference to reference

property OSAScript : a reference to OSAScript of this

set furl to POSIX file "/path/to/textfile.applescript" as alias

set [scpt, E] to OSAScript's scriptWithContentsOfURL:furl |error|:_1
if E ≠ missing value then return E's localizedDescription() as text
set [success, E] to scpt's compileAndReturnError:_1
if E ≠ missing value then return E's localizedDescription() as text

set scriptID to scpt's compiledScriptID()
set component to scpt's languageInstance()'s componentInstance()

set hands to reference
OSAGetHandlerNames(component, 0, scriptID, hands) of this

return hands

but the returned error seems to indicate an incompatible data type:

error "OSAGetHandlerNames unable to set argument 0 - the AppleScript value 
<NSAppleEventDescriptor: 'obj '{ 'form':'ID  ', 'want':'cptr', 'seld':'gptr'($blahblahblah$), 'from':null() }>
could not be coerced to type {ComponentInstanceRecord=[1q]}." number -10000

Am I going about this completely the wrong way ?

I’ll defer to Shane on this, but I can suggest Script Debugger’s scripting interface as an alternative. It provides access to the handlers and properties defined within a script. See the script handler and script property element collections of the document class.

Short answer: no. You can call C functions, but you can only pass (and receive) either Cocoa objects or AppleScript equivalents that are bridged. So if a function deals in other types, you’re out of luck.

Oh, I know. I have long been impressed by Script Debugger’s ability to enumerate all the various script elements for my perusal, it’s quite fantastic. I was looking into this experiment having felt, after so long, a bit tethered to have to rely on running Script Debugger in order to do this, and am looking into writing a barebones script to make use of through other apps. Besides OSAKit, I saw NSScriptingSuiteRegistry and related classes, but they seem to deal only with parsing .scriptsuite files.

It was a long-shot anyway given the lack of information available online for this, so I’m not so surprised my venture is over before it’s begun.

As I suspected. I initially wrote the script in JSObjC for this reason, but the call to OSAGetHandlerNames() crashes Script Editor. Thanks for getting back to me.

Script files can be parsed for handler names using Foundation methods. The following needs bulletproofing, but works in most cases:

use AppleScript version "2.4" -- Yosemite (10.10) or later.
use framework "Foundation"
use scripting additions

set |⌘| to current application

set scriptAlias to (choose file of type {"com.apple.applescript.script", "com.apple.applescript.script-bundle", "com.apple.application-bundle", "com.apple.applescript.text"})
set scriptURL to |⌘|'s class "NSURL"'s fileURLWithPath:(POSIX path of scriptAlias)

set {scpt, E} to |⌘|'s class "NSAppleScript"'s alloc()'s initWithContentsOfURL:(scriptURL) |error|:(reference)
if (E is not missing value) then
	set {NSAppleScriptErrorMessage:errMsg, NSAppleScriptErrorNumber:errNum} to E as record
	error "Problem with selected script: " & errMsg number errNum
end if
set src to scpt's source()
if (src is missing value) then error "No source code could be obtained from this script."

(* Regex:
	"(?m)" : Set "^" to match the beginnings of lines.
	"^\\s*+( … )" : Match optional white space at the beginning of a line, followed by a capture group containing …
	"(?:on|to) (?!error)" : … "on " or "to " not followed by "error" [move this to before the capture group to omit "on " and "to " from the results] …
	"(?:[a-zA-Z_]|\\|[^|]*+\\|)" : … followed by a non-diacritical Latin letter or an underscore or an entire barred expression …
	"(?:\\|[^|]*+\\||[^[:cntrl:]](?!--|#|\\(\\*))++" : … followed as far as they go by any barred expressions and/or non-control characters not followed by a comment indicator.
*)
set handlerRegex to |⌘|'s class "NSRegularExpression"'s regularExpressionWithPattern:("(?m)^\\s*+((?:on|to) (?!error)(?:[a-zA-Z_]|\\|[^|]*+\\|)(?:\\|[^|]*+\\||[^[:cntrl:]](?!--|#|\\(\\*))++)") options:(0) |error|:(missing value)
set regexMatches to handlerRegex's matchesInString:(src) options:(0) range:({0, src's |length|()})

set handlerTopLines to {}
set group1Template to |⌘|'s class "NSString"'s stringWithString:("$1")
repeat with thisMatch in regexMatches
	set end of handlerTopLines to (handlerRegex's replacementStringForResult:(thisMatch) inString:(src) |offset|:(0) template:(group1Template)) as text
end repeat

return handlerTopLines

Script edited 4th May 2019

1 Like

Thanks, @NigelGarvey. This is something I implemented fairly recently and, if I’m honest, it’s completely fine for my needs. I think the other thing was more an intellectual curiosity, and probably more robust than regular expression matching in some situations (though none I am likely to be worrying about).

This gets handler names. You could open the script in a new SD window and grab the handler names.

--property dostuff1 : ""
set whichWindow to 1
set handlerNames to {}
tell application "Script Debugger"
   set myHandlers to every script handler of document 1 of window whichWindow
   repeat with thisHandler in myHandlers
      set the end of handlerNames to the name of thisHandler
   end repeat
end tell
return handlerNames
on dostuff1()
   beep
end dostuff1

to DoStuff2()
   beep
end DoStuff2
on open fileList
   
end open


You could dispense with the repeat loop:

set whichWindow to 1
tell application "Script Debugger"
	set handlerNames to name of every script handler of document 1
end tell
return handlerNames

on dostuff1()
	beep
end dostuff1

to DoStuff2()
	beep
end DoStuff2
on open fileList
	
end open

Very nice Nigel! Works great for me! :+1:
It also has the advantage of returning the parameters of the handler.

This is another really nice feature of Nigel’s script: Getting the source text of a script file.

Hey Folks,

I’ve been doing this since the '90s and wrote an AppleScriptObjC-only version a while back.

Automating Handler Selection from Script Library File – Ideas?

-Chris

@JMichaelTX Thanks, Jim.

I’ve now beefed up the regex to cope with anything it’s likely to find in script code and to omit any trailing comments in the same line as the handler headers. It still can’t tell the difference between handlers in the script code and handlers in text or in comments, and it can’t tell if a handler’s in the main script or in a contained script object. But I’ll leave it at that.

@ccstone Hi Chris. Happy birthday! :slightly_smiling_face:

I don’t think I’ve ever noticed the replacementStringForResult: method before. I’d certainly never have thought to use it to return the matched text! :sunglasses: I’ve adopted it myself now in own script above. Thanks!

2 Likes