Mavericks (Mac OS X 10.9) ships with an updated version of AppleScript. One of the new features in AppleScript 2.3 is creating your own script libraries. I would like to explain in a small tutorial how this new feature can be very useful.
Pre 2.3 versions
you can use load script commands to return an instance of the script object stored in a file. This is an useful way to store script objects in files so they can be used by multiple AppleScript scripts or applications. The script object will behave much like a library, so this was one way to create your own pseudo-libraries. However, locating and managing such pseudo-libraries was always a mess and you need to write your own âincludeâ function to make sure the correct library and version are loaded, if users would make the effort. Sharing those pseudo-libraries was always difficult and rarely a success because it was hard for another user to understand the library. At the end these pseudo-libraries were mostly used by scripters locally on their machine, particularly those needing to do a lot of writing involving reuse of AppleScript code.
AppleScript Libraries
Script libraries are in basic form are pretty much the same as loading a script object from a file. In simplest form, the primary difference with loading script objects is that you donât need to know the path to the file, but the library must be stored in a fixed location. That location for libraries is a folder in one of the Libraries folders with the name âScript Librariesâ (which doesnât exist after installing the OS). You can also use AppleScriptObjC code in a library and use it in a normal script file that doesnât support AppleScriptObjC. At an even higher level you can use your own AppleScript terminology. With the use of AppleScriptObjC, your own terminology and the new loading mechanism, script libraries are a major improvement over just loading scripts.
Creating your first script library
The first thing to do is create a simple script library. It is a library that functions just like loading an script file only you do not need to load the file by its path, instead you call the script library by its name exactly as you address applications by their name. To get started in the easiest way, start with an handler that is missing in the AppleScript language. I think one of the most missed functions is string replacements. Just like a normal stored script object that we load later with the load script command we create a simple script and save it as script in the Library:
on textReplace(sourceText, searchText, replaceText)
set {TID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, searchText}
set textItems to every text item of sourceText
set AppleScript's text item delimiters to replaceText
set changedText to textItems as string
set AppleScript's text item delimiters to TID
return changedText
end textReplace
Save the script above as a script file, not a bundle, and store it in your âScript Librariesâ folder of your home folder with the name âBasic Text Utilities.scptâ. Apple suggests that when you are in the development phase you should store the script library in the Library folder of your userâs home folder. By default the folder âScript Librariesâ doesnât exist in the library folder in Mavericks. To use the script library we no longer need to know itâs path. When we use the new object specifier script AppleScript will look in the script libraries for the given script file name. We can use the script library as follows:
use myLib : script "Basic Text Utilities"
myLib's textReplace("Hxllo World!", "x", "a")
or, using a tell block
tell script "Basic Text Uilities"
textReplace("Hxllo World!", "x", "a")
end
or
script "Basic Text Utilities"'s textReplace("Hxllo World!", "x", "a")
Apart from the new object specifier script there is also the new âuseâ statement in AppleScript. The use statement for loading a script library loads an instance of the script file. Because there is no terminology definition in this script library we need to bind that instance to a variable (myLib).
Make use of AppleScriptObjC in Script Libraries
In this section Iâm not going to explain how AppleScriptObjC works. If you need that explanation, I recommend that you read Shane Stanleyâs ebook âAppleScriptObjC Exploredâ or âEveryday AppleScriptObjCâ
As we know, the power of Cocoa compared to AppleScript is huge. But in the past it was difficult for a normal script to make use of AppleScriptObjC. Having script libraries using cocoa and calling them by simple script files makes the use AppleScriptObjC even more attractive than it already was. For instance missing functions as string replacements, finding index of in arrays, sorting data or get key values of dictionaries (record) are all part of the Cocoa API and which does it all at an amazing speed compared to AppleScript. What weâre going to do is create a library as above but this time use AppleScriptObjC instead of AppleScriptâs text item delimiters to replace sub-strings in a string.
Start with a blank script and save it as a script bundle. Save it in the folder containing âBasic Text Utilities.scptâ with the name âASOC Text Utilities.scptdâ. When you store the script as script bundle, toolbar item to show the bundle contents will appear. Click this button and a drawer will appear. Inside the drawer youâll see the contents of your bundle. Inside this drawer there is a checkbox to enable AppleScriptObjC, which enables the key value OSAAppleScriptObjCEnabled key in the info.plist file. Make sure that the checkbox is checked because by default it is not in the AppleScript Editor. Now add the following code to your script.
on textReplace(sourceText, searchText, replaceText)
set cocoaString to current application's NSMutableString's stringWithString:sourceText
cocoaString's replaceOccurrencesOfString:(searchText as string) withString:(replaceText as string) options:(current application's NSCaseInsensitiveSearch) range:{0, (cocoaString's |length|())}
return cocoaString as unicode text
end textReplace
Save the script again and its use is identical to our AppleScript example:
use myLib : script "ASOC Text Utilities"
myLib's textReplace("Hxllo World!", "x", "a")
tell script "ASOC Text Uilities"
textReplace("Hxllo World!", "x", "a")
end
script "ASOC Text Utilities"'s textReplace("Hxllo World!", "x", "a")
Script Library with our own terminology
By adding our own terminology in our script library we can make script libraries behave like scripting additions. The advantage is that we can use features similar to Scripting Additions without using the complex languages of C/C++, using instead AppleScript(ObjC) code. Of course, there is a lot more you can do in a Scripting Addition, things like optional parameters which arenât supported in a scripting library with itâs own terminology. There are also classes and events you can create in a scripting addition as well. But apart from those shortcomings, these work from your (calling) script pretty much the same as Scripting Additions. First letâs take a look at how to define our own terminology.
Scripting definition files are files that are used by AppleScript to know the terminology (syntax) of applications or scripting addition in certain contexts. Real AppleEvents that are used to communicate through the system for interprocess communications are defined by a four letter code. These codes would make reading the AppleScript source code unreadable. Sometimes we use these when we want an UTF-8 encoded string for example. We use the class with a four letter code: «class utf8». Because AppleScript doesnât have a definition for this class, weâre forced to use the raw class name. Weâre going to use a definition file for the same purpose but instead of defining a class for script libraries we define commands and types. types are enumerators which can be useful for parameters with predefined values.
A valid but empty sdef file looks like the file below. Itâs an XML file that contains a dictionary element.
But first weâre going to add a suite. A suite is more or less a separator to divide different commands in different groups. For example, you can split all your commands into different suites like âText Suiteâ, âList Suiteâ, âRecord Suiteâ and âDate Suiteâ for examples of different categories. For now weâre going to use âText Suiteâ. Then we need to add a code attribute to the suite element. Here you choose a four-letter code. Apple says that they use only lower case letters and that lower case codes should only be used by Apple, or at least not in scripting definitions used in script libraries. So you must have at least 1 character uppercase or it will conflict with Appleâs own coding and rules. What I will do is using the odd position capital for commands, suite and parameter codes and using the even positions capital for enumerators. But itâs all up to you how youâre going to use them in the future, as long as at least one character is uppercase.
For all following XML elements we are going to a description attribute. This is because this description will be shown when the dictionary is opened by AppleScript Editor. So the description attributes are good for documentation about the script library for your own use but especially if you ever want to distribute it.
Once we have created a suite we can add commands and types to that suite. For now, our command is an AppleScript handler, so commands needs to be defined in an named parameter style. If we are not using restricted parameter names and restricted handler names, then weâre free to use whatever we want as long as the handler and named parameters match those in the script. For this example, I want a command named âreplace in stringâ. I add an element âcommandâ with an attribute name. The attribute ânameâ is the name of the handler weâre going to use in the script later. We also need code here for this command but this time it must be 8 characters long with the first 4 characters are the code thatâs been used for the suite. So our code ends with âStReâ.
After adding the command we want to add parameters to the command. There is a direct-parameter element which is the parameter thatâs directly behind the command. First example, the string after a do shell script command is called a direct-parameter. The direct-parameter contains a type and description. The direct-parameter can only be defined once and unlike named parameters it doesnât need a code. The type can be a self-defined enumeration type or standard AppleScript types (classes) like integer, text, and record for example.
Our command needs more than one parameter so we extend our command by adding named parameters. According to the sdef manual, named parameters support the attribute âoptionalâ, because script libraries doesnât support it Iâm not going to use those attributes here. The named parameters has a name, code, type and description. The name attribute of can contain also almost any word, but itâs better not to use confusing names. with or without are usually to set the boolean value of one ore more named parameters in one parameter. So a logical name for our second parameter to use something like âsearch forâ and âreplace withâ as our third named parameter. To show how enumrations works we add another parameter: how we search the string byte search is text search. With enumerations we need to fill in our own type. This type filled here weâre going to define later.
We also need to define our enumerator. We fill in numerations directly in the suite element next to the commands. As I said, for enumeration I use another capital type (even positions are capitalized) to avoid conflicts. And the attribute name of the enumeration element needs to be excactly the same as the value of the type we have used for âusing search optionâ. So first we need to create a container for every enumerator later:
The last part we need to do to finish out sdef file is defining the enumerators. The enumerators have a name and code attribute as well. The names will work as labels or constants.
Now save the file somewhere and give and name it âASOC Text Utilities.sdefâ, and remember were you save it. You can test if the file is correctly saved by openening the file with AppleScript Editor. If the file is not a proper sdef format it will say âthere is nothing to showâ.
Now we need to add the scripting definition file to the scripting bundle. Weâre going to re-use the script library âASOC Text Utilities.scptdâ and open it with AppleScript-editor. Open the drawer in to show itâs bundle contents (blue-white icon in your toolbar) and drop the âASOC Text Utilities.sdefâ in the file table. Fill in the Scripting Definition text field âASOC Text Utilitiesâ, the name of the sdef file without extensions. This the library itself, when editing, as software that will understand the terminology,
replace the code in the script library with the following code and save it:
on replace in string sourceText search for searchText replace with replaceText using search option searchOption
if searchOption = text search then
set searchOption to current application's NSCaseInsensitiveSearch
else
set searchOption to current application's NSLiteralSearch
end if
set cocoaString to current application's NSMutableString's stringWithString:sourceText
cocoaString's replaceOccurrencesOfString:(searchText as string) withString:(replaceText as string) options:searchOption range:{0, (cocoaString's |length|())}
return cocoaString as Unicode text
end replace in string
Now our script that will use this library has something new. Because weâre using a scripting definition it means that we have our own terminology and have extended the syntax of AppleScript. Because the libraryâs syntax is added to the AppleScript interpreter that it using the library you can also directly use the library without a tell block.
use script "ASOC Text Utilities"
replace in string "Hxllo World!" search for "X" replace with "e" using search option text search
or you can still use a safer tell block:
tell script "ASOC Text Utilities"
replace in string "Hxllo World!" search for "X" replace with "e" using search option binary search
end
For safety itâs better to use tell script block when youâre in the context of another application. When youâre in the context of the current application, I think, itâs pretty safe to call the command without the tell block around it.
Sharing libraries
Script libraries is a great improvement for AppleScript but on the other hand maybe less for MacScripter. First as you may have noticed my custom AppleScript syntax isnât supported by the AppleScript markup on MacScripter. Also having your own library can be confusing when you seek for help on MacScripter because not everybody is using the same libraries. So I would already warn everyone for this.
Software Iâve used
In the past I used Sdef Editor.app from Shadow Lab but they stopped supporting this application in 2007. It still works in Mavericks and works correctly (and itâs free). The sdef editor was developed for easily writing sdef files for applications and scripting additions. This editor has full support of the sdef file while script libraries only use a small part of it. So it can be a bit confusing. For the week before this was written I have been using Shaneâs ASObjC Explorer for Mavericks. This version of OSObjC Explorer has a built-in sdef editor that makes creating script libraries even easier, especially when using your own terminology. Also the sdef editor in Shaneâs application will only show you the options that are needed.
using the return element in sdef
For documentation purpose I use the return element in the sdef file. Here you define the return type of a command.
I hope this tutorial is useful and I welcome any comments about this tutorial.
- Shane has also a great video tutorial on his website for changing the case of a string object on Mac OS X Automation using AppleScriptObjC.
revision a: 29-10-13 Changed the named parameter with into by because with is normally used for boolean true values (thanks to Shane for pointing it out).
revision b: 01-11-13 Rearranged the order of topics in a more obvious way, thanks to McUsr
revision c: 04-11-13 Rewrote the tutorial and added some more information to it, writing 3 different kinds of libraries, extensively discudding the scripting definition file and put as much suggestion in it as possible.
Some Light editing done by A.C. Bell