Automating Handler Selection from Script Library File -- Ideas?

You don’t – you resolve the URL. Something like:

	set theURL to current application's |NSURL|'s URLByResolvingAliasFileAtURL:theURL options:0 |error|:missing value

Thanks, Shane.

It was not obvious to me where to put this statement, but after some trial-and-error, I found this worked. So for the benefit of others, to make it explicity clear, here’s the revised code snippet:

set thePath to current application's NSString's stringWithString:"~/Library/Script Libraries"

set thePath to thePath's stringByExpandingTildeInPath()
set theURL to current application's |NSURL|'s fileURLWithPath:thePath

--- RESOLVE ALIAS OR SYM LINK TO FOLDER ---
set theURL to current application's |NSURL|'s URLByResolvingAliasFileAtURL:theURL options:0 |error|:(missing value)

set fm to current application's NSFileManager's new()

Hey Folks,

Actually I’ve been doing this for well over a decade – first by pulling the recovery resource out of (SD) compiled script libraries – and now by converting the recovery rtf file (thanks be to Shane).

The appended script is Satimage.osax-dependent but is virtually instant and will NOT launch any apps by accident. (Easy enough to convert it to AppleScriptObjC.)

NOTE:

Libraries MUST be script bundles saved by Script Debugger.

You’ll want to change glob “Lb” to something else to find the correct files – I use “Lb” as a filter to find only the specific libraries I want to search (not test-libs for instance).

Currently it finds only these libraries: ELb.scptd, FLb.scptd, GLb.scptd, NLb.scptd, NLbD.scptd, which are in order: Error-lib, Find-lib, General-lib, Net-lib, NetDownloader-lib.

Due to a bug in Discourse “\\1” is not displayed property in code-blocks.

I have written it as \\\1 in the code, and the user must remove one of the backslashes to make it work properly.

-Chris

-----------------------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2014/02/09 06:02
# dMod: 2017/01/30 09:31
# Appl: Finder, Script Debugger, Script Debugger
# Task: Extract and Search Source of Script Libraries
# Libs: None
# Osax: Satimage.osax
# Tags: @Applescript, @Script, @Finder, @Script_Debugger, @Script_Debugger, @Extract, @Search, @Source, @Script_Libraries, @Libraries
-----------------------------------------------------------------------
use framework "AppKit"
use framework "Foundation"
use framework "OSAKit"
use scripting additions
-----------------------------------------------------------------------

set scptLibFldr to alias ((path to library folder from user domain as text) & "Script Libraries:")
set myPath to (path to me as text)
set recvFilePath to "Contents/Resources/Scripts/main.recover.rtf"
set tempSrc to ""

--» GET LIBRARY FILES
set libFileList to glob "*Lb*" from scptLibFldr as POSIX path of extension {"scptd"}

if libFileList = {} then
	error "Make sure your libraries are script bundles, and check the glob specifier!"
end if

repeat with _path in libFileList
   set scptRcvrFilePath to _path & recvFilePath
   set tempSrc to tempSrc & rtf2Text(scptRcvrFilePath) of me & linefeed & "•• NEWLIB ••" & linefeed
end repeat

### CHANGE THE 3 BACKSLASHES (\\\) IN THE NEXT LINE TO 2 BACKSLASHES ###
set outputList to fndUsing("^on (.+)", "\\\1", tempSrc, true, true) of me

-----------------------------------------------------------------------
--» HANDLERS
-----------------------------------------------------------------------
--» rtf2Text:
-----------------------------------------------------------------------
--  Auth: Shane Stanley
--  Task: Extract plain text from a RTF file.
--  dMod: 2014/03/23 14:22
-----------------------------------------------------------------------
on rtf2Text(thePath)
   set attString to current application's NSAttributedString's alloc()'s initWithPath:thePath documentAttributes:(missing value)
   return (attString's |string|()) as text
end rtf2Text
-----------------------------------------------------------------------
--» HANDLERS
-----------------------------------------------------------------------
on fndUsing(_find, _capture, _data, _all, strRslt)
   try
      set findResult to find text _find in _data using _capture all occurrences _all ¬
         string result strRslt with regexp without case sensitive
   on error
      false
   end try
end fndUsing
-----------------------------------------------------------------------

EDITED 2017/01/31 19:05 CST
- Code changed to allow for a bug in Discourse’s code-blocks.
- Added basic error-handling if no libraries are found.

Many thanks, Chris.
I had a feeling we’d be hearing from you.

I’m getting one compile error on this line:

### COMPILE ERROR ON THIS LINE ###
set outputList to fndUsing("^on (.+)", "\`", tempSrc, true, true) of me

### CHANGE "\`" TO "\\`" ### 
set outputList to fndUsing("^on (.+)", "\\`", tempSrc, true, true) of me

Is this a correct change?

In case it’s not obvious to anyone following along, this requires that your library files are all saved as .scptd, and not simple .scpt files.

@ShaneStanley, I’m getting an error with your handler:

use scripting additions
use framework "AppKit"
use framework "Foundation"
use framework "OSAKit"


set scriptPath to "/Users/Shared/Dropbox/Mac Only/Alias Folders/Script Libraries/[LIB] JMichael Lib AS.scptd"

set scriptStr to my rtf2Text(scriptPath)


--» rtf2Text:
-----------------------------------------------------------------------
--  Auth: Shane Stanley
--  Task: Extract plain text from a RTF file.
--  dMod: 2014/03/23 14:22
-----------------------------------------------------------------------
on rtf2Text(thePath)
  set attString to current application's NSAttributedString's alloc()'s initWithPath:thePath documentAttributes:(missing value)
  return (attString's |string|()) as text
end rtf2Text
-----------------------------------------------------------------------

This error:

missing value doesn’t understand the “string” message.

at this line:

Running Script Debugger 6.0.3 (6A191) on macOS 10.11.4.

Any ideas on how to fix?

I did try restarting SD6.

That’s telling you that attString is missing value, which is not surprising because the handler is expecting you to pass it the path to an rtf file, not a script. Look at Chris’s script and see how he’s building a path to the bundle’s main.recover.rtf file.

It would probably help to rewrite the handler as:

on rtf2Text(thePath)
	set {attString, theError} to current application's NSAttributedString's alloc()'s initWithPath:thePath documentAttributes:(reference)
	if attString is missing value then error (theError's localizedDescription() as text)
	return (attString's |string|()) as text
end rtf2Text

Then you will hopefully get some enlightenment if something goes wrong.

Thanks. I overlooked that.

Thanks for the update.

Here’s my revised script, which works as expected, returning a clean, plain-text version of my Script Library (.scptd):

use scripting additions
use framework "AppKit"
use framework "Foundation"
use framework "OSAKit"


set scriptPath to "/Users/Shared/Dropbox/Mac Only/Alias Folders/Script Libraries/[LIB] JMichael Lib AS.scptd"

set recoverRTFSubPath to "Contents/Resources/Scripts/main.recover.rtf"
set scriptRTFPath to scriptPath & "/" & recoverRTFSubPath

set scriptStr to my rtf2Text(scriptRTFPath)
set the clipboard to scriptStr

--- REVISED HANDLER FROM SHANE ---

on rtf2Text(thePath)
  set {attString, theError} to current application's NSAttributedString's alloc()'s initWithPath:thePath documentAttributes:(reference)
  if attString is missing value then error (theError's localizedDescription() as text)
  return (attString's |string|()) as text
end rtf2Text

Here is an alternate version able to do the job with every library including those saved from an other editor or even being flat files.

[code] use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

property storeInClipboard : true

true = store the text in the clipboard

false = store the text on the Desktop

set POSIXSource to POSIX path of ((path to library folder from local domain as text) & “Script Libraries:General Lib.scptd:”)

if not storeInClipboard then
set pathNSString to current application’s NSString’s stringWithString:POSIXSource
set bareNSString to (pathNSString’s stringByDeletingPathExtension())
set POSIXName to (bareNSString’s lastPathComponent()'s stringByAppendingPathExtension:“txt”)
set HfsName to POSIXName’s stringByReplacingOccurrencesOfString:":" withString:"/"
set destURL to current application’s |NSURL|'s fileURLWithPath:(POSIX path of (path to desktop))
set destURL to destURL’s URLByAppendingPathComponent:HfsName
end if

set theString to do shell script "osadecompile " & quoted form of POSIXSource

if storeInClipboard then
set the clipboard to theString
else
set theString to current application’s NSString’s stringWithString:theString
set theResult to theString’s writeToURL:destURL atomically:true encoding:(current application’s NSUTF8StringEncoding) |error|:(missing value)
end if
#===== [/code]

Yvan KOENIG running Sierra 10.12.3 in French (VALLAURIS, France) mercredi 15 mars 2017 16:02:44

Hey Folks,

I originally wrote scripts to extract Script Debugger AppleScript-Library text back in the mid-1990’s using one of the osaxen that would read file resources (AKUA Sweets?). The major design criteria was speed, and even on that old hardware with MacOS 8-9 the resource-reading-method was very, very fast.

I had some scripts that employed an osax with regular expression support and the Dialog Director osax to do all kinds of nice things with that source-text.

When I finally moved to OSX in 2002 (Jaguar) I was fairly heartbroken to lose all of my osaxen (outside of the Classic environment), but the following year (2003) the Satimage.osax came out and solved most of my problems. It had regEx support and could read/write file resources.

I’ve used osadecompile on and off since it became available, but I’ve always avoided it like the plague for regular operations – because it has the bad habit of launching certain applications when decompiling scripts containing their terminology.

Having 20 apps launch unexpectedly whenever you run a script was (and is) NOT supportable, so I kept using the file-resource-reading method.

Fast-forward to Script Debugger 6 — Mark discontinues Script Debugger libraries in favor of the new AppleScript library structure that debuted in Mavericks.

I would have to change my ways — again…

I tried osascript again, and found it still had a penchant to launch apps – so I looked for another method.

Shane gave me the idea to use the main.recover.rtf files in Script Debugger’s script-bundle scripts and helpfully provided some AppleScriptObjC code to do the job.

This didn’t work for anyone who didn’t have Script Debugger, but it was outstanding for me – because it was lightning fast and never, ever launched an app I didn’t want launched.

I’ve used this method for years to do all kinds of useful things.

Now – let’s revisit osadecompile.

Sometime in the last few years I asked Shane if that job could be done with AppleScriptObjC, and the answer was yes. Unfortunately it still launched apps I didn’t want launched, so I gave up on it again.

But — I tested recently on Sierra and didn’t have any app-launching problems. I don’t know if this has something to do with OS changes, or whether more developers are using the modern sdef format that stops that misbehavior. In any case osascript is working on my system (so far) without launching apps without my consent.

The AppleScriptObjC script below replicates the sort of thing I’ve been doing for just about 20 years — the last 14 ± using the Satimage.osax.

  • It filters the libraries to only the ones I want using a regular expression.

  • It extracts the source-text of all found libraries.

  • It has regular expression support for filtering the output text.
      – There’s a pre-built filter to output only handler-calls.

------------------------------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2017/03/12 22:04
# dMod: 2017/03/15 16:52
# Appl: AppleScriptObjC
# Task: Osadecompile from ASObjC - decompile AppleScript Libraries.
#     : RegEx filter to select only desired libraries.
#     : RegEx filter to output only hander-calls.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @ASObjC, @OSAdecompile, @Decompile, @Library, @Libraries
------------------------------------------------------------------------------
use framework "Foundation"
use framework "OSAKit"
use scripting additions
------------------------------------------------------------------------------

set scptLibFldrPath to "~/Library/Script Libraries/"
set scptLibFldrPath to current application's NSString's stringWithString:scptLibFldrPath
set scptLibFldrPath to scptLibFldrPath's stringByExpandingTildeInPath() as string

set libFileList to its findFilesWithRegEx:".*Lb.*" inDir:scptLibFldrPath -- filter files using a regEx

set scriptLibraryText to {}

repeat with libFile in libFileList
   set end of scriptLibraryText to linefeed & "••••• LIBRARY SEPARATOR •••••" & linefeed
   set end of scriptLibraryText to (its extractScriptSourceFrom:libFile)
end repeat

set AppleScript's text item delimiters to linefeed

set scriptLibraryText to scriptLibraryText as text -- entire text of all libraries

# return scriptLibraryText

# Filter library text using a regular expression – extract handler-calls:
set handlerList to its regexMatch:"(?m)^on (\\w.+)" fromString:scriptLibraryText captureTemplate:"$1"
set handlerList to handlerList as text

return handlerList

------------------------------------------------------------------------------
--» HANDLERS
------------------------------------------------------------------------------
on extractScriptSourceFrom:scriptPath
   set aURL to current application's |NSURL|'s fileURLWithPath:scriptPath
   set theScript to current application's OSAScript's alloc()'s initWithContentsOfURL:aURL |error|:(missing value)
   return theScript's source() as text
end extractScriptSourceFrom:
------------------------------------------------------------------------------
on findFilesWithRegEx:findPattern inDir:srcDirPath
   set fileManager to current application's NSFileManager's defaultManager()
   set sourceURL to current application's |NSURL|'s fileURLWithPath:srcDirPath
   set theURLs to fileManager's contentsOfDirectoryAtURL:sourceURL includingPropertiesForKeys:{} options:(current application's NSDirectoryEnumerationSkipsHiddenFiles) |error|:(missing value)
   set theURLs to theURLs's allObjects()
   set foundItemList to current application's NSPredicate's predicateWithFormat_("lastPathComponent matches %@", findPattern)
   set foundItemList to theURLs's filteredArrayUsingPredicate:foundItemList
   set foundItemList to (foundItemList's valueForKey:"path") as list
end findFilesWithRegEx:inDir:
------------------------------------------------------------------------------
on regexMatch:thePattern fromString:theString captureTemplate:templateStr
   set theString to current application's NSString's stringWithString:theString
   set theRegEx to current application's NSRegularExpression's regularExpressionWithPattern:thePattern options:0 |error|:(missing value)
   set theFinds to theRegEx's matchesInString:theString options:0 range:{0, theString's |length|()}
   set theResult to current application's NSMutableArray's array()
   
   repeat with aFind in theFinds
      set foundString to (theRegEx's replacementStringForResult:aFind inString:theString |offset|:0 template:templateStr)
      (theResult's addObject:foundString)
   end repeat
   
   return theResult as list
   
end regexMatch:fromString:captureTemplate:
------------------------------------------------------------------------------

I have a script that uses a similar method to export all of my handler-calls to a couple of sets in Typinator with one keystroke. It takes less than 1/2 a second to refresh the sets when I’ve changed my libraries.

Typinator’s Quick Search feature gives me very quick, smart-searchable access to my handlers.

I keep the ones I use every day in Script Debugger’s own text-substitutions, but this lets me find what I want on demand and is especially convenient when my memory is fuzzy.

-Chris

Whatever it is, if an app says it has a dynamic dictionary, there’s no getting around launching it. There might be an element of luck skill in your selection of applications :slight_smile:

1 Like

Hey Shane,

Please explain briefly what a dynamic dictionary is, or point to some informative documentation.

Possibly (and I did consider this).

On the other hand if I add an OmniWeb handler to one of my libraries the script I just posted will NOT launch it — even though:

  1. An OmniWeb script will immediately launch the app when compiled or saved.
  • osadecompile run from the shell on a library with an OmniWeb handler in it will launch the app.

This behavior is definitely different than when I first tested your ASObjC decompile code (in 2014 if memory serves).

Any ideas about the change in behavior?

-Chris

It’s one that has an entry for OSAScriptingDefinitionIsDynamic in its Info.plist file. InDesign is a good example: it supports scriptable plug-ins, so the dictionary has to be dynamic.

Nope. I’m surprised.

Chris,

Many thanks for your continued development and sharing of this great script! :+1:

I just tested it on my system, and it ran extremely fast, 0.17 sec, and did NOT cause any apps to launch or activate. :+1:

I’m running Script Debugger 6.0.4 (6A198) on macOS 10.11.6.

1 Like

In MarksLib.scptd is the handler :

on |join|(theString, theSeperator)
local saveTID, theResult

set saveTID to AppleScript's text item delimiters
set AppleScript's text item delimiters to theSeperator
set theResult to theString as text
set AppleScript's text item delimiters to saveTID
return theResult

end |join|

The script drops it because it doesn’t accept pipe in the name of handlers.

Hey Shane,

Can you check InDesign against Nigel’s most recent script using a test script with one or more InDesign handlers in it?

Retrieving Handler Names From Script File

I double-checked my conclusions in post #20 above on Sierra, and they check out.

If InDesign also fails to launch then I think it’s safe to say this launching apps issue is solved – at least when using AppleScriptObjC.

I’m also curious about how this behaves on Mojave.

-Chris

InDesign does launch.

Hey Shane,

Bleep!

Thanks for testing.

Is it possible that using current application's OSAScript's instead of Nigel’s NSAppleScript would make a difference?

-Chris

No. The whole thing is behaving as it was designed to.

Lots of good ideas above. Here is one that is a bit orthogonal. I tag every handler with “-- HANDLER” Then I use a KBM hot key that launches a script that takes all selected text in a ScriptDebugger window, runs it through awk, and spits out a list all the handlers in the selected text. You open the library in Script Debugger and hit ⌘A, hit your hot key, and a new window pops open with list.

The AppleScript called by KBM is:

tell application "Script Debugger"
	set wName to name of front window
	set iName to "INCLUDE TABLE FOR " & wName
	set x to selection of front window
	set the clipboard to x
	do shell script "sh /Users/jjh/bin/generate_include_table.sh"
	set tableDoc to make new document with properties {name:iName}
	set newSource to "############ " & iName & " ############" & "\n\n" & (the clipboard)
	set source text of tableDoc to newSource
	set the clipboard to newSource
end tell

The shell script is:

pbpaste | tr "\r" "\n" | grep HANDLER | sed "s/on //" | sed "s/ -- HANDLER//" |  \
awk '{printf("on %s\ntell script \"JJH AppleScript Library\"\nset out to %s\nreturn out\nend\nend\n\n",$0,$0)}' | \
pbcopy

The (compiled) output looks like this:


on BubbleSort(the_list)
	tell script "JJH AppleScript Library"
		set out to BubbleSort(the_list)
		return out
	end tell
end BubbleSort

on Evernote_edit_html(note_or_notename)
	tell script "JJH AppleScript Library"
		set out to Evernote_edit_html(note_or_notename)
		return out
	end tell
end Evernote_edit_html

For what you want, remove the awk code and change the second sed.

I’m using the extra stuff to generate an “include” file of the relevant handler library that lets you call handlers in your library without all of the annoying tell script “My Library” / out=myhandler() / end script business.

For example, you can be deep down in an Evernote script and

    set editedNote to my Evernote_edit_html(noteOrNotename)

will call Sublime Text 2 to let you edit the note’s HTML, then stick it back in the note when you are done. I find that ascetically more pleasing than

    tell script "Library Name"
        set editedNote to Evernote_edit_html(noteOrNotename)
    end tell

Maybe all of this is simpleminded; I don’t know. But it works. As I said, I am brand new to the AppleScript game, and don’t know the tricks of the trade. I still can’t quite wrap my head around not being able to properly link to compiled libraries. Or can you and I just haven’t figured out how yet?